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#

python
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:

python
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#

python
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#

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

✅ Code An Toàn#

python
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#

python
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:

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

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