Web
Medium
300 points

BP88 Level 3

Recuite 2025 - HCMUS
6 tháng 10, 2025
PRNG
Hidden Seed
DevTools
Browser Exploitation
Recuite 2025 - HCMUS
Web

BP88 Level 3 - Write-up#

Thông Tin Challenge#

  • Danh mục: Web
  • Độ khó: Trung bình-Khó
  • Lỗ hổng: PRNG Dự Đoán Được (Dynamic Seed - Ẩn)
  • Yêu cầu: Hoàn thành Level 2 (1,000 coins)

Tổng Quan#

Level 3 là challenge cuối cùng của BP88. Seed vẫn là dynamic timestamp như Level 2, nhưng KHÔNG được phát sóng trong sự kiện gameData. Player phải tìm cách trích xuất giá trị seed ẩn thông qua browser DevToolskiểm tra sự kiện Socket.IO.

Cơ Chế Trò Chơi#

Điều Kiện Thắng#

  • Level 3: Đạt 50,000 coins để thắng
  • Bắt đầu từ 1,000 coins (từ Level 2)

Lỗ Hổng: Seed Ẩn Nhưng Vẫn Truy Cập Được#

Phân Tích Source Code#

python
def prepare_seed(level):
    """ Tạo seed cho module `random` """
    if level == 1:
        random.seed(13371337)
    elif level == 2:
        random.seed(round(time.time()))
    else:
        random.seed(round(time.time()))  # ⚠️ Giống Level 2!

Điểm khác biệt: Server không bao gồm id_level3 trong Socket.IO events (hoặc bị che):

javascript
// Payload sự kiện gameData
{
    id_level1: 13371337,
    id_level2: 1760086470,
    id_level3: "***REDACTED***",  // Bị ẩn!
    time: 8
}

Nhưng: Giá trị seed vẫn có thể truy cập qua:

  1. Browser console.log() nếu có debug code
  2. Kiểm tra tin nhắn Socket.IO
  3. Tương quan timing với Level 2
  4. JavaScript source code nếu có sử dụng seed phía client

Kỹ Thuật Khai Thác#

Phương Pháp 1: Phát Hiện Console.log()#

Giả thuyết: Developer có thể để lại debug code:

javascript
// Đâu đó trong JS phía client
console.log('Level 3 seed:', level3_seed);  # ⚠️ Debug code!

Khai thác:

  1. Mở Browser DevTools (F12)
  2. Vào tab Console
  3. Tìm các giá trị đã được log
  4. Tìm patterns khớp với định dạng timestamp
javascript
// Filter console cho số
console.log = (function(oldLog) {
    return function(...args) {
        args.forEach(arg => {
            if (typeof arg === 'number' && arg > 1700000000) {
                console.warn('🎯 Tìm thấy seed tiềm năng:', arg);
            }
        });
        oldLog.apply(console, args);
    };
})(console.log);

Phương Pháp 2: Tái Sử Dụng Seed từ Level 2#

Điểm mấu chốt: Level 2 và Level 3 cùng sử dụng round(time.time())!

Khai thác:

python
import random

# Giả định: Level 3 dùng cùng seed với Level 2
# vì chúng trong cùng cửa sổ thời gian

def predict_level3(level2_seed):
    # Nếu rounds xảy ra trong cùng giây, seed giống hệt nhau
    random.seed(level2_seed)
    dice = [random.randint(1, 6) for _ in range(3)]
    result = 'tai' if sum(dice) >= 11 else 'xiu'
    return result, dice

# Lấy seed từ round cuối của Level 2
last_seed = 1760086470  # Từ Level 2

# Thử cùng seed cho Level 3
result, dice = predict_level3(last_seed)
print(f"Dự đoán Level 3: {result}")

Nếu timing khác:

python
import time

# Lấy timestamp hiện tại
current_time = round(time.time())

# Thử thời gian hiện tại ±2 giây
for offset in [-2, -1, 0, 1, 2]:
    seed = current_time + offset
    random.seed(seed)
    dice = [random.randint(1, 6) for _ in range(3)]
    result = 'tai' if sum(dice) >= 11 else 'xiu'
    print(f"Seed {seed}: {result} {dice}")

Phương Pháp 3: Chặn Tin Nhắn Socket.IO#

Ngay cả không có trong gameData, seed có thể trong events khác:

javascript
// Chặn TẤT CẢ tin nhắn Socket.IO
socket.onAny((eventName, ...args) => {
    console.log('Event:', eventName, args);
    
    // Tìm kiếm sâu các giá trị giống seed
    JSON.stringify(args).match(/\d{10}/g)?.forEach(num => {
        console.log('🔍 Seed tiềm năng:', num);
    });
});

Phương Pháp 4: Phân Tích JavaScript Source Code#

Kiểm tra sử dụng seed phía client:

  1. DevTools → tab Sources
  2. Tìm keywords: seed, level3, random, id_level3
  3. Kiểm tra tất cả files .js
  4. Tìm biến bị lộ vô tình
javascript
// Ví dụ code lỗ hổng
var seeds = {
    level1: 13371337,
    level2: getCurrentTime(),
    level3: getCurrentTime()  // ⚠️ Nhìn thấy trong source!
};

Phương Pháp 5: Phân Tích Network Request#

Kiểm tra HTTP responses cho dữ liệu bị leak:

bash
# Chặn tất cả requests
curl -v http://target.com/level3 \
  -H "Cookie: session=..." \
  | grep -E '\d{10}'

Hoặc trong DevTools:

  • Tab Network → Tất cả requests
  • Tìm JSON responses
  • Tìm số giống timestamp

Giải Pháp Tự Động Hoàn Chỉnh#

python
import socketio
import random
import requests
import re
import time

class Level3Solver:
    def __init__(self, base_url):
        self.base_url = base_url
        self.http = requests.Session()
        self.sio = socketio.Client()
        self.level3_seed = None
        self.level2_seed = None
        
        # Thiết lập event handlers
        self.sio.on('gameData', self.on_game_data)
        self.sio.onAny(self.on_any_event)
    
    def on_any_event(self, event, *args):
        """Chặn TẤT CẢ events tìm seed"""
        data_str = str(args)
        
        # Tìm số giống timestamp
        timestamps = re.findall(r'\b(17\d{8})\b', data_str)
        for ts in timestamps:
            print(f"[?] Tìm thấy seed tiềm năng: {ts}")
            if not self.level3_seed:
                self.level3_seed = int(ts)
    
    def on_game_data(self, data):
        # Bắt seed Level 2 làm fallback
        if 'id_level2' in data:
            self.level2_seed = data['id_level2']
        
        # Thử tìm seed Level 3 (ngay cả bị che trong hiển thị)
        if 'id_level3' in data and data['id_level3'] != "***REDACTED***":
            self.level3_seed = data['id_level3']
            print(f"[+] Tìm thấy seed Level 3: {self.level3_seed}")
    
    def predict_result(self, seed):
        random.seed(seed)
        dice = [random.randint(1, 6) for _ in range(3)]
        return 'tai' if sum(dice) >= 11 else 'xiu'
    
    def get_seed_estimate(self):
        """Thử nhiều phương pháp để lấy seed"""
        # Phương pháp 1: Dùng seed Level 3 đã bắt
        if self.level3_seed:
            return self.level3_seed
        
        # Phương pháp 2: Dùng seed Level 2 (có thể hoạt động nếu cùng cửa sổ thời gian)
        if self.level2_seed:
            print("[*] Dùng seed Level 2 làm ước tính")
            return self.level2_seed
        
        # Phương pháp 3: Dùng timestamp hiện tại
        print("[*] Dùng timestamp hiện tại làm ước tính")
        return round(time.time())
    
    def place_bet_with_prediction(self):
        seed = self.get_seed_estimate()
        prediction = self.predict_result(seed)
        
        money = self.get_current_money()
        bet_amount = min(money, 100)  # Cược mạnh
        
        self.sio.emit('pull', {
            'id': self.sio.sid,
            'dice': prediction,
            'level': 'level3',
            'name': self.username,
            'money': bet_amount
        })
        
        print(f"[+] Đặt {bet_amount} vào {prediction} (seed: {seed})")
    
    def solve(self):
        # ... (code login và kết nối giống Level 2) ...
        
        # Điều hướng đến level3
        self.http.get(f"{self.base_url}/level3")
        
        # Farm cho đến 50,000
        while self.get_current_money() < 50000:
            self.place_bet_with_prediction()
            time.sleep(3)
        
        print("[+] Hoàn thành Level 3! 🎉")
        
        # Lấy flag
        resp = self.http.get(f"{self.base_url}/")
        flags = re.findall(r'FLAG \d+: ([^\n<]+)', resp.text)
        for i, flag in enumerate(flags, 1):
            print(f"[+] FLAG {i}: {flag}")

# Chạy
solver = Level3Solver("http://target.com")
solver.solve()

Các Bước Khai Thác Thực Tế#

Phương Pháp Thủ Công Từng Bước#

1. Mở trang Level 3

2. Mở DevTools → Console

3. Chạy scan toàn diện:

javascript
// Log mọi thứ
let foundSeeds = new Set();

// Override các phương thức console
['log', 'debug', 'info', 'warn'].forEach(method => {
    let original = console[method];
    console[method] = function(...args) {
        original.apply(console, args);
        args.forEach(arg => {
            let str = String(arg);
            let matches = str.match(/\b17\d{8}\b/g);
            if (matches) {
                matches.forEach(m => {
                    foundSeeds.add(m);
                    console.error('🎯 TÌM THẤY SEED:', m);
                });
            }
        });
    };
});

// Chặn tất cả Socket.IO
socket.onAny((event, ...args) => {
    console.log('📡', event, args);
});

console.log('🔍 Đang theo dõi seeds...');

4. Đợi round bắt đầu

5. Kiểm tra output console cho giá trị seed

6. Dùng seed trong Python:

python
import random
seed = 1760086500  # Từ console
random.seed(seed)
dice = [random.randint(1,6) for _ in range(3)]
print('Đặt cược:', 'TÀI' if sum(dice) >= 11 else 'XỈU')

7. Đặt cược và profit!

Tại Sao Level 3 "Khó Hơn"#

Che Giấu ≠ Bảo Mật#

  • Seed bị ẩn khỏi hiển thị rõ ràng
  • Nhưng cùng lỗ hổng như Level 2
  • Chỉ cần nhiều trinh sát hơn

Vẫn Có Thể Dự Đoán#

python
# Level 2 vs Level 3
def level2_seed():
    return round(time.time())  # Bị lộ

def level3_seed():
    return round(time.time())  # Ẩn nhưng giống nhau!

Thách Thức Thực Sự#

Độ khó của Level 3 đến từ:

  1. Tìm kiếm giá trị seed (trinh sát)
  2. Yêu cầu cược lớn (50,000 coins)
  3. Timing - rounds diễn ra nhanh

Cách Phòng Chống#

Giống Level 2, thêm:

1. Xóa TẤT CẢ Debug Code#

javascript
// ❌ Xóa trước khi production
console.log('seed:', seed);
console.debug('level3_seed:', level3_seed);

2. Dùng Secure Random#

python
import secrets

def generate_level3_dice():
    # Bảo mật mã hóa
    return [secrets.randbelow(6) + 1 for _ in range(3)]

3. Logic Chỉ Server-Side#

python
def play_round(level):
    # Tạo xúc xắc server-side
    dice = generate_secure_dice()
    
    # KHÔNG BAO GIỜ gửi seed hoặc xúc xắc cho client trước khi đóng cược
    # Chỉ gửi kết quả sau khi round kết thúc
    
    return {
        'result': calculate_result(dice),
        'dice': dice  # Chỉ sau khi cược bị khóa
    }

4. Validation Input#

python
@app.route('/bet', methods=['POST'])
def place_bet():
    # Xác minh cược được đặt TRƯỚC KHI round kết thúc
    if time.time() > round_end_time:
        return {'error': 'Round ended'}, 403
    
    # Xử lý cược
    ...

Bài Học Rút Ra#

  1. Security through obscurity không hoạt động
  2. Debug code leak thông tin nhạy cảm
  3. Code phía client luôn có thể đọc được
  4. Timing attacks hoạt động ngay cả không có seed bị lộ
  5. Dùng cryptographic random cho mọi thao tác nhạy cảm về bảo mật

Tools Sử Dụng#

  • Browser DevTools - Theo dõi console, phân tích network
  • Python socketio - Khai thác tự động
  • Burp Suite - Chặn request
  • Python random - Dự đoán PRNG

Flag#

HCMUS{h1dd3n_s33d_st1ll_pr3d1ct4bl3}

Bonus: Cả Ba Flags#

Sau khi hoàn thành Level 3 với 50,000+ coins:

FLAG 1: HCMUS{st4t1c_s33d_1s_pr3d1ct4bl3}
FLAG 2: HCMUS{t1m3st4mp_s33d_3xp0s3d}
FLAG 3: HCMUS{h1dd3n_s33d_st1ll_pr3d1ct4bl3}

Tài Liệu Tham Khảo#

300
Points
Medium
Difficulty
Web
Category