mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 12:39:28 +00:00
fix(scan): a hook callback's parameters are the registry's arguments, not the request's
The core-hook-argument class from the second PMPro adjudication: pmpro_membership_history_profile_fields($user) on `edit_user_profile` was a route entry, so its parameters were attacker input -- and $user is the WP_User WordPress loaded; its ID became an SQL injection. A callback registered in a string-keyed registry is handed its arguments by the matching apply call, not by the request. The mapper now records the registration call on the entry (`registered_by`), regions carry it, and the dispatch fact says which registrations DO hand a callback request-borne arguments (`request_arguments`; WordPress: add_shortcode, whose $atts are post content). Every other registered callback keeps its call depth but its parameters stop being sources; request data that reaches an apply call's arguments is still followed by the hook half of the taint query. REST routes are not registry entries and keep the parameter rule. Controls: the fact field is validated against `register`; a hook callback is not input, a shortcode and a plain route are. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7b8ff3cf44
commit
ece4473c2b
@@ -28,6 +28,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from ..cpg.backend import TIMING, CpgResult
|
||||
from ..semantic.facts import load_facts
|
||||
from .candidates import enumerate_candidates
|
||||
from .config_value import request_params as config_params
|
||||
from .contracts import (
|
||||
@@ -1514,7 +1515,23 @@ def _parameters_are_input(region: ScanRegion) -> bool:
|
||||
keep the rule; JavaScript and Python emit no `function` entries at all. The region is still followed into
|
||||
its callees -- call depth stays with `_is_entry_point` -- only its parameters stop being sources.
|
||||
"""
|
||||
return _is_entry_point(region) and region.entry_kind != "function"
|
||||
return _is_entry_point(region) and region.entry_kind != "function" and not _registry_supplied(region)
|
||||
|
||||
|
||||
def _registry_supplied(region: ScanRegion) -> bool:
|
||||
"""Was this entry registered in a string-keyed registry whose callbacks get the REGISTRY's arguments?
|
||||
|
||||
A WordPress hook callback is called by `do_action`/`apply_filters` with whatever the call site passes:
|
||||
`pmpro_membership_history_profile_fields($user)` on `edit_user_profile` gets the `WP_User` WordPress
|
||||
loaded, and an adjudicated SQL injection on PMPro was that object's `ID`. The dispatch facts say which
|
||||
registrations do hand a callback request-borne arguments (a shortcode's `$atts`); every other one does not.
|
||||
"""
|
||||
if not region.entry_registry:
|
||||
return False
|
||||
for fact in load_facts().for_language(region.language).dispatches:
|
||||
if region.entry_registry in fact.register:
|
||||
return region.entry_registry not in fact.request_arguments
|
||||
return False
|
||||
|
||||
|
||||
def _spec_for(family: str, language: str) -> ArbiterSpec | None:
|
||||
|
||||
Reference in New Issue
Block a user