2024CISCN
我们/src扫到源码
from sanic import Sanic
from sanic.response import text, html
from sanic_session import Session
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):
return text(open(__file__).read())
@app.route("/admin", methods=['GET', 'POST'])
async def admin(request):
if request.ctx.session.get('admin') == True:
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")
return text("forbidden")
if __name__ == '__main__':
app.run(host='0.0.0.0')
大概扫一下,先看登入框
@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")
这里要cookie传参,这个要传入的值是adm;n
但是我们传入cookie的时候,这里会被截断,所以我们这里用八进制来进行编码绕过
所以我们传入user = "adm/073"
登入成功之后,我们再次对源码审计
上面标注了是5.12版本的,这里有个原型链污染
我们尝试一下文件读取
因为这里会对file进行读取,我们对file进行污染,从而进行文件读取
f key and value and type(key) is str and '_.' not in key:
这里对_.进行了过滤
RE_PATH_KEY_DELIM = re.compile(r"(?<!\)(?:\\)*.|([\d+])")
发现 \. 会当作 .进行处理,可以绕过题目的过滤,而 . 会作为 . 的转义不进行分割
__init__\\\\.__globals__
所以我们可以使用如下payload进行读文件
{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.__file__","value":"/etc/passwd"}
然后我们访问src
成功读取文件
接下来我们就可以读取flag了,但是我们不知道flag的名字
我们通过读取static.py
发现这个是请求了一下DIrectoryHandler,我们跟进一下
发现,这个里面可以看见目录,但是默认是不开启的,也就是false,我们想要开启这个目录功能,就要把它污染为true
但是我们不知道这个路由,所以我们要找到这个路由
sanic框架可以通过app.router.name_index['xxxxx']来获取注册的路由,我们可以打印看看
{'__mp_main__.static': <Route: name=__mp_main__.static path=static/<__file_uri__:path>>, '__mp_main__.src': <Route: name=__mp_main__.src path=src>, '__mp_main__.admin': <Route: name=__mp_main__.admin path=admin>}
我们发现这里打印了所有路由的信息
我们打印一下__mp_main__.static来看一下
<Route: name=__mp_main__.static path=static/<__file_uri__:path>>
和上面显示的一样
我们接下来断点看一下name.index
我们发现了这个route已经到了static,接着我们看下面的
下面的handler有个keywords下面有个directory_handler我们继续看
这个就有这个directory_view属性,这个就可以污染了
结果就是
我们成功得到这个属性了
http://127.0.0.1:8000/src?keke=print(app.router.name_index['__mp_main__.static'].handler.keywords['directory_handler'].directory_view)
接着我们就可以污染了
需要注意一点的是,这里不能用[]来包裹其中的索引,污染和直接调用不同,我们需要用.来连接,
我们这里要进行转义
因为__mp_main__.static是一起的
所以我们的payload就是这个
{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": "True"}
我们试一下看能不能成功
访问static/
成功污染
接下来就是污染到根目录就可以看见flag的名字了
这里是请求的文件信息,我们找到这个
这里是路径,我们是断点查看
因为这个parts是一个tuple类型的,我们不能直接污染
所以我们看一下这个parts具体调用
所以我们直接污染._parts的值就可以了,我们访问这个属性,看看是什么
http://127.0.0.1:8000/src?keke=print(app.router.name_index[%27__mp_main__.static%27].handler.keywords[%27directory_handler%27].directory._parts)
这个是一个list,那么我们就可以污染了因为pydash
只能处理 list
、obj
、dict
而不能处理 tuple
和 set
等对象
{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory._parts","value": ["/"]}
我们访问static/试一下
得到flag名字之后我们就可以读取了
污染完成之后就可以直接/src
参考
https://www.cnblogs.com/gxngxngxn/p/18205235
https://xz.aliyun.com/t/14620?time__1311=GqAhYK0KBK8D%2FD0ltdGQ30Qgu77wbAeD
https://hurrison.com/posts/ciscn2024/
2024DASCTF暑期
这道题是ciscn的改版
和原版不同的是这道题移除了admin的登入,然后根据注释,提供的源码还是不完整的
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')
除了这个之外,还有就是把关键的地方给过滤了
在对路由进行污染的时候我们必须用到parts来污染到根目录才能进行对flag文件的读取,况且这个属性的值是个list
所以我们只能换一种思路进行做题了,看了出题人的博客
这里有一个这个东西
污染之后可以直接访问到文件,我们试着污染一下,
成功污染我们试着在static目录访问一下
得到完整的文件我们访问一下
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')
对比之前的文件,发现这里面多了一个秘密文件,多了一个查看非法日志文件
@app.route("/Wa58a1qEQ59857qQRPPQ")
async def secret(request):
with open("/h111int",'r') as f:
hint=f.read()
return text(hint)
我们访问这个路由看一下提示
他说了flag的目录在/app下,这里我们还要参考一下,CISCN的题,当我们开启了目录功能之后,会发生什么呢,当然也提示了可以在
directory找到线索
在这里,我们发现如果我们开启了目录浏览功能,就会返回这个目录的路径什么的
看一下里面的的index方法
这里面是对路径进行处理
这里的路径就是parts和current进行拼接得到的
我们看一下这个current怎么来
这个base就是path和self.base如果我们访问/static/那么这个self.base就是static
如果说我们把current的值控制为..那么我们就可以访问上级目录了
如果说static下面有一个叫做ctf的文件,那么我们访问这个static/ctf../
那么这个base就是static,current就是ctf..,假如说,我们控制base为static/ctf
那么这个current就是..,最终就访问到static的上级目录,对于linux系统,我们需要一个文件名为xxx..的文件
我们才能访问成功,不然会报错,正好,这个adminLook路由会显示我们的日志情况,有文件名我们可以利用,看有没有符合要求的文件名
我们先输入错误的进去,触发日志情况正好有那个名字是..的文件
接下来我们先用这个file_or_directory
开启一下这个列目录功能
{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": True}
污染到tem目录,因为tem目录有文件可以读取
{"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/ddahJ6"}
接着访问就行了
最后再污染到/目录进行文件读取
{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.file_or_directory","value": "/"}
结束