BP88 Level 3

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 DevTools và kiể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#
pythondef 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:
- Browser console.log() nếu có debug code
- Kiểm tra tin nhắn Socket.IO
- Tương quan timing với Level 2
- 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:
- Mở Browser DevTools (F12)
- Vào tab Console
- Tìm các giá trị đã được log
- 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:
pythonimport 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:
pythonimport 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:
- DevTools → tab Sources
- Tìm keywords:
seed,level3,random,id_level3 - Kiểm tra tất cả files
.js - 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#
pythonimport 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:
pythonimport 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ừ:
- Tìm kiếm giá trị seed (trinh sát)
- Yêu cầu cược lớn (50,000 coins)
- 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#
pythonimport 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#
pythondef 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#
- Security through obscurity không hoạt động
- Debug code leak thông tin nhạy cảm
- Code phía client luôn có thể đọc được
- Timing attacks hoạt động ngay cả không có seed bị lộ
- 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}