webfreehard

umdmarket

umdctf

Task: a browser prediction market exposed WebTransport protocol docs and HMAC-signed quote tokens for trading. Solution: abuse RESEND, which re-signed historical prices with the current sequence number, to replay stale favorable quotes as fresh ones and grind the balance above the flag threshold.

$ ls tags/ techniques/
client_bundle_reversingprotocol_reversingstale_quote_bypasssigned_quote_replayresend_window_arbitrage

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

umdmarket — UMDCTF

Description

Predicting the future is now legal in all 50 states.

English summary: the challenge was a prediction market web app. We started with $100, needed to reach 10,000,000 internal units ($1000), and then call the flag purchase action. The intended defense was that every trade had to use a recent HMAC-signed quote, but the resend feature broke that freshness guarantee.

Analysis

1. Recon from the frontend bundle

The first useful finding was not in the visible UI, but in the shipped client bundle.

  • /docs/llms was a decoy.
  • /docs contained the real protocol documentation.
  • the frontend also exposed the exact WebTransport endpoint and certificate pin.

The service connected to:

https://umdmarket.challs.umdctf.io:4443/wt

with the embedded SHA-256 certificate hash:

ac02c9f7e1558563180ed412dedb9e793fd9b3ab36d64beb7e178283a68a4c9f

client_methods.txt made the binary client logic much easier to reconstruct. It showed the request opcodes, field order, and the fact that trades are not priced freely by the client. Instead, the client must echo back a server-issued signed quote.

2. Reconstructing the protocol

The important packet types were:

MessageOpcodeFields
QUOTE datagram0x01seq:uint16, ticker_id:uint16, yes_price:uint16, hmac[8]
RESEND request0x26seq:uint16, ticker_id:uint16
TRADE request0x30seq, ticker_id, yes_price, hmac, side:uint8, qty:uint32
BUY_FLAG request0x50available once balance >= 10,000,000

The signed QUOTE datagram was the core security primitive. The docs described the intended model as:

  1. server streams signed quotes,
  2. client may trade only by replaying one of those quotes,
  3. stale quotes are rejected when older than MaxAge = 5 ticks.

...

$ grep --similar

Similar writeups