命令执行漏洞
原理
指应用有时需要调用一些执行系统命令的函数,如PHP中的system、exec、shell_exec、passthru、popen、proc_popen等,Java中可以执行系统命令的函数有Runtime.getRuntime.exec()
,PrpcessBuilder.start()
(第一个是jdk1.5前。第二个是jdk1.5后)。当用户能控制这些函数的参数时,就可以将恶意系统命令拼接到正常命令中,从而造成命令执行攻击,这就是命令执行漏洞。
利用条件
- 应用调用执行系统命令的函数
- 将用户输入作为系统命令的参数拼接到了命令行中
- 没有对用户输入进行过滤或过滤不严
危害
- 继承Web服务程序的权限去执行系统命令或读写文件
- 反弹shell
- 控制整个网站甚至服务器
- 进一步内网渗透
- 等等(都能执行系统命令了,危害肯定超级大了)
常见连接符
符号 | 含义 |
---|---|
| | 前面命令输出结果作为后面命令的输入内容 |
|| | 前面命令执行失败时才执行后面的命令(具有短路效果,左边是true,右边不执行) |
& | 前面命令执行后继续执行后面的命令(无论左边是false还是true,右边都执行) |
&& | 前面命令执行成功时才执行后面的命令(具有短路效果,左边是false,右边不执行) |
函数
php中
在php下,允许命令执行的函数有:
|
|
如果页面中存在这些函数并且对于用户的输入没有做严格的过滤,那么就可能造成远程命令执行漏洞
其他函数
|
|
|
|
waf绕过
1.通配符
|
|
2.连接符
|
|
3.未初始化的bash变量
在bash环境中允许我们使用未初始化的bash变量,如 $a ,$b,$c
未初始化的变量值都是null
|
|
|
|
|
|
Java代码审计
常用方法
Java中可以执行系统命令的函数有:
1.Runtime.getRuntime.exec()
|
|
exec的使用方式有以下六种:
|
|
exec在执行系统命令时,当传入的参数为字符串参数
和数组参数
时,有不同的返回结果:
首先利用exec()方法执行字符串命令,如:Process proc = Rumtime.getRuntime.exec("ping 127.0.0.1");
执行成功。
利用exec()方法执行多个命令或命令中存在>
或|
等特殊字符时:Process proc = Rumtime.getRuntime.exec("ping 127.0.0.1ls");
执行失败。
然后当传入的命令改为数组参数时,发现ping和id两个命令可以正常执行:
|
|
原因分析:
当跟进exec函数,发现调用的是:
|
|
跟入exec方法,发现调用的是这种参数的方法:
|
|
那么为什么传入字符串和传入数组会造成不同的结果呢?
可以看到如下对字符串类型参数进行处理的代码:
StringTokenizer
对传入的字符串命令参数进行处理,然后再调用 exec(String[] cmdarray, String[] envp, File dir)
关键点就在于StringTokenizer
如何进行处理的,跟进去发现:
会用空格和换行符tab符等空白符\t\n\r\f
对传入的字符串进行分割,返回一个cmdarray
数组,其中cmdarray的第一个要素为执行的命令,该处理导致了再调用exec方法执行命令时,传入字符串参数和数组参数时的返回结果不同。
例如当传入参数分别如下:
|
|
当执行字符串参数时,经过StringTokenizer类后拆分为:
|
|
这就改变了原有的执行命令的语义,导致命令不能正常执行;
而当执行数组参数值,发现没有进入StringTokenizer类。
如果exec方法执行的参数是字符串参数,参数中的空格就会经过StringTokenizer
处理,处理完成后会改变原有的语义导致命令无法正常执行,要想执行命令必须绕过StringTokenizer
,即找到代替空格的字符就可以,这里比如:${IFS}
, $IFS$9
等。
但是url中因为RFC规范,只允许包含数字、字母和四个特殊字符-_.~
,不允许出现前面的{}
,所以对{}
进行url编码即可。
Java中,连接符的使用存在一定局限。例如:
|
|
以上代码中使用ping来诊断网络。当黑客输出www.baidu.com&ipconfig
时,拼接处的系统命令为www.baidu.com&ipconfig
,该命令在命令行终端可以执行。但是在Java运行环境下却执行失败。在Java中,www.baidu.com&ipconfig
被当做一个完整字符串而非两条命令,因为该示例代码并不存在命令执行漏洞。
2.ProcessBuilder.start()
|
|
ProcessBuilder.start()执行系统命令时,并没有获得Unix或Linux Shell。因此要使用Unix/Linux管道之类的功能,必须先调用一个shell程序,比如:想要通过Java执行ls;id
这个命令,必须先调用shell程序/bin/sh
才能执行,即:/bin/sh -c "ls;id"
修复
- 开发人员应将现有API用于其语言。如不用
Runtime.exec()
发出mail
的命令,而使用位于java.mail
的可用api - 如果不存在这种api,应进行查找和过滤恶意字符
- 对PHP语言来说,不能完全控制的危险函数最好不要使用