PHP代码审计之PHP危险函数总结
作者:互联网
代码执行相关函数
eval()
#传入的参数必须为PHP代码,需要以分号结尾。
#执行多行代码
eval(phpinfo());
eval($_POST[‘cmd’]);
assert()
#执行单行代码
与eval类似,字符串被 assert() 当做 PHP 代码来执行
assert($_POST[‘CMD]);
preg_replace
mixed preg_replace ( mixed pattern, mixed replacement, mixed subject [, int limit])
搜索subject中匹配pattern的部分, 以replacement进行替换。
preg_replace()函数原本是执行一个正则表达式的搜索和替换,但因为存在危险的/e修饰符,使 preg_replace() 将 replacement 参数当作 PHP 代码。(提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在包含 preg_replace() 的行中出现语法解析错误)
create_funcion()
create_function主要用来创建匿名函数,如果没有严格对参数传递进行过滤,攻击者可以构造特殊字符串传递给create_function()执行任意命令。
string create_function(string $args , string $code )
$args 变量部分
$code 方法代码部分(要执行的代码)
array_map()
array_map()函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组。 回调函数接受的参数数目应该和传递给array_map()函数的数组数目一致。
array_map( callable $callback, array $array1[, array $...] ) : array
array_map():返回数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。
call_user_func()
call_user_func 把第一个参数作为回调函数调用,其余参数是回调函数的参数。
call_user_func_array()
call_user_func_array把第一个参数作为回调函数(callback)调用,并把一个数组参数作为回调函数的参数
array_filter()
array_filter( array $array[, callable $callback[, int $flag = 0]] ) : array
依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。
usort uasort
usort() 通过用户自定义的比较函数对数组进行排序。
uasort() 使用用户自定义的比较函数对数组中的值进行排序并保持索引关联 。
php>=5.6:
变长参数是PHP5.6新引入的特性,在PHP中可以使用 func(...$arr)这样的方式,将$arr数组展开成多个参数,传入func函数。
php版本随意:
文件操作函数
file_put_contents() 函数把一个字符串写入文件中。
fputs() 函数写入文件
动态函数
PHP函数直接由字符串拼接
“${@phpinfo()}”
单引号不可以
(花括号中语句前要加个@ ,不然不行)(@ 空格 tab 注释符 + -等符号都可以,就是不能直接写代码)
命令执行相关函数
exec()
function exec(string $command,array[optional] $output,int[optional] $return_value)
php代码:
知识点:
exec 执行系统外部命令时不会输出结果,而是返回结果的最后一行,如果你想得到结果你可以使用第二个参数,让其输出到指定的数组,此数组一个记录代表输出的一行,即如果输出结果有20行,则这个数组就有20条记录,所以如果你需要反复输出调用不同系统外部命令的结果,你最好在输出每一条系统外部命令结果时清空这个数组,以防混乱。第三个参数用来取得命令执行的状态码,通常执行成功都是返回0。
passthru()
function passthru(string $command,int[optional] $return_value)
知识点:
passthru与system的区别,passthru直接将结果输出到浏览器,不需要使用 echo 或 return 来查看结果,不返回任何值,且其可以输出二进制,比如图像数据。
system()
function system(string $command,int[optional] $return_value)
知识点:
system和exec的区别在于system在执行系统外部命令时,直接将结果输出到浏览器,不需要使用 echo 或 return 来查看结果,如果执行命令成功则返回true,否则返回false。第二个参数与exec第三个参数含义一样。
反撇号`和shell_exec()
shell_exec() 函数实际上仅是反撇号 (`) 操作符的变体
代码:
用popen()函数打开进程
上面的方法只能简单地执行命令,却不能与命令交互。但有些时候必须向命令输入一些东西,如在增加Linux的系统用户时,要调用su来把当前用户换到root才行,而su命令必须要在命令行上输入root的密码。这种情况下,用上面提到的方法显然是不行的。
resource popen ( string $command , string $mode )
函数需要两个参数,一个是执行的命令command,另外一个是指针文件的连接模式mode,有r和w代表读和写。
函数不会直接返回执行结果,而是返回一个文件指针,但是命令已经执行。
popen()打开一个指向进程的管道,该进程由派生给定的command命令执行而产生。
返回一个和fopen()所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用pclose()来关闭。
此指针可以用于fgets(),fgetss()和 fwrite()
proc_open()函数
与Popen****函数类似,但是可以提供双向管道
pcntl_exec()函数
path是可执行二进制文件路径或一个在文件第一行指定了一个可执行文件路径标头的脚本
args是一个要传递给程序的参数的字符串数组。
pcntl是linux下的一个扩展,需要额外安装,可以支持 php 的多线程操作。
pcntl_exec函数的作用是在当前进程空间执行指定程序,版本要求:PHP > 4.2.0
SSRF
file_get_contents()
直接用file_get_contents()加载url指向文件
fsockopen()
fsockopen函数会使用socket跟服务器建立tcp连接,传输原始数据
curl_exec()
变量覆盖
$$
以上代码,可以用从G P C获得的 '参数=值',来覆盖之前的所有变量
extratct()
extract函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
该函数返回成功设置的变量数目。
2.语法
extract(array,extract_rules,prefix)
参数描述
array必需。规定要使用的数组。
extract_rules可选。extract函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。对不合法和冲突的键名的处理将根据此参数决定。
可能的值:
EXTR_OVERWRITE ——默认。如果有冲突,则覆盖已有的变量。
parse_str()
1.parse_str()函数介绍
parse_str函数把查询字符串解析到变量中。
注释:如果未设置array参数,由该函数设置的变量将覆盖已存在的同名变量。
parse_str函数的作用就是解析字符串并注册成变量,在注册变量之前不会验证当前变量
是否存在,所以直接覆盖掉已有变量
2.语法:
parse_str(string, array)**"
参数描述
string必需。规定要解析的字符串。
array可选。规定存储变量的数组名称。该参数指示变量存储到数组中。
import_request_variables()
PHP4-5.4.0 才有该函数。
import_request_variables将 GET/POST/Cookie变量导入到全局作用域中;
import_request_variables函数就是把GET、POST、COOKIE的参数注册成变量,用在register. globals被禁止的时候
语法:
bool import_request_variables(string$types[string$prefix] )
$type代表要注册的变量,G代表GET,P代表POST,C代表COOKIE,第二个参数为要注册变量的前缀
XXE
simplexml_load_string
实例: 闪灵s-cms
看到IF语句这边是需要满足两个条件
1、$signature 不等于空
2、$echostr 等于空
通过全局搜索可以知道了 $signature 是一个超全局变量 signature,$echostr也是
我们可以控制simplexml_load_string的内容,所以存在XXE
可以读取任意文件:
PHP黑魔法
in_array
在判断之前自动做类型转换
is_numeric
PHP提供了is_numeric函数,用来变量判断是否为数字。但是函数的范围比较广泛,不仅仅是十进制的数字。任何参数做16进制编码传入,会直接通过(true)
switch
md5()、sha1()
\1. 0e开头的全部相等 (==判断)
240610708和QNKCDZO的md5值类型相似,都是以0e开头,在== 相等操作符的运算下,结果返回了true。 MD5和sha1一样
\2. 利用数组绕过 (===判断)
MD5和sha1对一个数组哈希将返回NULL,而NULL===NULL返回true,所有可绕过判断
filter_var、parse_url
filter_var()
filter_var — 使用特定的过滤器过滤一个变量
最常见的是FILTER_VALIDATE_URL过滤器,用来判断是否是一个合法的url
filter_var可能存在一些绕过:
参考:PHP-Audit-Labs/README.md at master · hongriSec/PHP-Audit-Labs (github.com)
SSRF中可参考文章: https://www.jianshu.com/p/80ce73919edb
XSS中可参考:https://blog.csdn.net/mysteryflower/article/details/94405122
例如:
我们使用 payload :?url=javascript://comment``%250aalert(1)
,可以执行 alert 函数:
实际上,这里的 // 在JavaScript中表示单行注释,所以后面的内容均为注释,那为什么会执行 alert 函数呢?那是因为我们这里用了字符 %0a ,该字符为换行符,所以 alert 语句与注释符 // 就不在同一行,就能执行。当然,这里我们要对 % 百分号编码成 %25 ,因为程序将浏览器发来的payload:javascript://comment``%250aalert(1)
先解码成: javascript://comment%0aalert(1)
存储在变量 $url 中(上图第二行代码),然后用户点击a标签链接就会触发 alert 函数。
strcmp
数组跟字符串比较返回0
查看php手册
int strcmp ( string $str1,string $str2)
return values
return < 0 if str1 is less than str2; >0 if str1 is greater than str2, and 0 if they are equal
当输入的两个值不是字符串时就会产生非预期的返回值
比如
trim
trim 函数会过滤空格以及 \n\r\t\v\0,但不会过滤过滤\f
· " " (ASCII 32 (0x20)),普通空格符。
· "\t" (ASCII 9 (0x09)),制表符。
· "\n" (ASCII 10 (0x0A)),换行符。
· "\r" (ASCII 13 (0x0D)),回车符。
· "\0" (ASCII 0 (0x00)),空字节符。
· "B" \x0 "\v" (ASCII 11 (0x0B)),垂直制表符。
· \f的意思是:换页。将当前位置移到下一页的开头。
preg_match
preg_match 函数用于进行正则表达式匹配,返回 pattern 的匹配次数,它的值将是 0 次(不匹配)或 1 次,因为 preg_match() 在第一次匹配后将会停止搜索。如果在进行正则表达式匹配的时候,没有限制字符串的开始和结束(^ 和 $),则可以存在绕过的问题
pre_match 在匹配的时候会消耗较大的资源,并且默认存在贪婪匹配,如果传入一个超长的字符串,会导致 pre_match 消耗大量资源从而导致 php 超时(一般为30s),后面的 php 语句就不会执行。payload:
就是匹配文件名由字母、数字、下划线、破则号、斜杠、空白字符各种组合的并且后缀名是rpt的文件,如果匹配成功,就执行系统命令file打印文件的类型和编码信息,如果匹配失败就打印’regex failed’.
如果开启了/m,会存在绕过
注意到正则表达式结尾的/m 了,在php中,/m表示开启多行匹配模式,开启多行匹配模式之后^
和$
的含义就发生了变化,没开启多行模式之前(即单行匹配模式), ^
和$
是匹配字符串的开始和结尾,开启多行模式之后,多行模式^
,$
可以匹配每行的开头和结尾,所以上述payload里面含有换行符,被当做两行处理,一行匹配OK即可,所以进入了exec执行分支,进而导致命令执行。
修饰符说明
1 2 3 4 5 6 7 | i 在和正则匹配是不区分大小写 m 将字符串视为多行。默认的正则开始“”和结束“$”将目标字条串作为一单一的一“行”字符(甚至其中包括换行符也是如此)。如果在修饰符中加上“m”,那么开始和结束将会指点字符串的每一行的开头就是“”结束就是“$”。 o 评估表达式只有一次 s 如果设定了这个修正符,那么,被匹配的字符串将视为一行来看,包括换行符,换行符将被视为普通字符串。 x 忽略空白,除非进行转义的不被忽略。 g 在全局范围内找到所有匹配 cg 即使全局匹配失败也允许搜索继续 |
---|---|
preg_match_all
正则表达式全局匹配,成功返回整个模式匹配的次数(可能为零),如果出错返回 FALSE
ereg %00 截断
ereg 读到 %00 的时候,就截止了
这里 a=abcd%001234
,可以绕过
htmlentities
一般仅仅 htmlenetities($query) 这样用,不加第二个参数的情况下,很有可能存在漏洞
htmlentities不加第二个参数的话,不会转义单引号。
案例参考:
PHP-Audit-Labs/README.md at master · hongriSec/PHP-Audit-Labs (github.com)
(由于htmlentities运用错误,导致XSS、SQL注入)
$_REQUEST
① :
超全局数组 $_REQUEST 中的数据,是 $_GET 、 $_POST 、 $_COOKIE 的合集,而且数据是复制过去的,并不是引用。所以对 $_GET 、 $_POST 处理并不会影响 $_REQUEST 中的数据。
可以发现 REQUEST 数据丝毫不受过滤函数的影响
(意思就是GPC的数据经过过滤处理,request不会受到影响)
案例:PHP-Audit-Labs/README.md at master · hongriSec/PHP-Audit-Labs (github.com)
② :
php中 REQUEST 变量默认情况下包含了 GET ,POST 和 COOKIE 的数组。在 php.ini 配置文件中,有一个参数 variables_order ,这参数有以下可选项目
这些字母分别对应的是 E: Environment ,G:Get,P:Post,C:Cookie,S:Server。这些字母的出现顺序,表明了数据的加载顺序。而 php.ini 中这个参数默认的配置是 GPCS ,也就是说如果以 POST 、 GET 方式传入相同的变量,那么用 REQUEST 获取该变量的值将为 POST 该变量的值 所有如果存在类似:$_GET传入的参数,通过$_REQUEST进行过滤,就可能存在问题
案例:PHP-Audit-Labs/README.md at master · hongriSec/PHP-Audit-Labs (github.com)
iconv
Windows FindFirstFile利用
将文件不可知部分之后的字符用 ‘<’ 或者’>’代替,只使用一个’>‘只能代表一个字符,使用’>>’两个可以代替多个字符。
可以用来进行未知文件名的文件包含,和目录爆破。
session反序列化
在php中session有三种序列化的方式,分别是php_serialize, php_binary和php
【在 PHP
中默认使用的是 PHP
引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '``需要设置的引擎``')``】
这里举个例子来了解一下在不同的处理器下,session所储存的格式有什么不一样(测试的时候php版本一定要大于5.5.4,不然session写不进文件)):
比如这里我get进去一个值为shy,查看一下各个存储格式:
这有什么问题,其实PHP中的Session的实现是没有的问题,危害主要是由于程序员的Session使用不当而引起的。如:使用不同处理器来处理session文件。
php引擎的存储格式是键名 | serialized_string,而php_serialize引擎的存储格式是serialized_string。如果程序使用两个引擎来分别处理的话就会出现问题。
先以php_serialize的格式存储,从客户端接收参数并存入session变量
(1.php)
接下来使用php处理器读取session文件
(2.php)
攻击思路:
首先访问1.php,在传入的参数最开始加一个'|',由于1.php是使用php_serialize处理器处理,因此只会把'|'当做一个正常的字符。然后访问2.php,由于用的是php处理器,因此遇到'|'时会将之看做键名与值的分割符,从而造成了歧义,导致其在解析session文件时直接对'|'后的值进行反序列化处理。
这里可能会有一个小疑问,为什么在解析session文件时直接对'|'后的值进行反序列化处理,这也是处理器的功能?这个其实是因为session_start()这个函数,可以看下官方说明:
首先生成一个payload:
攻击思路中说到了因为不同的引擎会对'|',产生歧义,所以在传参时在payload前加个'|',作为a参数,访问1.php,查看一下本地session文件,发现payload已经存入到session文件
访问一下2.php
看看会有什么结果
成功触发了student类的__wakeup()方法。
标签:审计,php,函数,代码,参数,数组,array,PHP 来源: https://www.cnblogs.com/yokan/p/16102621.html