Source-sink flow analysis
TL;DR: Pick a dangerous sink (eval, exec, query, deserialize), trace data flow backwards to any user-reachable source, and decide whether the sanitiser in between is sufficient. CodeQL / Semgrep / Joern automate the search.
What it is
Most code-review bugs are taint-flow bugs: tainted data (source) reaches a dangerous API (sink) without an effective sanitiser. Instead of reading every file, you enumerate sinks of interest, then ask “can untrusted input get here?”. This inverts traditional review and scales to millions of LOC.
Preconditions / where it applies
- Source code (or decompiled bytecode) available — Java JARs, PHP, .NET assemblies, JS bundles
- Known sink list for the language/framework — see dangerous-php-sinks, dangerous-java-sinks, dangerous-aspnet-sinks
- A definition of “source” for the target — typically HTTP request fields, message queues, file uploads, external API responses
Technique
- Enumerate sinks. Grep / AST-search the codebase. For PHP
eval|system|exec|passthru|popen|include|require|unserialize|preg_replace.*\/e. For JavaRuntime.exec|ProcessBuilder|ObjectInputStream.readObject|InitialContext.lookup|ScriptEngine. For ASP.NETBinaryFormatter|LosFormatter|XmlSerializer|Process.Start. - Classify sinks by impact (RCE > SQLi > SSRF > path traversal) and by trust required (whether arg must be string-controlled or only partially).
- Backtrack each callsite to its parameters. Follow getters, DI containers, framework binders. Stop when you hit a request object (taint confirmed), a hard-coded literal (dead), or a normaliser that fully encodes the value (safe).
- Use a query language for repeatability:
// CodeQL: PHP eval reachable from $_GET
import php
from EvalExpr e, Variable v
where v.getAnAccess().getEnclosingCallable() = e.getEnclosingCallable()
and v.getName() = "_GET"
select e, "eval reaches $_GET"
1
2
3
4
5
6
7
8
# Semgrep: Java Runtime.exec with non-literal arg
rules:
- id: java-exec-from-request
pattern: Runtime.getRuntime().exec($X)
pattern-not: Runtime.getRuntime().exec("...")
message: exec sink with non-literal arg
languages: [java]
severity: ERROR
- Confirm reachability end-to-end — route mapping, auth filter, role gate. A sink is only a bug if a real HTTP path reaches it.
- Triage sanitisers. Identify whether the encoder is correct for the sink context —
htmlspecialcharsdoes not stop SQLi;addslashesdoes not stopLIKEwildcards;escapeshellargdoes not stop arg injection on Windows.
Detection and defence
- CI-integrate Semgrep/CodeQL queries — block PRs that introduce new sink+source pairs
- Maintain an internal sink catalogue per stack and keep it current as new framework helpers ship
- Prefer parameterised APIs over string concatenation — kills entire bug classes at the sink
- Tag tainted sources at the framework layer (annotate request DTOs) so static analysis can propagate trust automatically
References
- CodeQL — about data flow analysis — official guide
- Semgrep taint mode — sources, sinks, sanitisers
- Joern Code Property Graph — alternative flow-analysis engine
- OWASP Code Review Guide — general methodology
See also: codeql-custom-query-development, semgrep-custom-rule-development, whitebox-to-exploit-methodology