# REPRO-2026-00239: WP Review Slider Pro SQL injection ## Summary Status: published Severity: high Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00239 CVE: CVE-2026-8441 ## Package Name: Unknown Ecosystem: Unknown Affected: Unknown Fixed: Unknown ## Root Cause ## Summary CVE-2026-8441 is an unauthenticated SQL injection vulnerability in the WP Review Slider Pro WordPress plugin. The vulnerable public AJAX action is `wprp_load_more_revs`, reached through `/wp-admin/admin-ajax.php` from a public page rendering the plugin shortcode. The genuine WP Review Slider Pro 12.6.7 code reads the request field `notinstring` in `WP_Review_Pro_Public::wppro_loadmore_revs_ajax()`, passes it through `sanitize_text_field()`, and later concatenates it into an unquoted numeric `AND id NOT IN (...)` SQL fragment in `GetReviews_Functions::wppro_queryreviews()`. The reproduction proves the issue against a live WordPress/MariaDB service using the genuine plugin source zip, with time-based and conditional extraction payloads, and then proves a line-accurate fixed patch makes the same endpoint fast. ## Impact - **Package/component affected:** WP Review Slider Pro (Premium), WordPress plugin slug/codebase `wp-review-slider-pro`; public AJAX action `wprp_load_more_revs`; vulnerable helper `public/partials/getreviews_class.php`. - **Affected versions:** Public advisory states versions up to and including 12.7.2. The runtime proof uses a genuine WP Review Slider Pro (Premium) 12.6.7 source zip (`sha256: 7f6092f4ea58b5c5c0dba72ba014b9395bf9608f9aeeb95206e80dc14f0b17e5`), which contains the vulnerable line-accurate code path. - **Risk level and consequences:** High. An unauthenticated attacker who can access a public page rendering a WP Review Slider Pro template can send a POST to WordPress `admin-ajax.php` and inject SQL into the plugin's review query. The reproduced consequence is blind SQL injection sufficient to infer database contents via conditional delays, demonstrated by extracting the first character of the WordPress admin username. ## Impact Parity - **Disclosed/claimed maximum impact:** SQL injection with ability to extract database data remotely and unauthenticated. - **Reproduced impact from this run:** Full remote API reachability and blind SQL injection. The script demonstrates normal fast response (~0.106s), repeated `SLEEP(5)` delays (~5.03s and ~5.02s), `SLEEP(3)` delay (~3.02s), and a conditional extraction oracle where `admin` username first-character test for `a` delays (~3.02s) while the false `x` test is fast (~0.016s). - **Parity:** `full` for the SQL injection/data-extraction claim. - **Not demonstrated:** Bulk dumping of the entire database was not performed; the proof stops after a minimal conditional data-extraction primitive to avoid unnecessary exposure. Code execution was not claimed or demonstrated. ## Root Cause The root cause is insufficient SQL neutralization of an attacker-controlled request parameter in a public AJAX handler: 1. `WP_Review_Pro_Public::wppro_loadmore_revs_ajax()` is registered for both authenticated and unauthenticated users via `wp_ajax_wprp_load_more_revs` and `wp_ajax_nopriv_wprp_load_more_revs`. 2. The handler performs a nonce check, but the nonce is exposed to public visitors by `wp_localize_script()` on frontend pages rendering `[wprevpro_usetemplate]`. 3. The handler reads `$_POST['notinstring']` and applies `sanitize_text_field()`. This WordPress text sanitizer is not an SQL escaping or parameterization mechanism; it does not remove SQL operators such as `) OR (SELECT SLEEP(...))#`. 4. In `public/partials/getreviews_class.php`, the plugin calls `explode(',', $notinstring)` and immediately rejoins the array with `implode(',', $tempnotinarray)`, then builds `AND id NOT IN ($notinstring)` by string concatenation. 5. The resulting query is executed by `$wpdb->get_results()` without a prepared statement or integer casting for the `NOT IN` list. The line-accurate patch used in the fixed control changes the vulnerable line to cast the CSV list through `absint` before it is rejoined: ```php $tempnotinarray = array_filter(array_map('absint', explode(",", $notinstring))); ``` This preserves numeric review IDs while removing SQL syntax from the `NOT IN` list. The public advisory names fixed version 12.7.3; no public source commit was available because the plugin is distributed commercially. ## Reproduction Steps 1. Run `bundle/repro/reproduction_steps.sh`. 2. The script: - verifies the bundled genuine WP Review Slider Pro 12.6.7 zip and records source identity; - installs/uses PHP, MariaDB, WordPress, and WP-CLI; - activates the genuine plugin in a live WordPress instance; - seeds real plugin tables with review/template data and publishes a page containing `[wprevpro_usetemplate tid="1"]`; - retrieves the public nonce from that page; - sends real HTTP POST requests to `/wp-admin/admin-ajax.php` with `action=wprp_load_more_revs`, the nonce, and attacker-controlled `notinstring` payloads (the request also includes `noti` for ticket terminology, but the genuine plugin consumes `notinstring`); - captures vulnerable timing and response artifacts; - patches the genuine product file in place with a line-accurate integer-casting fix; - restarts the PHP service and proves the same payload is fast after patching. 3. Expected evidence of reproduction: - vulnerable `SLEEP(5)` requests take approximately five seconds twice; - vulnerable conditional true extraction takes approximately three seconds while the false condition is fast; - fixed `SLEEP(5)` attempts are fast after the patch; - response JSON contains `dbcall` fields showing injected SQL before the patch and sanitized `AND id NOT IN (1)` after the patch. ## Evidence - **Primary runtime log:** `bundle/logs/reproduction_steps.log` - **Evidence summary:** `bundle/logs/evidence.log` - **Runtime manifest:** `bundle/repro/runtime_manifest.json` - **Timing summary:** `bundle/artifacts/timing_summary.json` - **Vulnerable HTTP responses:** - `bundle/artifacts/vuln_sleep5_attempt1_response.json` - `bundle/artifacts/vuln_sleep5_attempt2_response.json` - `bundle/artifacts/vuln_data_true_response.json` - `bundle/artifacts/vuln_data_false_response.json` - **Fixed HTTP responses:** - `bundle/artifacts/fixed_sleep5_warmup_response.json` - `bundle/artifacts/fixed_sleep5_attempt1_response.json` - `bundle/artifacts/fixed_sleep5_attempt2_response.json` - **Source identity and patch evidence:** - `bundle/logs/plugin_source_identity.log` - `bundle/logs/real_notinstring_patch.diff` - `bundle/artifacts/real_vulnerable_getreviews_excerpt.txt` - `bundle/artifacts/real_ajax_handler_excerpt.txt` Key runtime evidence from the final verified run: ```text Endpoint: POST http://127.0.0.1:36179/wp-admin/admin-ajax.php Action: wprp_load_more_revs Nonce source: public page rendering [wprevpro_usetemplate tid="1"] Vulnerable timings: normal notinstring=1,2,3: 0.106269s SLEEP(5) attempt 1: 5.028141s SLEEP(5) attempt 2: 5.018714s SLEEP(3): 3.017385s conditional user_login first char == 'a': 3.016486s conditional user_login first char == 'x': 0.015544s Fixed patched-product timings: SLEEP(5) warmup after patch (ignored): 0.046941s SLEEP(5) attempt 1: 0.016054s SLEEP(5) attempt 2: 0.015599s normal: 0.016293s ``` The runtime manifest records `entrypoint_kind="api_remote"`, `service_started=true`, `healthcheck_passed=true`, and `target_path_reached=true`. ## Recommendations / Next Steps - Update WP Review Slider Pro to version 12.7.3 or later. - Apply integer allow-listing/casting to every numeric `IN`/`NOT IN` list built from request data; using `array_map('absint', ...)` is appropriate for review ID lists. - Prefer `$wpdb->prepare()` for all dynamic SQL, and avoid concatenating request-controlled strings into SQL fragments. - Add regression tests for `wprp_load_more_revs` with malicious `notinstring` values such as `1) OR (SELECT SLEEP(5))#`, verifying the resulting query contains only numeric IDs and has no timing delay. - Audit related AJAX handlers in the same plugin for similar array/CSV-to-SQL concatenation patterns. ## Additional Notes - The script was run successfully twice consecutively after fixes, confirming idempotency in a clean runtime directory under `bundle/artifacts/runtime`. - The ticket text abbreviates the affected parameter as `noti`; the real WP Review Slider Pro code and public advisories identify the consumed parameter as `notinstring`. The reproduction includes the `noti` field in requests for traceability, but the genuine plugin's vulnerable sink is `$_POST['notinstring']`. - The fixed control is a line-accurate local patch to the same genuine product file because a public vendor git commit was not available for the commercial plugin. ## Reproduction Details Reproduced: 2026-07-06T08:22:36.448Z Duration: 2520 seconds Tool calls: 309 Turns: Unknown Handoffs: 3 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00239 pruva-verify CVE-2026-8441 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00239&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00239/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-8441 - Source: https://nvd.nist.gov/vuln/detail/CVE-2026-8441 ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 24656 bytes) - bundle/repro/rca_report.md (analysis, 8595 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 26212 bytes) - bundle/vuln_variant/rca_report.md (analysis, 10523 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 1262 bytes) - bundle/logs/evidence.log (log, 1259 bytes) - bundle/repro/runtime_manifest.json (other, 1351 bytes) - bundle/repro/validation_verdict.json (other, 792 bytes) - bundle/logs/reproduction_steps.log (log, 7397 bytes) - bundle/logs/plugin_source_identity.log (log, 2494 bytes) - bundle/logs/real_notinstring_patch.diff (other, 1041 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 6262 bytes) - bundle/vuln_variant/variant_manifest.json (other, 3754 bytes) - bundle/vuln_variant/validation_verdict.json (other, 3208 bytes) - bundle/logs/vuln_variant/variant_evidence.log (log, 1173 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 1384 bytes) - bundle/logs/vuln_variant/reproduction_steps.log (log, 10854 bytes) - bundle/logs/vuln_variant/variant_plugin_source_identity.log (log, 5247 bytes) - bundle/logs/vuln_variant/variant_notinstring_patch.diff (other, 1110 bytes) - bundle/logs/vuln_variant/fixed_version.txt (other, 326 bytes) - bundle/vuln_variant/artifacts/variant_timing_summary.json (other, 500 bytes) - bundle/vuln_variant/artifacts/candidate_matrix.json (other, 756 bytes) - bundle/vuln_variant/artifacts/fixed_original_notinstring_sleep_response.json (other, 1524 bytes) - bundle/vuln_variant/artifacts/fixed_textsource_sleep_response.json (other, 448 bytes) - bundle/vuln_variant/artifacts/fixed_textsearch_sleep_response.json (other, 750 bytes) - bundle/vuln_variant/artifacts/fixed_textlang_sleep_response.json (other, 473 bytes) - bundle/vuln_variant/artifacts/fixed_textrtype_sleep_response.json (other, 467 bytes) - bundle/vuln_variant/artifacts/fixed_shortcodepageid_sleep_response.json (other, 477 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00239 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00239/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00239 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev