DASCTF2024 Sanic‘s revenge(复现)

DASCTF2024 Sanic’s revenge(复现)

下载附件,得到不完整的源码,

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  
  
app = Sanic(__name__)  
app.static("/static/", "./static/")  
  
@app.route("/*****secret********")  
async def secret(request):  
    secret='**************************'  
    return text("can you find my route name ???"+secret)  
  
@app.route('/', methods=['GET', 'POST'])  
async def index(request):  
    return html(open('static/index.html').read())  
  
@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')

就是 CISCN 的 sanic 改的,同样是通过原型链污染来查看文件名称。但是这里把 parts 禁掉了也就是无法继续用 CISCN 的污染链了,

不过在 gxngxngxn 师傅的博客最后还发现可以通过把 file_or_directory 污染来实现文件读取。但是不会显示文件名称。先开启列目录功能 payload:

{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": true}

然后

{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.file_or_directory","value": "/"}

效果如下

但是不知道 flag 文件名,没法直接读 flag。那么先读一手环境变量,文件名称“/proc/self/environ”,有时候也可以读取“/proc/1/environ”试试,发现还真有 flag。

一交竟然是个假的,天塌了。看来还是得老实的一步一步做呀,看见源码说删除了一些源码,那么当务之急是要先寻找源码,访问"/app/app. py",显示文件不存在,猜测源码文件应该是改了名字或者换了位置,然后本来想试着自己去找找新链子发现确实是不怎么好找,摆了。

赛后,直接访问"/proc/1/cmdline" (这个可以查看进程为 1 的系统命令参数)

可以看到就是执行文件 start. sh 嘛,该文件在根目录,直接访问得到内容

知道了源码文件名称,下载查看

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/", "D:/yinwenmingtwo/PythonCode/测试/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 下,不过还是需要知道 flag 的文件名称才行。

在之前国赛中,我们跟踪到 DirectoryHandler 类后,只注意了其初始化部分

当时知道 directory 是个对象无法被污染,然后跟进其来源也就是 Path 类中

可以看到通过 _from_parts 处理来获得的对象,

前面调试发现最终目录是由 parts 控制的,但由于 parts 是个 tuble ,其不能被污染。这里可以看到其赋值给_parts 了,是个 list 型,所以最后污染_parts 来实现了根目录文件读取。

因为这里 parts 字符串被过滤了嘛,而且 value 不能是 list 型,所以得另外找污染的了。我先是继续跟进了下 _parse_args 函数,想着看看 parts 是怎么赋的值,但是发现是通过遍历 args,而 args 也是个 tuble 型。所以只能向后找,不过向后又确实没什么好找的了,刚刚看到直接就成对象了。

回到 gxngxngxn 师傅思路,还是在 DirectoryHandler 类中,在其 handle 方法中发现在开启 directory_view 功能后还会对 directory 对象进行处理

return self._index(  
    self.directory / current, path, request.app.debug  
)

跟进看看 _index 函数

没什么就是对路径的一些处理。调试一波

继续看传入的参数

return self._index(  
    self.directory / current, path, request.app.debug  
)

这里有个拼接。directory 我们知道是路径,这个 current 是什么呢。

current = path.strip("/")[len(self.base) :].strip("/") 

经 GDP 一解释大概就是把 path 去掉 / 后,提取出 base 内容,最后剩下的就是 current。调试发现访问 url/static/ current 的值是空,那么我们如果把其构造为 .. 是不是就可以实现目录穿越了呢。其值是由 path 和 base 控制,需要看这两个是否可控

path 是访问的 url 路径,base 我之前试过可直接进行污染。

手动把 current 改为"…"试试

发现成功实现目录穿越。那么现在关键就是怎么污染呢,能使 current.. ,访问 /static/gao…/(需要一个存在的目录)

发现 currentgao..,看了 gxngxngxn 师傅分析,把 base 污染为 static/gao 即可获得 ..

那么具体这道题该怎么用呢,我们要看的是 /app 目的文件。但是 app 目录下有什么目录呢,那不就是/static(后面反应过来和这个没关,只要是存在子目录就行)。想到可以利用污染 file_or_directory 为 /app,访问 /static/static../,实现穿越(这里穿越还是穿越的/static/,也就是到了 /app 目录下,因为 parts 没变嘛,污染 file_or_directory 只是为了访问其子目录 (上面说了必须要要子目录才行))。

发现不行,看了 gxngxngxn 师傅说这在 windows 里面可以,但是在 linux 中就访问 /static/static../ 就必须要有 static.. 目录才行,所以出题人在这里给了 tmp 目录,那么答案也显而易见了。

file_or_directory 设为/tmp,在路由 adminLook 查看/tmp 目录。

可以看到是有 7a4Eku.. 目录的,先进行污染

开启列目录功能

{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": true}

污染 file_or_directory 为/tmp

{"key":"__class__.__init__.__globals__.app.router.name_index.__mp_main__.static.handler.keywords.file_or_directory","value": "/tmp"}

污染 base

{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.base","value": "static/7a4Eku"}

最后访问路由/static/7a4Eku…/就会使得 current 为"…"实现/static 的目录穿越到/app 下。

最后访问/app/45W698WqtsgQT1_flag 得到 flag。


参考:https://www.cnblogs.com/gxngxngxn/p/18290489

  • 18
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值