在Java中的Runtime.getRuntime本质就是使用ProcessBuilder,以ProcessBuilder里用ProcessImpl,start 的一个子进程执行命令,
Java的native调用
a. Windows是CreateProcessW 创建子进程执行命令
b. Unix中以enecve 来创建子进程执行命令
Java并没有使用system函数进行创建子进程执行命令,通常我们也知道system的函数非常容易发生注入风险,比如system(“ls “+”test;rm *”); 后面部分是用户输入的,用户输入通过注入符号;和后续指令rm *,被系统执行。
execve, CreateProcessW 函数通过执行命令和参数分别传递的方式,这种方式杜绝了system的拼接命令的方式,有一定的安全性,但也未必是安全的。
如果传入的参数对原执行命令来说是用于执行的情况下,enecve, createprocessW无法防护
1. Windows下的
cmd.exe /K 参数可以批量执行命令
String btype = request.getParameter("inputparam"); String cmds[] = {"cmd.exe","/K","\"C: &&del C:\\r2.txt”+btype+” &&del C:\\r1.txt \""}; System.Runtime.getRuntime().exec(cmds);
虽然此时传入的参数是用于cmd.exe的,但因为cmd.exe 使用了/K参数,将后续传入的参数用于命令执行,如果传入&&del *,最后变成
cmd.exe /K “del C:\\test1.txt && del *&&del C:\\test2.txt”
2. Unix 下的
String btype = request.getParameter("test"); String cmds[] = {"/bin/bash ","test.sh"+btype}; System.Runtime.getRuntime().exec(cmds);传入的参数作为bash直接执行的,此时如果作为参数传入;rm *,最后等效于
/bin/bash test.sh;rm *
String btype = request.getParameter("inputparam"); String cmds[] = {"/bin/bash ","-c","sh script.sh"+btype}; System.Runtime.getRuntime().exec(cmds);
此时传入的参数是用于bash执行脚本文件的,但因为使用了-c参数,使后续传入的参数用于命令执行,如果传入;rm *,最后等效于
/bin/bash –c “sh script.sh; rm *”
上述的例子提示只要在实际应用过程中,传入的参数(例如上例的sh script.sh;)是用于运行程序内部(例如sh -c,cmd.exe/k)执行命令的,execve,CreateProcessW函数就无法防护,此时用法等效于system(“sh script.sh;rm *”)函数
对用于常见的用于shell执行的符号进行转码,避免命令注入的风险
字符 |
功能描述 |
Unix |
Windows |
| |
管道:连接上个指令的标准输出,作为下个指令的标准输入 |
\| |
^| |
; |
连续指令服务 |
\; |
^; |
& |
后台运行 |
\& |
^& |
$ |
变量替换 |
\$ |
^$ |
> |
重定向输入 |
\> |
^> |
< |
目标文件内容发送到命令中 |
\< |
^< |
` |
返回当前执行结果 |
\` |
^` |
\ |
作为连接符号用,或者转义用 |
\\ |
^\ |
! |
执行上一条shell命令 |
\! |
^! |
例如:Unix 中
可以通过编码方式,避免命令的注入
/bin/bash script.sh\;rm *
/bin/bash –c “sh script.sh\; rm *”
比如: >> 需要转义成\>\>
编码是指在调用函数之前需要对参数进行编码,不需要多次编码。
比如说在某个script.sh test.sh\;rm *里面继续还有命令调用,而这个命令是用参数传入
sh $1
上述的解决方案并不是对test.sh;rm * 做两次编码,对script.sh来说传入参数test.sh;rm *只需要做一次编码,而在script.sh里需要对输入的参数$1在进行一次编码,遵循谁调用谁编码的原则。
如果命令的参数是有特征性的建议使用白名单对输入的参数进行保护
比如允许[a-z][A-Z][0-9] _- 等有限的字符
a. |;&$><`\! 可以将这些字符直接作为黑名单过滤
b. \t\n\r\f \u0000 这些字符需要作为黑名单过滤,特别是空字符截断 \u0000 (这个在JVM6里是没有保护)