mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 20:49:28 +00:00
feat(rules): Django request sources; a destination with a fixed origin is not untrusted
python.toml's sources were Flask-only, so no flow could start anywhere on a Django
project. OpenCVE's SSRF reads its URL from `form.cleaned_data`, and the scan answered
the sink file and found nothing. Added: request.GET/POST/body/COOKIES/FILES/META,
DRF's query_params, and cleaned_data.
With those sources known, OpenCVE reported three redirects that cannot leave the
site: `reverse("cves") + "?" + request.GET.urlencode()` (twice) and
`reverse(route, kwargs=...)`. Destination sink facts can now set
`prefix_fixes_origin` and `origin_anchors`. A path is cut when it enters a
concatenation, f-string or `+=` through anything other than its leftmost operand,
and that operand fixes the origin. Origin-fixing operands:
- a literal `/x`, `?` or `#`;
- a literal of the form scheme://host/;
- an anchor call;
- a variable whose every assignment fixes the origin.
A path is also cut when it passes through an anchor. `"/" + x` (//evil) and
`"https://" + host` stay reported. A Joern-gated test covers all seven shapes.
Exploratory (retune after protocol-v1): OpenCVE vulnerable and fixed pins are both
0 findings, down from 2 and 3. The SSRF itself is still missed, because the flow
crosses a registry dispatch (`REGISTRY.get(t)(extras).run()`) that the engine does
not follow.
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
ec3d1457fb
commit
562b670a43
@@ -118,6 +118,10 @@ class TaintSpec:
|
||||
# Which argument of each apply call its RESULT comes from, as `call:position`; position 0 means the call
|
||||
# returns nothing a flow can carry. Only calls a fact describes appear here.
|
||||
dispatch_value: tuple[str, ...] = ()
|
||||
# Whether a prefix that fixes the origin makes a destination safe, and the calls that build such a
|
||||
# prefix (`reverse`). From the sink facts; only destination families set them.
|
||||
fixed_origin: bool = False
|
||||
origin_anchors: tuple[str, ...] = ()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -255,6 +259,8 @@ def taint_specs(
|
||||
safe_shapes = tuple(sorted({call for fact in scoped.sanitizers if _is_shape(fact) for call in fact.calls}))
|
||||
by_family: dict[str, set[str]] = {}
|
||||
bounded: dict[str, set[str]] = {}
|
||||
fixed_origin: set[str] = set()
|
||||
anchors: dict[str, set[str]] = {}
|
||||
for sink in scoped.sinks:
|
||||
family = families.family_of_cwe(sink.cwe)
|
||||
if family is None:
|
||||
@@ -264,6 +270,9 @@ def taint_specs(
|
||||
# is on the fact already, so which sinks these are is read from the data rather than declared here.
|
||||
if sink.cwe in OVERFLOW_CWES:
|
||||
bounded.setdefault(family.id, set()).update(sink.calls)
|
||||
if sink.prefix_fixes_origin:
|
||||
fixed_origin.add(family.id)
|
||||
anchors.setdefault(family.id, set()).update(sink.origin_anchors)
|
||||
return {
|
||||
family_id: TaintSpec(
|
||||
family=family_id,
|
||||
@@ -275,6 +284,8 @@ def taint_specs(
|
||||
safe_shape_sinks=safe_shapes,
|
||||
dispatch_apply=dispatch_apply,
|
||||
dispatch_value=dispatch_value,
|
||||
fixed_origin=family_id in fixed_origin,
|
||||
origin_anchors=tuple(sorted(anchors.get(family_id, ()))),
|
||||
)
|
||||
for family_id, calls in sorted(by_family.items())
|
||||
}
|
||||
|
||||
@@ -60,6 +60,8 @@ def request_params(
|
||||
# `apply_filters` is, which makes a general mechanism into a WordPress feature.
|
||||
"dispatchApply": spec.dispatch_apply,
|
||||
"dispatchValue": spec.dispatch_value,
|
||||
"fixedOrigin": "true" if spec.fixed_origin else "false",
|
||||
"originAnchors": spec.origin_anchors,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user