mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
feat(three-stage-scan): add aliased-sink mutation the regex scan misses
This commit is contained in:
@@ -172,7 +172,7 @@ This plan ships the three-stage scan without rebuilding CWE policy or the regex
|
|||||||
- _Requirements: 8.1_
|
- _Requirements: 8.1_
|
||||||
- _Boundary: Benchmark Harness_
|
- _Boundary: Benchmark Harness_
|
||||||
|
|
||||||
- [ ] 7.2 (P) Add a mutation of a known sink that a same-line pattern does not match
|
- [x] 7.2 (P) Add a mutation of a known sink that a same-line pattern does not match
|
||||||
- At least one wrapper or intermediate-variable mutation exists per the evaluation plan.
|
- At least one wrapper or intermediate-variable mutation exists per the evaluation plan.
|
||||||
- Observable completion: stage 1 misses the mutation; the case is listed as expected in a split-sink or mutation manifest.
|
- Observable completion: stage 1 misses the mutation; the case is listed as expected in a split-sink or mutation manifest.
|
||||||
- _Requirements: 8.2_
|
- _Requirements: 8.2_
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
"""Mutation of os.system: the callable is aliased before use so a same-line regex misses it."""
|
||||||
|
import os
|
||||||
|
|
||||||
|
from flask import Flask, request
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
run_cmd = os.system
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/ping")
|
||||||
|
def ping():
|
||||||
|
host = request.args["host"]
|
||||||
|
return run_cmd("ping -c 1 " + host) # CWE-78 mutation: os.system aliased before the call
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
name = "mutation-python"
|
||||||
|
language = "python_web"
|
||||||
|
frameworks = ["flask"]
|
||||||
|
setup = ["pip install flask"]
|
||||||
|
modes = ["quick", "standard"]
|
||||||
|
known_noise = []
|
||||||
|
baselines = []
|
||||||
|
|
||||||
|
[source]
|
||||||
|
type = "local"
|
||||||
|
path = "../fixtures/mutation-python"
|
||||||
|
|
||||||
|
[[expected]]
|
||||||
|
cwe = "CWE-78"
|
||||||
|
class = "command injection"
|
||||||
|
path = "app.py"
|
||||||
|
line = 13
|
||||||
|
function = "ping"
|
||||||
|
sink = "run_cmd"
|
||||||
|
evidence = "aliased os.system runs an attacker-controlled command"
|
||||||
@@ -63,6 +63,7 @@ def test_repository_benchmark_manifests_load_and_point_to_fixtures() -> None:
|
|||||||
"java-web-smoke.toml",
|
"java-web-smoke.toml",
|
||||||
"javascript-node-web-smoke.toml",
|
"javascript-node-web-smoke.toml",
|
||||||
"javascript-vulnerable.toml",
|
"javascript-vulnerable.toml",
|
||||||
|
"mutation-python.toml",
|
||||||
"python-vulnerable.toml",
|
"python-vulnerable.toml",
|
||||||
"python-web-smoke.toml",
|
"python-web-smoke.toml",
|
||||||
"split-sink-c.toml",
|
"split-sink-c.toml",
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
"""Mutation fixtures: stage-1 same-line patterns must miss the aliased sink (requirement 8.2)."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from openultrasast.benchmark import BenchmarkRun, evaluate_benchmark, load_benchmark_manifest, resolve_benchmark_source
|
||||||
|
from openultrasast.findings import StaticFinding, quick_scan_findings
|
||||||
|
from openultrasast.gate import LANGUAGE_MANIFESTS
|
||||||
|
from openultrasast.preprocess import preprocess_repository
|
||||||
|
from openultrasast.rank import rank_targets
|
||||||
|
|
||||||
|
MANIFEST = Path("benchmarks/manifests/mutation-python.toml")
|
||||||
|
|
||||||
|
|
||||||
|
def _quick_scan(target: Path) -> list[StaticFinding]:
|
||||||
|
_, targets = preprocess_repository(target)
|
||||||
|
return quick_scan_findings(target, targets, rank_targets(targets))
|
||||||
|
|
||||||
|
|
||||||
|
def test_stage1_misses_aliased_os_system_mutation() -> None:
|
||||||
|
manifest = load_benchmark_manifest(MANIFEST)
|
||||||
|
target = resolve_benchmark_source(MANIFEST, manifest)
|
||||||
|
planted = [item for item in manifest.expected if item.rule_id is None]
|
||||||
|
assert planted, "mutation manifest must document a stage-1 miss without rule_id"
|
||||||
|
expected = planted[0]
|
||||||
|
assert expected.sink == "run_cmd"
|
||||||
|
assert expected.line is not None
|
||||||
|
|
||||||
|
source = (target / expected.path).read_text()
|
||||||
|
lines = source.splitlines()
|
||||||
|
assert "os.system" in source
|
||||||
|
assert "os.system(" not in lines[expected.line - 1]
|
||||||
|
assert "run_cmd(" in lines[expected.line - 1]
|
||||||
|
|
||||||
|
findings = _quick_scan(target)
|
||||||
|
on_line = [finding for finding in findings if finding.path == expected.path and finding.line == expected.line]
|
||||||
|
assert on_line == [], f"stage-1 hit the mutation on {expected.path}:{expected.line}: {on_line}"
|
||||||
|
|
||||||
|
run = BenchmarkRun(benchmark_run_id="mutation", root=Path("/tmp/mutation"), manifest=manifest)
|
||||||
|
result = evaluate_benchmark(run=run, mode="quick", findings=findings, scan_id=None, scan_run_dir=None)
|
||||||
|
missed = {(miss.path, miss.cwe, miss.rule_id) for miss in result.misses}
|
||||||
|
assert (expected.path, expected.cwe, None) in missed
|
||||||
|
|
||||||
|
|
||||||
|
def test_mutation_fixture_is_outside_stage1_gate_corpus() -> None:
|
||||||
|
listed = {name for names in LANGUAGE_MANIFESTS.values() for name in names}
|
||||||
|
assert "mutation-python" not in listed
|
||||||
Reference in New Issue
Block a user