Application Security
Secure Code Review
4 min read
Secure code review is a core AppSec skill tested in most security interviews. This lesson covers the tools, techniques, and vulnerabilities you need to identify.
SAST vs DAST vs SCA
| Tool Type | What It Analyzes | When It Runs | Examples |
|---|---|---|---|
| SAST | Source code | Build time | Semgrep, CodeQL, SonarQube |
| DAST | Running application | Runtime | Burp Suite, OWASP ZAP, Nuclei |
| SCA | Dependencies | Build time | Snyk, Dependabot, Trivy |
| IAST | Running + instrumented | Runtime | Contrast Security |
Interview Question
Q: "What are the limitations of SAST tools?"
Answer:
- False positives: Flagging safe code as vulnerable
- No runtime context: Can't detect configuration issues
- Language-specific: Each language needs different rules
- Business logic blind: Can't understand intended behavior
Code Review Methodology
The STRIDE-per-Element Approach
When reviewing code, systematically check for:
1. INPUT HANDLING
└── Where does external data enter?
└── Is it validated, sanitized, escaped?
2. AUTHENTICATION
└── How are credentials handled?
└── Are sessions managed securely?
3. AUTHORIZATION
└── Are access controls enforced?
└── Is there role/permission checking?
4. DATA PROTECTION
└── Is sensitive data encrypted?
└── Are secrets hardcoded?
5. ERROR HANDLING
└── Do errors leak information?
└── Are failures handled safely?
Spotting Vulnerabilities in Code
SQL Injection
# VULNERABLE
def get_user(username):
query = f"SELECT * FROM users WHERE username = '{username}'"
return db.execute(query)
# SECURE
def get_user(username):
query = "SELECT * FROM users WHERE username = %s"
return db.execute(query, (username,))
Command Injection
# VULNERABLE
import os
def ping_host(host):
os.system(f"ping -c 1 {host}")
# SECURE
import subprocess
def ping_host(host):
# Validate input
if not re.match(r'^[\w.-]+$', host):
raise ValueError("Invalid hostname")
subprocess.run(["ping", "-c", "1", host], check=True)
Path Traversal
# VULNERABLE
def read_file(filename):
with open(f"/uploads/{filename}") as f:
return f.read()
# SECURE
import os
def read_file(filename):
base_dir = "/uploads"
full_path = os.path.normpath(os.path.join(base_dir, filename))
# Ensure path stays within base directory
if not full_path.startswith(base_dir):
raise ValueError("Invalid path")
with open(full_path) as f:
return f.read()
Insecure Deserialization
# VULNERABLE
import pickle
def load_session(data):
return pickle.loads(base64.b64decode(data))
# SECURE
import json
def load_session(data):
return json.loads(base64.b64decode(data))
Interview Exercise: Find the Bugs
// How many vulnerabilities can you spot?
app.get('/user/:id', async (req, res) => {
const userId = req.params.id;
const query = `SELECT * FROM users WHERE id = ${userId}`;
try {
const user = await db.query(query);
res.send(`<h1>Welcome ${user.name}</h1>`);
} catch (err) {
res.send(`Error: ${err.message}`);
}
});
Answer:
- SQL Injection: Direct interpolation of
userId - XSS: Unescaped
user.namein HTML response - Information Disclosure: Full error message exposed
- Missing AuthZ: No check if requester can access this user
Interview Tip: When given code to review, verbalize your thought process. Interviewers want to see HOW you find vulnerabilities, not just that you find them.
Next, we'll cover threat modeling methodologies. :::