Java-Sec-Code学习1-命令注入漏洞
Case1
url: http://127.0.0.1:8080/codeinject?filepath=/tmp;cat%20/etc/passwd
这是一个典型的命令注入payload,通过;
符号即可实现命令分隔,实现多命令执行。那么该漏洞背后的对应的代码实现是什么呢?我们来探索一下:
@GetMapping("/codeinject")
public String codeInject(String filepath) throws IOException {
String[] cmdList = new String[]{"sh", "-c", "ls -la " + filepath};
ProcessBuilder builder = new ProcessBuilder(cmdList);
builder.redirectErrorStream(true);
Process process = builder.start();
return WebUtils.convertStreamToString(process.getInputStream());
}
代码非常简短,首先是Spring Boot的路由映射绑定到/codeinject路由。然后,通过接受一个String类型的filepath参数,并未经任何处理和过滤直接拼接进入到cmdList数组中了。这里的cmdList中主要是使用sh -c
来实现命令调用,通过;
分隔命令即可执行命令,也可以通过&
符号来实现其他命令执行。
随后,通过传入cmdList创建一个ProcessBuilder类。这里的ProcessBuilder类是Java1.5引入的,用于创建和管理操作系统进程,是java.lang下的内置类。ProcessBuilder通过start方法就可用创建要给Process进程对象,这里也就导致了cmdList中的恶意语句被系统执行。最后的部分这是通过获取进程的允许结果并转为字符串返回给前端。
Case2
路由绑定在/codeinject/host中
@GetMapping("/codeinject/host")
public String codeInjectHost(HttpServletRequest request) throws IOException {
String host = request.getHeader("host");
logger.info(host);
String[] cmdList = new String[]{"sh", "-c", "curl " + host};
ProcessBuilder builder = new ProcessBuilder(cmdList);
builder.redirectErrorStream(true);
Process process = builder.start();
return WebUtils.convertStreamToString(process.getInputStream());
}
这里的source点发生了变化,不是GET参数,来自HTTP header中的host参数。这里对获取到的host进行了打印,并直接拼接host参数到cmdList数组中。这里的cmdList数组也是使用sh -c
来执行命令。
这里依旧是通过新建ProcessBuilder对象,并调用start方法来实现cmd命令的进程创建。同样返回执行结果的字符串形式。
payload:
通过修改Host头中的内容,使用127.0.0.1&whoami
即可实现命令执行。
Case3
路由绑定在/codeinject/sec,这是一个修复后的案例。
@GetMapping("/codeinject/sec")
public String codeInjectSec(String filepath) throws IOException {
String filterFilePath = SecurityUtil.cmdFilter(filepath);
if (null == filterFilePath) {
return "Bad boy. I got u.";
}
String[] cmdList = new String[]{"sh", "-c", "ls -la " + filterFilePath};
ProcessBuilder builder = new ProcessBuilder(cmdList);
builder.redirectErrorStream(true);
Process process = builder.start();
return WebUtils.convertStreamToString(process.getInputStream());
}
这里直接获取GET参数filepath,但是在拼接到cmdList数组中前,首先通过SecurityUtil.cmdFilter的安全过滤。然后再通过ProcessBuilder对象的start方法来执行。
跟进看一下cmdFilter方法内容。
public static String cmdFilter(String input) {
if (!FILTER_PATTERN.matcher(input).matches()) {
return null;
}
return input;
}
在此处,通过正则匹配的方式匹配传入的内容。如果匹配成功就通过,匹配失败就返回null。
private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/\\.-]+$");
正则表达式如上:只有数字、字母和_/\.-
被允许,这才能匹配成功。
通过正则表达式限制filepath参数内容,以避免通过管道符和换行符,命令分隔符。