mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 11:49:28 +00:00
Scanning a two-file WordPress slice returned 64 regions examined, zero
findings and zero degradations. The graph it examined had no methods in it.
php2cpg 4.0.623 feeds several files to one `php-parse` process and reads its
stdout and stderr merged. php-parse writes a `====> File <next>:` banner to
stderr the moment it starts the next file, while the previous file's JSON --
megabytes of it -- is still draining from a block-buffered stdout. The banner
lands inside the JSON, ujson reports
expected json value or } got "=" at index 5011645
and the frontend drops that file. Reproducible on real plugin code, and the
size that matters is one file's AST dump, not the repository's:
class.memberorder.php alone (1834 lines) -> 142KB CPG, no failures
the same file + a two-line file -> 5.7KB CPG, BOTH dropped
One oversized file empties the whole graph. `joern-parse` then does not
propagate any of the frontend's warnings: 13 lines of output, exit status 0,
"Successfully wrote graph". This is what made a 637-file plugin unscannable
and reported it as clean -- it was never a scale limit.
Two fixes, because the failure arrives on two different paths:
* `_unparsed_files` reads the per-file warnings the frontend does emit, and
the scan reports them as `files_unparsed`. This covers the direct-frontend
path where the warnings are visible.
* The batched query now carries a `__census__` of the graph -- method and
file counts -- under a key no request id can collide with, and a scan over
a graph with no methods degrades as `cpg_empty`. This covers joern-parse,
which shows nothing. It rides the batch rather than costing its own
invocation, because avoiding a JVM start is the entire reason batching
exists.
Neither invents a verdict. There is no rung for "the graph was empty" because
the ladder labels verdicts and none was produced, which is exactly why it has
to be a degradation.
pmpro two-file slice before: 0 findings, degradations=[]
after: 0 findings, degradations=[cpg_empty]
Gates byte-identical; 840 tests pass.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
49 lines
2.4 KiB
TOML
49 lines
2.4 KiB
TOML
# WP Statistics 13.1.5 -- a WordPress plugin with an unauthenticated SQL injection reached through a hook.
|
|
#
|
|
# The third WordPress checkout, and the one chosen because it is NOT expected to be found. PMPro's CVE is a
|
|
# concatenation inside the function that receives the value; this one crosses WordPress's filter dispatcher,
|
|
# which is the shape most plugin data actually travels in. A table that only lists hits teaches nothing about
|
|
# where the engine stops.
|
|
#
|
|
# Verified in the code. At this commit `includes/class-wp-statistics-pages.php:225` reads:
|
|
#
|
|
# $exist = $wpdb->get_row("SELECT `page_id` FROM ... AND `id` = {$current_page['id']}", ARRAY_A);
|
|
#
|
|
# and one line below, the SAME value is used correctly:
|
|
#
|
|
# $wpdb->query($wpdb->prepare("UPDATE ... AND `id` = %d", $current_page['id']));
|
|
#
|
|
# The value arrives from an unauthenticated REST hit. `Hits::rest_params()` copies every `$_REQUEST` key into
|
|
# `$this->rest_hits`; `Hits::set_current_page()` returns `'id' => $this->rest_hits->current_page_id`; and it
|
|
# is bound to the sink not by a call but by
|
|
#
|
|
# add_filter('wp_statistics_current_page', array($this, 'set_current_page')); # hits.php:43
|
|
# return apply_filters('wp_statistics_current_page', $current_page); # pages.php:105
|
|
#
|
|
# Both ends of that edge name the callback with a STRING, so no frontend can draw it. The fix (bc98b414)
|
|
# wraps both values in `esc_sql`, which is a modelled sanitizer -- so if the flow were visible at all, the
|
|
# pair would separate.
|
|
|
|
name = "wpstatistics"
|
|
url = "https://github.com/wp-statistics/wp-statistics.git"
|
|
commit = "ca66e7b89587dce3ee189f1eb42211ad111db4b7"
|
|
language = "php"
|
|
license = "GPL-2.0"
|
|
license_file = "license.txt"
|
|
measures = ["detection"]
|
|
note = """
|
|
The parent of the fix commit bc98b414 ("Sanitize current_page_id & current_page_type in rest request",
|
|
2022-02-13), released in the 13.1.x line. Nothing is vendored -- the recipe fetches on request and CI never
|
|
does.
|
|
"""
|
|
|
|
[[known]]
|
|
id = "CVE-2022-25148"
|
|
family = "injection"
|
|
weakness = "unauthenticated SQL injection: current_page_id from an unauthenticated REST hit is interpolated into a $wpdb->get_row query, having crossed the wp_statistics_current_page filter"
|
|
file = "includes/class-wp-statistics-pages.php"
|
|
function = "record"
|
|
line = 225
|
|
reference = "fixed in bc98b4146afd6f4b29fda1136772dcf1a42f26e6, which applies esc_sql to current_page_type and current_page_id at their source; the vulnerable line is verified in the checkout"
|
|
in_scope = true
|