non-rce学习与分析
这是AntCTF x D^3CTF
比赛,蚂蚁安全非攻实验室出题目 [non RCE?]
赛题源码https://github.com/Ant-FG-Lab/non_RCE
学习与分析
检查源码发现可以从AntiUrlAttackFilter
、LoginFilter
、AdminServlet
这三个文件入手
password绕过
要进入/admin/*
页面,一定要有password
参数,LoginFilter
对password
参数进行验证。由下图可知password
是未知的,并不是本地源码的空字符串,所以需要绕过。AntiUrlAttackFilter.java
这个文件是对/*
进行过滤,恰好有forward
操作request.getRequestDispatcher(filteredUrl).forward(request.response)
这个语句意思是将客户端的请求转向(forward
)到getRequestDispatcher()
方法中参数定义的页面或者链接。
为什么forward
之后就不会过滤呢?因为这里在@WebFilter
装饰器的参数中有个叫dispatcherTypes
的参数,默认存在DispatcherType.REQUEST
参数,而他还有DispatcherType.FORWARD
、DispatcherType.INCLUDE
、DispatcherType.ASYNC
、DispatcherType.ERROR
这4个参数,如果设置了dispatcherTypes
所对应的参数,则会进行filter
过滤,反之没有设置则不会再被filter
进行过滤
因为此次为默认,只会过滤REQUEST
请求,不会过滤FORWARD
,则造成了绕过
如果设置为如下方式,则仍会进行过滤
1 |
因此http://localhost:8080/;admin/importData?password=123
或http://localhost:8080/admin.//importData?password=123
都可以进行绕过。
条件竞争
看一下AdminServlet,.java
文件,发现进入/admin/importData
页面要有databaseType
和jdbcUrl
参数,首先这两个值不能为空,然后会对jdbcUrl进行黑名单检查,检查通过后,当databaseType
值为mysql
时,会进行数据库连接。这个时候猜测可以使用MySQL JDBC
反序列化。
发现黑名单禁止jdbcUrl
包含%
, autoDeserialize
这两个字符串,autoDeserialize
就不解释了,%
是防止对autoDeserialize
进行编码传参
此时需要绕过
看了一下BlackListChecker.java
类的逻辑,发现BlackList
使用的单例工厂模式,即只有一个实例
再看check(String s)
函数操作,取出实例后将传入的字符串放入setToBeChecked(String s)
函数中,因为只有一个实例,所以每次请求都会刷新this.toBeChecked
的值,意味着只要在被拦截的poc
执行doCheck()
之前将不被拦截的poc
放入setToBeChecked(String s)
中重新对this.toBeChecked
赋值,则可绕过。
其实代码中public volatile String toBeChecked;
将toBeChecked
变量设为volatile
也很明显的表示此处可以使用条件竞争,同时也增大了成功的机会,毕竟volatile
用于标记一个变量“应当存储在主存”。更确切地说,每次写入一个volatile
变量,应该写到主存中,而不是仅仅写到CPU
缓存;每次读取volatile
变量,都应该从主存读取,而不是从CPU
缓存读取。
MySQL JDBC反序列化
这部分之前分析过,MySQL JDBC反序列化
AspectJWeaver反序列化Gadget构造
AspectJWeaver反序列化之前也介绍过
目标文件没有commons-collections
依赖,所以需要重新构造gadget
。
项目中存在DataMap.java
文件,里面的方法比较全,可以使用DataMap
类替代commons-collections
中的类。
关键代码如下:
1 | HashMap wrapperMap = new HashMap(); |
测试一下:
修改完代码后将ysoserial
打成jar
包,修改MySQL_Fake_Server
的配置
- 修改
ysoserial jar
包的位置 - 增加
"AspectJWeaver1":["AspectJWeaver1","aaa.txt;YWhpaGloaQ=="]
命令行测试
向本地写入文件
1 | import requests |
启动MySQL_Fake_Server的server.py
后启动py
脚本,写入成功
RCE
写入文件之后怎么执行是一个问题。
首先说一下,通过代码运行的话,我是测试成功了,因为要写的路径清楚而去也能写进去;但直接运行jar
包的话,是写不进去class
的
这里有两种方法:
- 反序列化执行命令
interceptor
加载
反序列化执行命令先通过AspectJWeaver
向服务器上写入恶意的类,该类中重新实现一个readObject
方法,再通过MySQL
的反序列化加载该类,执行readObject
实现RCE
。
但是本地测试的时候class
文件可以写入,但是第二次执行MySQL
反序列化漏洞时服务端报找不到写入的类文件。
注意,当使用jar
包运行时,是无法通过上述流程写进一个class
文件并执行的(java-agent
应该是可以的)
第二种方法测试成功。
首先编写antInterceptor
类
1 | package servlet; |
然后编译成class
文件后转成base64
转换脚本为
1 | import base64 |
转换后修改ysoserial
文件
然后发送数据,这里注意,base64
中有特殊字符,需要进行URL
编码,我这里使用脚本发的数据,所以转换一次就行,在浏览器上发数据需要转换两次
1 | + |
写入文件成功
此时只需要将jdbcUrl
修改成jdbc:mysql://127.0.0.1:3309/mysql?autoDeserialize=true&statementInterceptors=servlet.antInterceptor
,直接发送就能rce
总结
通过这道题学习、复习了很多,感叹于师傅们的思路与操作。更加觉得实践出真知。
本文用到的文件:https://github.com/altEr1125/altEr1125.github.io/tree/master/file/non-rce%E5%AD%A6%E4%B9%A0%E4%B8%8E%E5%88%86%E6%9E%90
Reference
https://meizjm3i.github.io/2021/03/07/Servlet%E4%B8%AD%E7%9A%84%E6%97%B6%E9%97%B4%E7%AB%9E%E4%BA%89%E4%BB%A5%E5%8F%8AAsjpectJWeaver%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Gadget%E6%9E%84%E9%80%A0-AntCTFxD-3CTF-non-RCE%E9%A2%98%E8%A7%A3/
https://www.cnblogs.com/sijidou/p/14631154.html
https://www.anquanke.com/post/id/256974