PHP黑魔法
黑魔法
所谓黑魔法,即指在web开发中,一些看似违反直觉,但能实现特殊功能的技巧或语言特性,而这些特性往往依赖php的弱类型、动态特性或隐式转换机制,就是利用函数的逻辑漏洞来进行攻击。
全局变量
如果存在<?php include('flag.php');,可以用下面的方法得到flag
$GLOBALS
php全局变量$GLOBALS引用全局作用域中可用的全部变量,可利用这个特性看flag:
get_defined_vars()
用法:**var_dump(get_defined_vars())**
intval()函数缺陷
intval() 函数用于将其他类型的数据转化为整型数据。
1 | intval(mixed $value, int $base = 10): int |
$value:需要使用intval()进行转化的数据
$base:指定被转化数据采用的进制(默认为10进制)
字符串解析:
若字符串的首个字符不为数字且不为空格等空白字符,则该字符串转化为零。
若字符串的首个字符不为数字但为空格等空白字符,则尝试读取其余字符,若忽略到数字字符前的所有空白字符,在遇到非数字字符时停止对字符串的读取并将已读取字符串转化为数值。
即把前面空格去掉在解析
若字符串的 首个字符为数字,则尝试读取其余字符,在遇到非数字字符(除符合科学计数法格式的字符 e 或 E外)时停止对字符串的读取并将已读取字符转化为数值。
科学计数法绕过
所以当遇到下面这些情况时就可用科学计数法绕过:
1 | if(intval($num) < 2020 && intval($num + 1) > 2021) |
1 | if($num==4476){ |
虽然科学计数法的数值(字符串)在某些版本(如php7.0.0)无法被**intval()正确解析**,但PHP是默认得到它的,在与数值 1 进行加法运算时,$num 将被 PHP 正确解析。
进制转换绕过
1 | if($num==="4476"){ |
这种情况,就看他转换为八进制或者十六进制来绕过。
1 | 0b?? : 二进制 |
payload:
1 | num=0x117c || num=010574 |
再看下面这种情况:
1 | if($num==4476){ |
这里就是正则匹配过滤了字母,所以不能用十六进制了,可以转换为八进制.
1 | num=010574 |
小数点绕过
1 | if($num==="114514"){ |
像上诉这个就不能使用进制转换了,那么可以使用传值小数,intval()会帮我们转换为整型,以此达到绕过的目的。
payload:
1 | num=114514.114514 |
preg_match()函数缺陷
preg_match()主要用于执行正则匹配,其基本语法:
1 | int preg_match(string $pattern,string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] ) |
而php手册告诉我们,该函数的返回值有三种,分别为:
1 | return 1; //如果匹配到 |
安全的写法是使用 === 运算符对返回值进行比较,手册推荐用效率更快的 strpos 函数替代 preg_match 函数
数组绕过
preg_match只能处理字符串,如果传入数组会返回false,不会进入if语句:
1 |
|
%0a换行符绕过
**.不会匹配换行符(\n,\r)**,如:
1 |
|
%0a不行
而在非多行模式下(即/i模式下),**$会忽略在句尾的%0a**
1 |
|
^和$字符用来匹配字符串的开始和结束,要求我们必须是hello
回溯绕过
具体可以参考p牛的博客:https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html
1 | preg_match('/<\?.*[(`;?>].*/is', $_GET['a']); |
如果我们输入phpinfo();//aaaaa,由于**.*可以匹配任意字符,此时会进入贪婪模式,会将phpinfo();//aaaaa所有字符进行匹配**
PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit,如果回溯次数大于1000000次时返回False:
1 | $a = 'hello world'+'h'*1000000 |
trim()及is_numberic()函数缺陷
trim函数会过滤空格以及\n\r\t\v\x00,但不会过滤\f(%0c)(换行符)
1 |
|
is_numberic用于检测是否是数字或数字字符串,而当数字前面有空格或\n\t\r\f\v等换行符时会被认为是数字
1 |
|
所以**\f能突破这两个函数的限制**,下面例题:
1 |
|
strcmp函数缺陷
1 | int strcmp ( string $str1 , string $str2 ) |
参数 str1第一个字符串。str2第二个字符串。如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。
在php5.3之前,当这个函数接受到了不符合的类型,这个函数将发生错误,显示了报错的警告信息后,将return 0。
ereg(),eregi()函数缺陷
int ereg(string pattern, string originalstring, [array regs])函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回1,否则返回0,且搜索对大小写敏感
ereg()函数存在NULL截断漏洞,当传入的字符串包含%00时,只有**%00前的字符串会传入函数并执行,而后半部分不会传入函数判断。因此可以使用%00截断,连接非法字符串,从而绕过函数**
1 |
|
十六进制数 0x36d即十进制的 877,反转后是778
所以payload:
1 | ?c=a%00778 |
strlen()函数缺陷
strlen()函数用于求字符串的长度,可以用科学计数法来绕过:
1 |
|
payload:
1 | ?num=1e9 |
strpos()函数缺陷
strpos()函数用于查找字符串在另一字符串种第一次出现的位置(区分大小写)。
1 |
|
这里传入abc会打印123,但如果传入一个数组或不传入数据或传入没有abc的值都会打印123,这时因为该函数只解析string类型的字符串,给它个数组就不知到如何解析,就会返回null,null==0,而当不传入数据或传入数据不包含abc时,就会由于找不到值而返回null。
in_array()函数缺陷
in_array()函数用来判断字符串是否存在与数组中,但是在判断的时候,会进行类型强制转换,就会出现数字比较的情况。
1 |
|
字符串变量解析特性
PHP需要将所有参数转换为有效的变量名,因此在解析查询字符串变量时,它会做两件事:
- 删除空白符
- 将某些字符
[+.等等转换为下划线(包括空格)
例如:
| User input | Decoded PHP | variable name |
|---|---|---|
| %20foo_bar%00 | foo_bar | foo_bar |
| foo%20bar%00 | foo bar | foo_bar |
| foo%5bbar | foo[bar | foo_bar |
1 |
|
需要注意的是,当php版本小于8时,GET传参请求的参数名含有非法字符.,会被转为_,但如果参数名前面有[,这个[会被直接转为_,但如果后面有.,这个.就不会被转为_.
如果有一个 WAF 规定某个参数的值必须是数字,不能包含字母时,我们就可以利用这个绕过。
如上面**[RoarCTF 2019]Easy Calc要求我们num变量必须为数字,此时我们可通过?%20num=phinfo()绕过**
参考
https://nivi4.notion.site/PHP-8b2e93df6683422f815651f12d1b97c7


















