【Web安全】命令注入与代码注入漏洞解析及安全测试指南
在Web安全领域,注入类漏洞一直是威胁系统安全的“重灾区”。其中,命令注入(Command Injection)和代码注入(Code Injection)因其能直接危害系统权限、破坏数据完整性,成为安全测试人员重点关注的对象。命令注入允许攻击者通过可控输入执行系统终端命令,直接接管服务器;代码注入则能让攻击者注入恶意代码并执行,灵活性更高,危害范围更广。本文将从代码审计角度出发,解析两种漏洞的产生
前言
在Web安全领域,注入类漏洞一直是威胁系统安全的“重灾区”。其中,命令注入(Command Injection)和代码注入(Code Injection)因其能直接危害系统权限、破坏数据完整性,成为安全测试人员重点关注的对象。
命令注入允许攻击者通过可控输入执行系统终端命令,直接接管服务器;代码注入则能让攻击者注入恶意代码并执行,灵活性更高,危害范围更广。本文将从代码审计角度出发,解析两种漏洞的产生原理、关键代码特征、利用限制及检测要点,帮助安全测试人员快速识别并验证此类漏洞,为系统安全防护提供依据。
一、命令注入漏洞解析
命令注入的核心风险在于:程序将用户可控输入直接拼接为系统命令并执行,且未做严格过滤。以下从多语言视角结合代码实例分析其原理。
1. 危险函数识别
不同语言中存在直接执行系统命令的危险函数,是命令注入的高风险点:
- Python:
exec
、eval
、os.system
、os.open
、execfile
等函数可直接执行系统命令或代码,若参数包含用户输入且未过滤,极易引发漏洞。 - Java:
Runtime.getRuntime().exec()
是执行系统命令的核心方法,若其参数由用户直接控制,可能导致任意命令执行。此外还要关注ProcessBuilder
、ProcessImpl
(底层类)、如Apache Commons Exec、Guava
的某些工具类
2. 典型漏洞代码分析(Java)
如下代码直接获取用户输入的cmd
参数并通过Runtime.exec()
执行,存在高危命令注入漏洞:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String cmd = req.getParameter("cmd"); // 用户可控输入,未过滤
Process process = Runtime.getRuntime().exec(cmd); // 直接执行用户输入的命令
InputStream in = process.getInputStream();
// 读取命令执行结果并返回
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int i = -1;
while ((i = in.read(b)) != -1) {
byteArrayOutputStream.write(b, 0, i);
}
PrintWriter out = resp.getWriter();
out.print(new String(byteArrayOutputStream.toByteArray()));
}
风险点:用户可通过cmd
参数传入任意系统命令(如whoami
、ipconfig
),直接获取服务器权限。
3. 命令注入的限制(以Java为例)
Java的Runtime.exec()
对命令参数的处理机制可能限制注入的利用,需特别注意:
-
当
exec
传入字符串类型命令时,会通过StringTokenizer
分割空白字符,将拼接后的内容作为单个命令的参数,而非多条命令。例如,如下代码中用户输入
www.baidu.com&ipconfig
,本意是执行ping www.baidu.com
和ipconfig
两条命令,但实际会被当作ping
的参数(www.baidu.com&ipconfig
),导致注入失败:protected ByteArrayOutputStream ping(String url) throws IOException { // url为用户输入,拼接后作为ping命令的参数 Process process = Runtime.getRuntime().exec("ping "+ url); InputStream in = process.getInputStream(); // 读取结果逻辑... return byteArrayOutputStream; }
可以使用www.baidu.com; ipconfig
的形式突破限制
- 若
exec
传入字符串数组(如exec(new String[]{"ping", url})
),参数分割更严格,注入难度更高。
二、代码注入漏洞解析
代码注入与命令注入类似,但更灵活:攻击者注入的是可执行代码(如Java代码),而非系统命令。其核心是程序将用户输入作为代码执行,Java反射机制是常见触发点。
1. 危险函数识别
# 动态类加载与反射
Class.forName
ClassLoader.loadClass
Method.invoke()
Constructor.newInstance()
# 脚本引擎
javax.script.ScriptEngine
# JNDI 相关方法
InitialContext.lookup()
# 动态编译与执行
JavaCompiler
# Expression Language(EL 表达式)
ExpressionFactory
这里举例 2 种常见常见:
2. 反射机制导致的代码注入
Java反射允许通过类名、方法名动态调用代码,若这些参数可控且未过滤,会引发代码注入:
String ClassName = req.getParameter("ClassName"); // 用户可控类名
String MethodName = req.getParameter("Method"); // 用户可控方法名
String[] Args = new String[]{req.getParameter("Args").toString()}; // 用户可控参数
try {
Class clazz = Class.forName(ClassName); // 加载用户指定的类
Constructor constructor = clazz.getConstructor(String[].class);
Object obj = constructor.newInstance(new Object[]{Args}); // 实例化对象
Method method = clazz.getMethod(MethodName); // 获取用户指定的方法
method.invoke(obj); // 执行方法
} catch (Exception e) {
e.printStackTrace();
}
风险点:攻击者可通过ClassName
传入危险类(如Runtime
),通过MethodName
调用exec
等方法,间接执行系统命令或恶意代码。
3. 第三方组件漏洞示例(Apache Commons Collections)
这里引用《Java代码审计 (入门篇 )》的案例:Apache Commons Collections 3.1版本因反射机制的不当使用,在反序列化场景中存在代码注入漏洞。其核心是InvokerTransformer
类的transform
方法可动态调用任意方法:
public class InvokerTransformer implements Transformer {
private String iMethodName; // 方法名(可控)
private Class[] iParamTypes; // 参数类型(可控)
private Object[] iArgs; // 参数(可控)
public Object transform(Object input) {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes); // 获取指定方法
return method.invoke(input, this.iArgs); // 执行方法
} catch (Exception e) {
// 异常处理...
}
}
}
利用原理:攻击者通过反序列化控制iMethodName
、iParamTypes
、iArgs
,可调用URLClassLoader
加载远程恶意类(如Evil
),进而执行系统命令(如whoami
),甚至构造回显获取执行结果。
三、C/C++ 中的注入场景及代码审计
C/C++ 作为系统级开发的核心语言,在嵌入式/终端系统底层接口开发(如防火墙规则配置、进程管理、设备控制等)中应用广泛。由于其直接与系统内核交互的特性,命令注入漏洞的危害更为直接,可能导致设备被完全控制。
1. C/C++ 中命令注入的危险函数及场景
C/C++ 中执行系统命令的函数主要依赖标准库或系统调用,以下是高风险函数及典型场景:
(1)system
函数(最常见风险点)
system(const char* command)
函数通过调用 shell 执行传入的命令字符串,若字符串包含用户可控输入且未过滤,会直接引发命令注入。
场景示例:
系统防火墙配置接口可能允许用户配置 IP 黑名单,如下代码通过 system
执行 iptables
命令添加规则:
#include <stdlib.h>
#include <string.h>
// 假设user_ip来自用户输入(如应用配置、网络请求参数)
void add_firewall_blacklist(const char* user_ip) {
char cmd[256];
// 直接拼接用户输入到命令中,存在注入风险
sprintf(cmd, "iptables -A INPUT -s %s -j DROP", user_ip);
system(cmd); // 执行拼接后的命令
}
风险利用:
若用户输入 192.168.1.1; rm -rf /
,此时拼接后的命令为:iptables -A INPUT -s 192.168.1.1; rm -rf / -j DROP
shell 会先执行 iptables
命令,再执行 rm -rf /
,导致系统文件被删除。
(2)popen
函数(带输出的命令执行)
popen(const char* command, const char* mode)
用于执行命令并通过管道获取输出,其命令字符串的处理逻辑与 system
一致,同样存在注入风险。
场景示例:
设备网络管理模块可能通过 popen
执行 ifconfig
查看用户指定网卡的信息:
#include <stdio.h>
#include <string.h>
void get_network_info(const char* interface) {
char cmd[128];
// 拼接用户输入的网卡名(如"eth0; whoami")
sprintf(cmd, "ifconfig %s", interface);
FILE* fp = popen(cmd, "r");
// 读取并返回输出...
pclose(fp);
}
风险利用:
用户输入 eth0; cat /etc/passwd
时,命令会变为 ifconfig eth0; cat /etc/passwd
,导致系统用户信息泄露。
(3)exec
系列函数(间接风险)
exec
系列函数(如 execl
、execv
、execlp
等)用于替换当前进程映像执行新命令。若通过字符串拼接构造命令参数,仍可能存在注入风险(尤其当使用 execlp
等依赖 shell 解析的函数时)。
风险示例:
#include <unistd.h>
#include <string.h>
void execute_command(const char* user_input) {
char cmd[128];
// 拼接用户输入作为命令参数
sprintf(cmd, "ping -c 4 %s", user_input);
// execlp会通过shell解析命令字符串,等同于system
execlp("/bin/sh", "sh", "-c", cmd, (char*)NULL);
}
风险点:与 system
类似,用户输入 8.8.8.8 && reboot
会导致设备重启。
2. 通用系统中的注入风险场景
在各类基于 C/C++ 开发的系统中,以下场景易出现命令注入风险:
- 防火墙/网络规则配置:通过系统接口配置
iptables
、nftables
等规则时,用户输入的 IP、端口、协议等参数若直接拼接进命令,可能被注入。 - 设备管理命令:远程调试、进程启停、日志收集等功能(如系统命令封装接口)中,用户输入的设备 ID、进程名等未过滤,可能注入恶意命令。
- 脚本引擎调用:通过 C/C++ 调用 shell 脚本(如
.sh
文件)时,若脚本参数包含用户输入且未处理,会间接导致注入。
3. C/C++ 代码审计要点(针对注入漏洞)
(1)危险函数定位
优先检索代码中是否包含以下函数,重点检查其参数来源:
system
、popen
、pclose
exec
系列(尤其是execlp
、execvp
等依赖 shell 解析的函数)- 自定义命令执行封装函数(如各类系统命令调用接口)
(2)输入处理逻辑审计
- 是否直接拼接用户可控输入:检查命令字符串是否通过
sprintf
、strcat
等函数拼接用户可控变量(如网络输入、配置文件、IPC 消息等)。 - 特殊字符过滤:是否过滤了命令分隔符(
;
、&
、&&
、||
)、命令替换符(`
、$(...)
)、重定向符(>
、<
)等。 - 白名单验证:对用户输入(如 IP、端口、网卡名)是否采用白名单限制(如 IP 格式校验、端口范围限制)。
安全示例(对比修复):
对上述防火墙接口进行修复,通过白名单校验 IP 格式并过滤特殊字符以修复风险:
#include <stdlib.h>
#include <regex.h>
#include <string.h>
// 验证IP格式(简单示例)
int is_valid_ip(const char* ip) {
regex_t regex;
// 匹配IPv4地址的正则(简化版)
const char* pattern = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
if (regcomp(®ex, pattern, REG_EXTENDED) != 0) return 0;
int ret = regexec(®ex, ip, 0, NULL, 0) == 0;
regfree(®ex);
return ret;
}
void add_firewall_blacklist_safe(const char* user_ip) {
// 1. 验证IP合法性(白名单校验)
if (!is_valid_ip(user_ip)) {
// 日志记录+拒绝处理
return;
}
// 2. 进一步过滤特殊字符(防御残余风险)
for (int i = 0; user_ip[i] != '\0'; i++) {
if (strchr(";|&`$<>", user_ip[i]) != NULL) {
// 发现危险字符,拒绝处理
return;
}
}
// 3. 安全拼接命令
char cmd[256];
snprintf(cmd, sizeof(cmd), "iptables -A INPUT -s %s -j DROP", user_ip);
system(cmd);
}
4. 审计工具与辅助手段
- 静态分析:使用 Clang 静态分析器(
scan-build
)或系统开发工具链中的漏洞扫描模块,检测system
等函数的参数是否包含用户输入。 - 动态测试:在测试环境中向可疑接口传入含特殊字符的输入(如
;id
、&&whoami
),观察是否执行恶意命令。 - 依赖检查:检查系统中是否使用了第三方 C/C++ 库(如命令解析库),确认其是否存在命令注入相关历史漏洞。
总结
命令注入和代码注入均为高危注入类漏洞,其核心成因在于“用户可控输入未严格过滤就被当作命令/代码执行”。安全测试人员在检测时需重点关注:
- 危险函数:Python中的
exec
、os.system
,Java中的Runtime.exec()
、Class.forName()
、Method.invoke()
等,检查其参数是否包含用户输入。 - 输入处理逻辑:验证程序是否对用户输入进行了严格过滤(如禁止特殊字符
|
、&
,限制类名/方法名白名单)。 - 组件风险:关注第三方组件(如Apache Commons Collections)的历史漏洞,结合反序列化等场景检测潜在注入点。
命令注入在 C/C++ 系统级开发中危害极大,核心风险源于“用户输入未严格验证即作为命令执行”。审计时需重点关注 system
、popen
等危险函数的参数处理逻辑,结合通用系统接口(如防火墙、设备管理),通过白名单校验、特殊字符过滤、参数化命令调用等方式消除风险,确保用户输入无法突破预期执行范围。
本文是「Web安全」系列内容,点击专栏导航查看全部内容。
更多推荐
所有评论(0)