$ cat writeup.md…
$ cat writeup.md…
sekai2026
Task: exploit a Sui/Stellar bridge where relayer attestations omit fee_recipient and pending transactions leak signatures. Solution: front-run completions, repair failed Sui legs, and recycle honest funds until the Stellar balance reaches 250.
No original organizer description was provided in the solved-task notes.
English summary: the challenge provides an integrated Stellar Soroban bridge and Sui Move bridge with an HTTP relayer. The /flag endpoint returns the flag only when the player's Stellar SEKAI balance is at least 250.
The system bridges SEKAI tokens between Stellar and Sui. A relayer observes deposits, signs cross-chain attestations, and submits complete_* transactions on the destination chain. The player can claim 100 SEKAI on Stellar and 100 SEKAI on Sui, while an honest bot continuously cycles funds between both chains.
The key Stellar-side vulnerability is in complete_from_sui(recipient, amount, message_id, attestation, fee_recipient):
recipient.to_string() bytes, amount encoded as little-endian 16 bytes, and message_id;fee_recipient is not included in the signed message and is therefore attacker-controlled.The relayer exposes pending Stellar completion transactions for about 15 seconds at /stellar/<uuid>/pending_transactions before submitting them. Those pending records contain the recipient, amount, message ID, and valid relayer attestation. By copying the pending transaction and submitting it first with fee_recipient set to the player, the attacker steals the amount / 8 bridge fee. The honest recipient still receives amount - fee, but the message becomes processed, so the relayer's later transaction fails with MessageAlreadyProcessed.
The Sui side has a related unsigned-fee-recipient issue. During Sui checkpoint/RPC rebuilds, the relayer sometimes fails to submit complete_from_stellar; failed attestations are exposed at /sui/<uuid>/attestations. The attacker can manually complete those failed Sui attestations for the honest user while setting fee_recipient to the player. These Sui fee coins do not directly satisfy the Stellar balance win condition, but they can be donated to the honest Sui account. The honest bot then bridges them back to Stellar, increasing later honest Sui-to-Stellar amounts and producing enough additional Stellar-side fees to pass the threshold.
The final exploit used the following sequence:
claim, giving the player 100 Stellar SEKAI.100 SEKAI on Sui and deposit that Sui coin to Stellar for the player.fee_recipient = player. The recipient amount and stolen fee both go to the player, so the Stellar balance reaches 200./stellar/<uuid>/pending_transactions and front-run every honest Sui-to-Stellar completion with fee_recipient = player./sui/<uuid>/attestations for failed complete_from_stellar attestations. Manually complete them on Sui with fee_recipient = player so the honest bot can continue cycling.ReceiveCapExceeded). Donating to honest lets the honest bot bridge larger amounts back to Stellar, creating more stealable Stellar fees./flag until the player Stellar balance reaches at least 250.The final successful run performed the setup, front-ran the player's own 100 completion, repaired a failed honest Sui completion of amount 100, then front-ran honest Stellar completions with amounts:
88, 80, 62, 49, 38, 30, 24, 19, 15, 13, 11, 9
This provided enough bridge fees to cross the win threshold and retrieve the flag.
The core of win.py is to poll leaked pending Stellar completions and resubmit them first with the fee redirected to the player:
def frontrun(): while not stop.is_set(): r = requests.get(PEND, timeout=8).json() for t in r.get("pending_transactions", []): mid = t["message_id"] if mid in done: continue res = C.complete( t["recipient"], t["amount"], t["message_id"], t["attestation"], fee_recipient=cfg.PLAYER_PUB, ) if res[0] == "SUCCESS": done.add(mid) time.sleep(0.8)
The Sui repair thread uses failed exposed attestations and the same unsigned-fee-recipient idea:
def repair_sui_failed(): ats = requests.get(cfg.SUI_ATTEST, timeout=12).json().get("attestations", []) for a in ats: if a.get("status") != "failed" or a.get("function") != "complete_from_stellar": continue args = [ cfg.SUI_BRIDGE_OBJ, a["recipient"], str(a["amount"]), list(bytes.fromhex(a["message_id"].removeprefix("0x"))), list(bytes.fromhex(a["attestation"])), cfg.SUI_PLAYER_ADDR, ] sui_tx.move_call(cfg.SUI_PKG, "bridge", "complete_from_stellar", [], args)
188, 214, 239, and 245.200, and honest-cycle fees decay due integer division and bridge fees.ReceiveCapExceeded because the player has already consumed the Stellar receive cap.$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar