Python Secure Code Review Notes for Pentesters
Practical Python secure code review and vulnerability hunting notes for pentesters, AppSec engineers, and security researchers.
Table of Contents
- User Input Sources (Taint Sources)
- Taint Flow Concept
- Dangerous Sinks
- SQL Injection (SQLi)
- Command Injection
- Path Traversal
- SSRF (Server-Side Request Forgery)
- Cross-Site Scripting (XSS)
- SSTI (Server-Side Template Injection)
- Insecure Deserialization
- Authentication & Authorization
- File Upload Vulnerabilities
- Open Redirect
- Weak Cryptography
- XXE (XML External Entity)
- Dangerous Python APIs
- Flask / Django Specific Checks
- API Security Checks
- Business Logic Checks
- Race Condition Checks
- Logging & Information Disclosure
- Useful Search Keywords
- Pentester Mindset
- Real Attack Chain Example
- Quick Manual Review Flow
1. User Input Sources (Taint Sources)
These are attacker-controlled inputs entering the application.
Common Sources
| Source | Example | Risk |
|---|---|---|
| HTTP Parameters | request.args.get() | User input |
| POST Data | request.form.get() | Injection |
| JSON Body | request.json | API abuse |
| Headers | request.headers.get() | Header manipulation |
| Cookies | request.cookies.get() | Session tampering |
| File Uploads | request.files | Malicious file upload |
| URL Path | /user/<id> | IDOR |
| Environment Variables | os.getenv() | Secret leakage |
| Command Line Args | sys.argv | CLI injection |
| YAML/XML Input | yaml.load() | RCE |
| Pickle Data | pickle.loads() | Deserialization RCE |
Example
user = request.args.get("user")
User controls user.
2. Taint Flow Concept
User Input
↓
Application Logic
↓
Dangerous Sink
↓
Vulnerability
Example
cmd = request.args.get("host")
os.system("ping " + cmd)
Flow:
request.args.get()
↓
String concatenation
↓
os.system()
↓
Command Injection
3. Dangerous Sinks
| Vulnerability | Dangerous Sink |
|---|---|
| SQL Injection | cursor.execute() |
| Command Injection | os.system() |
| RCE | eval() |
| XSS | render_template() |
| SSRF | requests.get() |
| Path Traversal | open() |
| Deserialization | pickle.loads() |
| SSTI | render_template_string() |
4. SQL Injection (SQLi)
Vulnerable Code
user_id = request.args.get("id")
query = "SELECT * FROM users WHERE id=" + user_id
cursor.execute(query)
Attack Payload
1 OR 1=1
Why Vulnerable?
User input modifies SQL query structure.
Secure Code
cursor.execute(
"SELECT * FROM users WHERE id=%s",
(user_id,)
)
Things to Check
- String concatenation
- f-strings in SQL queries
%formatting.format()usage- Raw SQL execution
- ORM raw queries
Dangerous Patterns
f"SELECT * FROM users WHERE id={id}"
"SELECT * FROM users" + user_input
Search Keywords
execute(
raw(
text(
5. Command Injection
Vulnerable Code
ip = request.args.get("ip")
os.system("ping " + ip)
Attack Payload
127.0.0.1 && whoami
Dangerous APIs
| API | Risk |
|---|---|
os.system() | Command Injection |
subprocess.Popen() | RCE |
subprocess.call() | RCE |
os.popen() | RCE |
Dangerous Example
subprocess.Popen(
"ping " + ip,
shell=True
)
shell=True is highly dangerous.
Secure Code
subprocess.run(
["ping", ip],
shell=False
)
Things to Check
shell=True- String concatenation
- User-controlled arguments
- Bash/sh execution
- Environment variable injection
6. Path Traversal
Vulnerable Code
filename = request.args.get("file")
with open("/app/files/" + filename) as f:
data = f.read()
Attack Payload
../../../etc/passwd
Risks
- Sensitive file read
- Config disclosure
- Credential leakage
Secure Code
from pathlib import Path
base = Path("/app/files").resolve()
target = (base / filename).resolve()
if not str(target).startswith(str(base)):
raise Exception("Traversal detected")
Things to Check
../- URL encoding bypass
- Absolute path injection
- Zip Slip
- User upload paths
7. SSRF (Server-Side Request Forgery)
Vulnerable Code
url = request.args.get("url")
requests.get(url)
Attack Payload
http://169.254.169.254/latest/meta-data/
Risks
- Internal service access
- Cloud credential theft
- Port scanning
- Firewall bypass
Dangerous Libraries
| Library | Risk |
|---|---|
requests | SSRF |
urllib | SSRF |
httpx | SSRF |
Things to Check
- Internal IP access
- Redirect following
- Non-HTTP protocols
- DNS rebinding
- User-controlled URLs
8. Cross-Site Scripting (XSS)
Vulnerable Code
return "<h1>" + name + "</h1>"
Attack Payload
<script>alert(1)</script>
Risks
- Session hijacking
- Cookie theft
- Phishing
- DOM manipulation
Dangerous Patterns
Markup(user_input)
render_template_string(user_input)
Things to Check
- Jinja2 rendering
- Disabled autoescape
- HTML reflection
- DOM XSS
- Unsafe markdown rendering
9. SSTI (Server-Side Template Injection)
Vulnerable Code
render_template_string(user_input)
Attack Payload
{{7*7}}
Advanced Payload
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
Risks
- Remote Code Execution
- Secret access
- File read
Things to Check
- Dynamic template rendering
- Jinja2 expression injection
- User-controlled templates
10. Insecure Deserialization
Vulnerable Code
pickle.loads(user_data)
Why Dangerous?
Pickle can execute arbitrary Python code during deserialization.
Risks
- Remote Code Execution
- Full server compromise
Dangerous APIs
| API | Risk |
|---|---|
pickle.loads() | RCE |
yaml.load() | RCE |
marshal.loads() | Unsafe deserialization |
Secure Code
yaml.safe_load(data)
Things to Check
- Base64 pickle blobs
- Untrusted YAML
- Cache/session deserialization
- User-controlled serialized objects
11. Authentication & Authorization
Dangerous Pattern
if request.args.get("admin") == "true":
Things to Check
- JWT signature validation
- Role modification
- Missing authorization
- IDOR
- Session fixation
- Weak password reset logic
Common Issues
| Issue | Example |
|---|---|
| IDOR | /api/user/123 |
| Missing Auth | No token check |
| Trusting Client | Role from request |
| JWT None Algorithm | Signature bypass |
12. File Upload Vulnerabilities
Vulnerable Code
file.save("/uploads/" + file.filename)
Risks
- Web shell upload
- Path traversal
- Malware upload
Bypass Examples
shell.php.jpg
test.jsp;.png
Things to Check
- Extension validation
- MIME type trust
- Public upload directory
- Executable file upload
- Filename sanitization
13. Open Redirect
Vulnerable Code
return redirect(url)
Attack Payload
https://evil.com
Risks
- Phishing
- OAuth token theft
Things to Check
- External redirects
- Missing allowlist
- Header injection
14. Weak Cryptography
Dangerous Patterns
hashlib.md5(data)
random.random()
Risks
- Predictable tokens
- Weak hashing
- Session compromise
Secure Alternatives
| Weak | Secure |
|---|---|
| MD5 | bcrypt |
| SHA1 | SHA256 |
| random | secrets |
Things to Check
- Hardcoded keys
- Static IVs
- ECB mode
- Weak token generation
15. XXE (XML External Entity)
Vulnerable Code
from lxml import etree
etree.parse(xml_data)
Risks
- File read
- SSRF
- Internal access
Things to Check
- XML parsers
- SVG uploads
- SOAP services
- External entities enabled
16. Dangerous Python APIs
| API | Risk |
|---|---|
eval() | Code Execution |
exec() | RCE |
os.system() | Command Injection |
subprocess.Popen() | RCE |
pickle.loads() | Deserialization |
yaml.load() | RCE |
requests.get() | SSRF |
open() | Path Traversal |
render_template_string() | SSTI |
redirect() | Open Redirect |
17. Flask / Django Specific Checks
Flask
Check For
debug=True- Secret key exposure
- SSTI in Jinja2
- Unsafe session handling
Django
Check For
DEBUG=True- CSRF disabled
- Unsafe serializers
- Raw SQL usage
18. API Security Checks
Check For
- Missing authentication
- Broken authorization
- Rate limiting absence
- Mass assignment
- Excessive data exposure
- JWT flaws
- GraphQL introspection
19. Business Logic Checks
Check For
- Coupon abuse
- Race conditions
- Payment bypass
- Negative quantity/value
- OTP reuse
- Workflow bypass
20. Race Condition Checks
Example
Check balance
↓
Deduct money
Can requests execute simultaneously?
Things to Check
- Parallel requests
- Shared resources
- Double spending
- Inventory abuse
21. Logging & Information Disclosure
Check For
- Stack traces
- Debug endpoints
- Secret leakage
- Verbose errors
- Internal IP exposure
22. Useful Search Keywords
Inputs
request.args
request.form
request.json
request.headers
Dangerous Sinks
eval(
exec(
os.system(
subprocess.Popen(
pickle.loads(
yaml.load(
render_template_string(
23. Pentester Mindset
Always ask:
Can attacker control this?
Can validation be bypassed?
Can this become RCE?
Can vulnerabilities chain together?
What is worst-case impact?
24. Real Attack Chain Example
SSRF
↓
Internal Redis Access
↓
Write SSH Key
↓
Remote Shell
25. Quick Manual Review Flow
| Step | Action |
|---|---|
| 1 | Identify user input |
| 2 | Trace data flow |
| 3 | Find dangerous sinks |
| 4 | Check validation |
| 5 | Check authentication/authorization |
| 6 | Assess exploitability |
| 7 | Determine impact |