- 原理
- 示例代码
- 命令注入的局限
- 无法进行命令注入的原因
命令注入是指在某种开发需求中,需要引入对系统本地命令的支持来完成某些特定的功能。当未对可控输入的参数进行严格的过滤时,则有可能发生命令注入。攻击者可以使用命令注入来执行系统终端命令,直接接管服务器的控制权限。
在开发过程中,开发人员可能需要对系统文件进行移动、删除或者执行一些系统命令。Java的Runtime类可以提供调用系统命令的功能。如下代码可根据用户输入的指令执行系统命令。由于cmd参数可控,用户可以在服务器上执行任意系统命令,相当于获得了服务器权限。
示例代码import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Proxy;
@WebServlet(name="Command",urlPatterns = "/Command")
public class Command extends HttpServlet {
@Override
public void init() throws ServletException{
}
@Override
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(byteArrayOutputStream.toString());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
@Override
public void destroy() {
super.destroy();
}
}
命令注入的执行结果如图
系统命令支持使用连接符来执行多条语句,常见的连接符有|、||、&、&&,其含义为
| 符号 | 含义 |
|---|---|
| | | 前面命令输出结果作为后面命令的输入内容 |
| || | 前面命令执行失败时才执行后面的命令 |
| & | 前面命令执行后继续执行后面的命令 |
| && | 前面命令执行成功后才执行后面的命令 |
例如命令ping www.baidu.com&ipconfig的执行效果如图所示,先执行完ping命令,后执行ipconfig命令
对于Java环境中的命令注入,连接符的使用存在一些局限。例如如下代码,利用ping命令来诊断网络。其中url参数为用户可控,当恶意用户输入www.baidu.com&ipconfig时,系统可以成功执行,但是Java运行环境中就不行,因为www.baidu.com&ipconfig被当作一个完整的字符串而非两条命令,因此如下代码不存在命令执行漏洞。
protected ByteArrayOutputStream ping(String url) throws IOException{
Process process = Runtime.getRuntime().exec("ping " + url);
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);
}
return byteArrayOutputStream;
}
无法进行命令注入的原因
Runtime类中exec方法存在如下几种实现,显而易见,要执行的命令可以通过字符串和数组的方式传入。
public Process exec(String command) throws IOException {
return exec(command, null, null);
}
public Process exec(String command, String[] envp) throws IOException {
return exec(command, envp, null);
}
public Process exec(String cmdarray[]) throws IOException {
return exec(cmdarray, null, null);
}
public Process exec(String[] cmdarray, String[] envp, File dir)
throws IOException {
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
}
当传入的参数类型为字符串时,会先经过StringTokenizer的处理,主要是针对空格以及换行符等空白字符进行处理,后续会分割出一个cmdarray数组保存分割后的命令参数,其中cmdarray的第一个元素为所要执行的命令。经过处理后的参数www.baidu.com&ipconfig成为ping命令的参数,因此此时的连接符&并不生效,从而无法注入系统命令。



