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
- bypass
http-request deny if { path_beg /admin }
直接使用用//admin
进行绕过,但是在测试中发现GET方式仍然不行,最后在其他大佬的wp中看到使用的是HEAD进行发送。最后会出来cookie:
- 传输cookies时候还有个小trick,
http1.1
是允许cookie分段传输的所以
Cookie: KEY=0be40039bcd8286eab237f481641b16e5e3ab442e0bc1135f08c143b22dc1efc;
Cookie: IMPERSONATE=admin
- 但是传入正则后仍然无法getflag
- 这里就和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直接被解析了。
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>
标签中。
- 回到上面题目:
<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 ?>
*/
- 我们要直接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没有被执行跳转呢?
- 难道是因为只要在代码块中出现错误就会跳转到
catch(err)
中,那么我们把代码改成
function foo() {
try{
return x = 'x';
print(s);
}catch(err){
return alert(1);
}}
foo();
- 可以看到这次直接返回了x,并没有执行
catch(err)
内容。可以猜测这个和let
有关。
- 我们再把
let
换成const
呢?可以发现alert
被执行了。那么我们可以知道let
和const
有一个共同的特点那就是暂存死区
,因为let声明不会被提升到当前执行上下文的顶部。该变量属于从块开始到初始化处理的“暂存死区”。也就是说第一个return
到let
之间都是暂存死区。在程序的控制流程在新的作用域进行实例化时,在此作用域中用let、const声明的变量会在该作用域中先创建,但这个时候还没有进行词法绑定,没有进行对声明语句的求值运算,所以是不能访问的,访问会抛出错误。所以let可以简单理解为最先执行,但是没有将其与变量绑定,所以后面return访问时候就抛出了错误,导致了catch被执行。
- 在mozilla就有这么一个例子(直通车),所以导致xss被执行。
exp 综上所述
http://127.0.0.1/xss.php?xss=alert(1);let%20location=1;%0a%3C!--%3Cscript