Web
Easy
150 points

BP88 Level 1

Recuite 2025 - HCMUS
6 tháng 10, 2025
PRNG
Static Seed
Predictable Random
Dice Game
Recuite 2025 - HCMUS
Web

BP88 - Write-up

Thông Tin Challenge

  • Danh mục: Web
  • Độ khó: Dễ
  • Lỗ hổng: PRNG Dự Đoán Được (Static Seed)
  • Game: Tài Xỉu (trò chơi xúc xắc)

Tổng Quan

BP88 là một web-based dice gambling game với 3 levels độ khó. Level 1 sử dụng static seed cho Python's random module, khiến kết quả xúc xắc hoàn toàn có thể dự đoán được.

Cơ Chế Trò Chơi

Luật Tài Xỉu

  • Tung 3 xúc xắc (mỗi xúc xắc 1-6)
  • Tổng ≥ 11: Tài (High)
  • Tổng ≤ 10: Xỉu (Low)
  • Người chơi đặt cược Tài hoặc Xỉu

Điều Kiện Thắng

  • Level 1: Đạt 50 coins để mở khóa Level 2

Lỗ Hổng: PRNG với Static Seed

Phân Tích Source Code

def prepare_seed(level):
    """ Tạo seed cho module `random` """
    if level == 1:
        random.seed(13371337)  # ⚠️ Seed cố định!
    elif level == 2:
        random.seed(round(time.time()))
    else:
        random.seed()

def roll_dice():
    dice = [random.randint(1, 6) for _ in range(3)]
    return dice

Vấn đề:

  • Level 1 luôn sử dụng seed giống nhau (13371337)
  • Chuỗi số ngẫu nhiên là hoàn toàn xác định
  • Có thể dự đoán outcome của mọi lượt tung xúc xắc

Khai Thác

Bước 1: Reverse Engineer PRNG

Python's random module với seed cố định sẽ luôn tạo ra cùng một chuỗi:

import random

def predict_result(seed):
    random.seed(seed)
    dice = [random.randint(1, 6) for _ in range(3)]
    total = sum(dice)
    result = 'tai' if total >= 11 else 'xiu'
    return result, dice, total

# Seed của Level 1
seed = 13371337
result, dice, total = predict_result(seed)
print(f"Kết quả: {result}, Xúc xắc: {dice}, Tổng: {total}")

Bước 2: Test Dự Đoán

import random

random.seed(13371337)

# Lượt tung đầu tiên
print([random.randint(1,6) for _ in range(3)])  # [5, 2, 5] = 12 = Tài

# Reset seed cho mỗi game
random.seed(13371337)
print([random.randint(1,6) for _ in range(3)])  # [5, 2, 5] = 12 = Tài (giống nhau!)

Điểm mấu chốt: Mỗi round game reset seed về 13371337, nên mọi round đều có kết quả giống hệt nhau!

Cách Phòng Chống

❌ Code Lỗ Hổng

def prepare_seed(level):
    if level == 1:
        random.seed(13371337)  # Seed cố định

✅ Code An Toàn

import secrets

def roll_dice():
    # Dùng random bảo mật mã hóa
    dice = [secrets.randbelow(6) + 1 for _ in range(3)]
    return dice

# Hoặc dùng os.urandom
import os
def roll_dice_secure():
    dice = [int.from_bytes(os.urandom(1), 'big') % 6 + 1 for _ in range(3)]
    return dice

Cách Tiếp Cận Tốt Hơn: Server-Side Secrets

import secrets
import time

def generate_provably_fair_seed():
    # Kết hợp nhiều nguồn entropy
    timestamp = str(time.time_ns())
    random_bytes = secrets.token_hex(32)
    server_secret = os.environ.get('SERVER_SECRET')
    
    # Hash tất cả lại với nhau
    import hashlib
    seed_material = f"{timestamp}{random_bytes}{server_secret}"
    seed = int(hashlib.sha256(seed_material.encode()).hexdigest(), 16)
    
    return seed % (2**32)

Bài Học Rút Ra

  1. Không bao giờ dùng static seeds cho việc tạo số ngẫu nhiên
  2. Đừng dùng random module cho ứng dụng liên quan bảo mật
  3. Dùng secrets module cho cryptographic randomness
  4. Đừng reset seed về cùng một giá trị mỗi round
  5. Hệ thống provably fair cần commitment schemes

Level 2

Thì seed lúc này được random nhưng seed cũng hiển thị ở trên web nên có thể dễ dàng lấy và sử dụng để dự đoán

Level 3

Lúc này seed đã được giấu đi nhưng nó vẫn năm đâu đó ở trên web thì có thể mở browers tool rồi nhập lệnh JS để hiển thị nó

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

// 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);
    });
});

Flag

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

150
Points
Easy
Difficulty
Web
Category