源码点这:bowu678/php_bugs: PHP代码审计分段讲解 (github.com)
掌握本题应具备的能力/技巧:php基础知识、对php特定函数的理解、对URL编码与ASCII码的理解与使用。(python基本语法的掌握、python Requests库的基础使用)【可选用】
甜点部分:
ini_set("display_error", false); #关闭错误报告
error_reporting(0); //关闭所有PHP错误报告
foreach([$_GET, $_POST] as $global_var) {
foreach($global_var as $key => $value) {
$value = trim($value); //trim — 去除字符串首尾处的空白字符(或者其他字符)
is_string($value) && $req[$key] = addslashes($value); // is_string — 检测变量是否是字符串,addslashes — 使用反斜线引用字符串
}
}
#req数组里 有一系列键值对,键与值是通过GET和POST接收的,例如url为xxx.php?a=2
则$_GET["a"]=2 在req数组也有$req["a"]=2,req数组的值用addslashes做过防注入,且用trim函数去除过前后两端的空格。
正餐部分:
第一个函数:is_palindrome_number($number) #palindrome:回文
用strval($number)函数获取$number的变量值,然后将这个字符串两端向中间逐渐比较,用这种方式判断,传入参数$number是否为回文字符串。
整体通读可以得知,想要转到Flag里,就要满足以下四个条件:
①:is_numeric($_REQUEST['number']) ==0
is_numeric函数必须判断REQUEST中number的值不为数字。
②:$req['number'] == strval(intval($req['number']))
req数组的number经过strval和intval这两个函数筛选后,必须不变。
③:$value1 == $value2
$value1 = intval($req["number"])
$value2 =intval(strrev ($req["number"]) )
$req数组中number的值,经过intval函数处理后得到的值, 必须等于经过strrev函数与intval函数双重处理后的值。
④:is_palindrome_number( $req["number"] ) == 0
$req数组中number的值传入函数i_p_n中,必须返回0,也就是说,回文函数应该判断number不是回文数。
由上可得,传入参数number既要是回文数,又得满足一系列条件。
number=11 就不能够满足条件①。
从①开始,可以发现,只有①的number是从REQUEST数组里面选的,而②③④的number都是$req数组里的。而$req数组里的number是经过"处理"的(甜点部分)。
所以可用%00这个空字符绕过①,number=%0011,满足条件①不为数字,$REQUEST数组接收number后,会自动去除空字符%00,所以REQUEST["number"]=11 不变。
number=%0011 现在不满足最重要的条件④;
条件④的回文判断是用逻辑写的,而条件②③的回文判断使用函数intval,strrev,strval这三个函数写的,也就是说,添加一个字符,让它绕过条件②③这三个函数,就能满足条件④。
这个符号可以是%0c(换页符 \t ) %2B(+)
最终结果为number=%00%0c11 或 number=%00%2B11
%2B能够从逻辑上被盘出来;因为intval()对+号的处理比较特殊:
+123 会变成123 123+ 会变成 123 1+23 会变成 1 12+3 会变成 12
这样将+号放数字前面就能绕过条件②③了。
下面介绍Fuzzing(模糊测试)思路。
%00{?}11 ?为ASCII字符,要找到一个ASCII字符让它满足上面的②③④条件。
#基本ASCII码128位,再加扩展ASCII码128位,共256位 所以是%00 到%255 但是这里需要用16进制 就是%00~%FF
#hex(1)=0x1
#理解这段代码着重python的“字符串切片”以及“格式化输出”
import requests
for i in range(256):
i=str(hex(i))
if len(i)==3: #0x1 把x去了
i=i[0:1:]+i[2::]
else: #0x10 把0x去了
i=i[2::]
i="%"+i
#print(i)
number=f"%00{i}11"
re = requests.get(f"http://127.0.0.1/php_bugs/02%20%E7%BB%95%E8%BF%87%E8%BF%87%E6%BB%A4%E7%9A%84%E7%A9%BA%E7%99%BD%E5%AD%97%E7%AC%A6.php?number={number}")
#print(re.text)
if "x" in re.text:
print(i)