> For the complete documentation index, see [llms.txt](https://tazarkour.gitbook.io/blog/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tazarkour.gitbook.io/blog/writeups/hxp-39c3-ctf-or-on-error-resume-next-web.md).

# hxp 39C3 CTF | on error resume next (Web)

<figure><img src="/files/V3DF3CuL6EBtigIWy1I2" alt=""><figcaption></figcaption></figure>

As always with hxp the challenge resolves around exploiting a golang web app, but unlike their last [famous challenge](https://hxp.io/blog/114/hxp-38C3-CTF-Fajny-Jagazyn-Wartoci-Kluczy/) this one dosen't have error handling in the code.&#x20;

main.go :&#x20;

```go
package main

import (
	"database/sql"
	_ "embed"
	"net/http"
	"os"
	"strconv"
	"sync"
	"text/template"
	"time"

	_ "github.com/go-sql-driver/mysql"
)

type User struct {
	ID     int64
	Name   string
	Credit uint64
}

type transactions struct {
	Sender   int64
	Receiver int64
	Amount   uint64
}

//go:embed index.html
var indexHtml string
var tmpl = template.Must(template.New("index.html").Parse(indexHtml))

var db *sql.DB

//go:embed schema.sql
var dbSchema string

func initDB() {
	db, _ = sql.Open("mysql", "user:password@tcp(db)/db?multiStatements=true")

	db.SetConnMaxLifetime(time.Minute * 5)
	db.SetMaxOpenConns(1)
	db.SetMaxIdleConns(1)

	db.Exec(dbSchema)
}

func Sum(userID int64) uint64 {
	if userID == 1 { // System is always bankrupt :/
		return 0
	}

	rows, _ := db.Query("SELECT amount, receiver, sender FROM transactions")
	defer rows.Close()

	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
}

func main() {
	initDB()

	// Sorry, I still haven't learned DB transactions :/
	var mutex sync.Mutex

	http.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
		mutex.Lock()
		defer mutex.Unlock()

		rows, _ := db.Query("SELECT name, id FROM users")
		defer rows.Close()
		var users []User

		for rows.Next() {
			user := User{}
			rows.Scan(&user.Name, &user.ID)
			users = append(users, user)
		}

		for i := range users {
			users[i].Credit = Sum(users[i].ID)
		}

		tmpl.Execute(w, struct {
			Msg   string
			Users []User
		}{
			r.URL.Query().Get("msg"),
			users,
		})
	})

	demoUserLimit := 5
	http.HandleFunc("POST /signup", func(w http.ResponseWriter, r *http.Request) {
		mutex.Lock()
		defer mutex.Unlock()

		if demoUserLimit <= 0 {
			http.Redirect(w, r, "/?msg=Demo+Version+Limit+Reached", http.StatusFound)
			return
		}
		demoUserLimit -= 1

		r.ParseForm()

		res, _ := db.Exec("INSERT INTO users (name, id) VALUES (?, ?)", r.Form.Get("name"), r.Form.Get("id"))
		id, _ := res.LastInsertId()
		db.Exec("INSERT INTO transactions (subject, amount, sender, receiver) VALUES (?, ?, ?, ?)", "Gift from the system", 10, 1, id)

		http.Redirect(w, r, "/?msg=User+Created", http.StatusFound)
	})

	http.HandleFunc("POST /transfer", func(w http.ResponseWriter, r *http.Request) {
		mutex.Lock()
		defer mutex.Unlock()

		r.ParseForm()

		sender, _ := strconv.ParseInt(r.Form.Get("sender"), 10, 64)
		amount, _ := strconv.ParseUint(r.Form.Get("amount"), 10, 64)

		sum := Sum(sender)

		if sum < amount {
			http.Redirect(w, r, "/?msg=Too+Poor+For+Transfer", http.StatusFound)
			return
		}

		db.Exec("INSERT INTO transactions (receiver, sender, subject, amount) VALUES (?, ?, ?, ?)", r.Form.Get("receiver"), sender, r.Form.Get("subject"), amount)
		http.Redirect(w, r, "/?msg=Transferred", http.StatusFound)
	})

	http.HandleFunc("POST /flag", func(w http.ResponseWriter, r *http.Request) {
		mutex.Lock()
		defer mutex.Unlock()

		r.ParseForm()

		id, _ := strconv.ParseInt(r.Form.Get("id"), 10, 64)
		sum := Sum(id)
		if sum >= 1337 {
			flag, _ := os.ReadFile("flag.txt")

			http.Redirect(w, r, "/?msg="+string(flag), http.StatusFound)
			return
		}

		http.Redirect(w, r, "/?msg=Too+Poor+For+Flag", http.StatusFound)
	})

	http.ListenAndServe(":13371", nil)
}

```

schema.sql :&#x20;

```sql
DROP TABLE IF EXISTS transactions;
DROP TABLE IF EXISTS users;

CREATE TABLE users (
    id SERIAL,
    name VARCHAR(255)
);
INSERT INTO users(name, id) VALUES ('System', 1);

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)
);
```

There are two key distinctions here in the code, first the balance of each user is only calculated through transcations with the sum function, the user system will return automatically 0.

```go
func Sum(userID int64) uint64 {
	if userID == 1 { // System is always bankrupt :/
		return 0
	}

	rows, _ := db.Query("SELECT amount, receiver, sender FROM transactions")
	defer rows.Close()

	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 second one is in the user creation, we can control the id of the user but we cannot reuse the same id or put an id below 1.

```go
http.HandleFunc("POST /signup", func(w http.ResponseWriter, r *http.Request) {
		mutex.Lock()
		defer mutex.Unlock()

		if demoUserLimit <= 0 {
			http.Redirect(w, r, "/?msg=Demo+Version+Limit+Reached", http.StatusFound)
			return
		}
		demoUserLimit -= 1

		r.ParseForm()

		res, _ := db.Exec("INSERT INTO users (name, id) VALUES (?, ?)", r.Form.Get("name"), r.Form.Get("id"))
		id, _ := res.LastInsertId()
		db.Exec("INSERT INTO transactions (subject, amount, sender, receiver) VALUES (?, ?, ?, ?)", "Gift from the system", 10, 1, id)

		http.Redirect(w, r, "/?msg=User+Created", http.StatusFound)
	})
```

this can be exploited, when given 9223372036854775807 which is the maximum value for int64 for Golang, this would interpret the integer as 0 but when given to the database it is given as a string so it would pass as that value in big integer.

so we create a user with that ID :&#x20;

<figure><img src="/files/7onjCeJQQIllIE3USO0B" alt=""><figcaption></figcaption></figure>

it passes perfectly and no transaction is created for that user.

the same logic error is also found in the transfer method :&#x20;

```go
http.HandleFunc("POST /transfer", func(w http.ResponseWriter, r *http.Request) {
		mutex.Lock()
		defer mutex.Unlock()

		r.ParseForm()

		sender, _ := strconv.ParseInt(r.Form.Get("sender"), 10, 64)
		amount, _ := strconv.ParseUint(r.Form.Get("amount"), 10, 64)

		sum := Sum(sender)

		if sum < amount {
			http.Redirect(w, r, "/?msg=Too+Poor+For+Transfer", http.StatusFound)
			return
		}

		db.Exec("INSERT INTO transactions (receiver, sender, subject, amount) VALUES (?, ?, ?, ?)", r.Form.Get("receiver"), sender, r.Form.Get("subject"), amount)
		http.Redirect(w, r, "/?msg=Transferred", http.StatusFound)
	})
```

when transferring the value it would send it to that user but it would never get removed from the sender since when passing the userid in the flag method to the Sum method, this userid would be passed as 0 and would be not found in the database, making us able to make infinite transactions.

<figure><img src="/files/F90z7j1yczo3HZmh9Qf7" alt=""><figcaption></figcaption></figure>

we keep passing the same request multiple times until we get the desired amount and then get the flag :&#x20;

<figure><img src="/files/2CkXrd3jAzKeOvthsLO8" alt=""><figcaption></figcaption></figure>

As always thank you for reading :)
