Skip to content

Source Code Review for Web Vulnerabilities

6 min read Created Sep 13, 2026 Last updated Sep 13, 2026

Source code review finds vulnerabilities by reading application source rather than testing the running application. White-box access changes the game: instead of guessing inputs and observing behavior, you trace data from user-controlled entry points to dangerous function calls (sinks) and confirm whether any sanitization or validation breaks the chain. This is the primary methodology for OSWE-style assessments where you have full source access. For automated detection of specific vulnerability classes, see command injection detection and SQL injection detection.

Approaches covered on this page:

  • Bottom-up: start from dangerous sinks, trace backwards to find reachable user input
  • Top-down: start from entry points (routes, controllers), follow data forward to sinks
  • Dangerous sinks per language (PHP, Python, Java, .NET, JavaScript)
  • Grep and semgrep patterns for rapid discovery
  • Database query logging to trace injection paths
  • Chaining findings into multi-step exploit chains

Bottom-Up: Sink-First Review

Start by finding dangerous function calls, then trace backwards to determine if user input reaches them without sanitization. This is the fastest approach when you know which vulnerability classes to look for. Grep the codebase for known sinks, then manually trace each result back through the call chain to an entry point.

Advantages: fast discovery, focused scope, works well on large codebases. Disadvantages: misses logic bugs, custom sinks, and vulnerabilities in code paths that do not use known dangerous functions.

Top-Down: Entry-Point-Driven Review

Start from user-facing entry points (HTTP routes, API endpoints, form handlers) and follow data through the application. Map the route definitions first, then read each controller method and trace how request parameters move through models, helpers, and database queries.

In frameworks:

  • Laravel: check routes/web.php, routes/api.php, controller methods
  • Django: check urls.py, view functions, forms.py
  • Spring: check @RequestMapping, @GetMapping, @PostMapping annotations
  • Express: check app.get(), app.post(), router.use()
  • ASP.NET MVC: check controller classes, [HttpGet]/[HttpPost] attributes

Advantages: finds logic bugs, catches custom sinks, provides complete understanding of data flow. Disadvantages: slow on large codebases, requires understanding the framework.

Dangerous Sinks by Language

Grep for these functions as starting points for bottom-up review. Each sink can lead to a specific vulnerability class when reached by user input.

PHP

# Command Injection
system, exec, passthru, shell_exec, popen, proc_open, pcntl_exec
backtick operator: `$cmd`

# Code Execution
eval, assert, preg_replace (with /e modifier), create_function
call_user_func, call_user_func_array, array_map, usort (with callbacks)

# SQL Injection
mysqli_query, pg_query, PDO::query (with concatenated input)
$pdo->prepare() is safe ONLY if placeholders are used

# File Operations
file_get_contents, file_put_contents, fopen, readfile, include, require
include_once, require_once, fwrite

# Deserialization
unserialize, yaml_parse (with unsafe callbacks)

# SSRF
curl_exec, file_get_contents (with URL), fopen (with URL wrappers)

# Weak Crypto / Tokens
mt_rand, rand, uniqid, md5(time()), sha1(microtime())

Python

# Command Injection
os.system, os.popen, subprocess.call, subprocess.Popen, subprocess.run
subprocess.check_output, commands.getoutput

# Code Execution
eval, exec, compile, __import__

# SQL Injection
cursor.execute (with string formatting or concatenation)
raw SQL in Django: RawSQL, .raw(), .extra()

# Deserialization
pickle.loads, pickle.load, yaml.load (without SafeLoader)
yaml.unsafe_load, marshal.loads, shelve.open

# SSTI
render_template_string (Jinja2 with user input in template)
Template(user_input) in Django/Jinja2

# File Operations
open, os.path.join (without path validation), shutil.copy
send_file, send_from_directory (Flask)

# SSRF
requests.get, urllib.request.urlopen, http.client.HTTPConnection

Java

# Command Injection
Runtime.getRuntime().exec, ProcessBuilder, Process

# SQL Injection
Statement.execute, Statement.executeQuery (with concatenation)
PreparedStatement is safe ONLY when parameterized
Hibernate: createQuery with HQL concatenation, createSQLQuery

# Deserialization
ObjectInputStream.readObject, XMLDecoder.readObject
XStream.fromXML, SnakeYAML yaml.load
JSON: ObjectMapper with enableDefaultTyping (Jackson)

# XXE
DocumentBuilderFactory (without disabling external entities)
SAXParserFactory, XMLInputFactory, TransformerFactory

# SSRF
URL.openConnection, HttpURLConnection, HttpClient
RestTemplate (Spring), WebClient (Spring WebFlux)

# File Operations
FileInputStream, FileOutputStream, new File() with user input
Paths.get (without normalization check)

# SSTI
VelocityEngine.evaluate, FreemarkerTemplate with user input
PebbleEngine with user input

.NET (C#)

# Command Injection
Process.Start, ProcessStartInfo

# SQL Injection
SqlCommand with concatenated CommandText
EntityFramework: FromSqlRaw, ExecuteSqlRaw (with string interpolation)
SqlDataAdapter with concatenated queries

# Deserialization
BinaryFormatter.Deserialize, SoapFormatter.Deserialize
ObjectStateFormatter.Deserialize (ViewState)
JsonConvert.DeserializeObject with TypeNameHandling != None
XmlSerializer (with user-controlled type)
DataContractSerializer, NetDataContractSerializer

# XXE
XmlDocument.Load, XmlReader.Create (without DtdProcessing.Prohibit)
XDocument.Load, XmlTextReader

# File Operations
File.ReadAllText, File.WriteAllText, FileStream
Path.Combine (without path traversal validation)

# SSRF
HttpClient, WebClient, HttpWebRequest, WebRequest.Create

JavaScript / Node.js

# Command Injection
child_process.exec, child_process.execSync, child_process.spawn
child_process.execFile (safe if no shell: true)

# Code Execution
eval, Function constructor, vm.runInNewContext, vm.runInThisContext
setTimeout/setInterval with string argument

# Prototype Pollution
Object.assign, lodash.merge, lodash.set, lodash.defaultsDeep
JSON.parse followed by recursive merge

# SQL Injection
sequelize.query (with raw concatenation)
knex.raw, pg client.query (with concatenation)

# SSTI
ejs.render, pug.render, handlebars.compile (with user input)
nunjucks.renderString

# File Operations
fs.readFile, fs.writeFile, fs.createReadStream
path.join (without path traversal check), res.sendFile

# SSRF
axios, node-fetch, http.request, https.request, got

Grep Patterns for Rapid Discovery

Run these against the application source to quickly identify potential vulnerability locations. Start with the most critical classes (command injection, deserialization, SQLi) and work down.

┌──(kali㉿kali)-[~]
└─$ grep -rn --include='*.php' -E '(system|exec|passthru|shell_exec|popen|proc_open|pcntl_exec)\s*\(' .
┌──(kali㉿kali)-[~]
└─$ grep -rn --include='*.php' -E '(->query|->exec|mysqli_query|pg_query)\s*\(' . | grep -v 'prepare'
┌──(kali㉿kali)-[~]
└─$ grep -rn --include='*.php' -E '(unserialize|yaml_parse)\s*\(' .
┌──(kali㉿kali)-[~]
└─$ grep -rn --include='*.py' -E '(os\.system|os\.popen|subprocess\.(call|run|Popen|check_output)|eval|exec|pickle\.(loads|load)|yaml\.load)\s*\(' .
┌──(kali㉿kali)-[~]
└─$ grep -rn --include='*.java' -E '(Runtime\.getRuntime\(\)\.exec|ProcessBuilder|ObjectInputStream|XMLDecoder|readObject)' .
┌──(kali㉿kali)-[~]
└─$ grep -rn --include='*.cs' -E '(BinaryFormatter|ObjectStateFormatter|Process\.Start|TypeNameHandling|FromSqlRaw)' .
┌──(kali㉿kali)-[~]
└─$ grep -rn --include='*.js' --include='*.ts' -E '(child_process|exec|execSync|spawn|eval|Function\()' . | grep -v node_modules

Using Semgrep

Semgrep provides language-aware pattern matching that understands AST structure, avoiding false positives from comments and strings. Install and run against common vulnerability patterns:

┌──(kali㉿kali)-[~]
└─$ semgrep --config=p/owasp-top-ten .
┌──(kali㉿kali)-[~]
└─$ semgrep --config=p/security-audit .

The p/owasp-top-ten ruleset covers SQLi, XSS, command injection, deserialization, and XXE across multiple languages. The p/security-audit ruleset adds crypto weaknesses, hardcoded secrets, and insecure configuration patterns. Custom rules can target application-specific patterns.

Database Query Logging

Enable query logging on the application's database to see the exact SQL being executed. This reveals injection points that are hard to trace through code alone, especially when the application uses an ORM that builds queries dynamically.

MySQL

┌──(kali㉿kali)-[~]
└─$ mysql -u root -p -e "SET GLOBAL general_log = 'ON'; SET GLOBAL general_log_file = '/tmp/mysql_queries.log';"
┌──(kali㉿kali)-[~]
└─$ tail -f /tmp/mysql_queries.log | grep -i 'select\|insert\|update\|delete'

PostgreSQL

┌──(kali㉿kali)-[~]
└─$ psql -U postgres -c "ALTER SYSTEM SET log_statement = 'all'; SELECT pg_reload_conf();"
┌──(kali㉿kali)-[~]
└─$ tail -f /var/log/postgresql/postgresql-14-main.log

Browse the application while tailing the log. When you submit a form or interact with a feature, check whether your input appears literally in the SQL query. If it does, the query is concatenated, not parameterized, and SQL injection is likely possible. If your input appears as a parameter placeholder (? or $1), the query is parameterized and that specific path is safe.

Tracing User Input to Sink

Once you find a dangerous sink, trace backwards to determine if user input reaches it. Follow these steps:

  1. Identify the function containing the sink
  2. Check all parameters of that function: are any derived from request data?
  3. Trace each parameter back through the call chain: does it pass through any sanitization, validation, or type casting?
  4. Check for framework-level protections: CSRF tokens, input filtering middleware, WAF rules
  5. Determine if the sanitization is complete: does it handle all bypass techniques for that vulnerability class?

Common sanitization gaps that lead to exploitable findings:

  • Blacklist filtering instead of whitelist: missed characters, encoding bypasses
  • Sanitization applied to one parameter but not another in the same function
  • Type checking in the controller but not in the model method (direct model access bypasses it)
  • Regex validation with anchoring issues (see regex restriction bypass)
  • Escaping for the wrong context: HTML encoding does not prevent SQLi

Building Exploit Chains

Single vulnerabilities from code review often chain into full compromise. Common patterns:

  • Auth bypass + file upload + RCE: SQL injection in login form gives admin access, admin panel has file upload, upload filter bypass gives web shell
  • SSRF + file read + deserialization: SSRF reads internal configuration, config contains secret key, forged session cookie triggers deserialization RCE
  • Info disclosure + type juggling + privilege escalation: source code leak reveals password comparison logic, type juggling bypasses == check, access to admin functionality
  • LFI + log poisoning + RCE: LFI reads log files, inject PHP into User-Agent header, include the log file for code execution

Document each link in the chain during review. A vulnerability that seems low-impact alone may be critical when combined with another finding.

References