Fearless Concurrency

Challenge Description

Rust is the most safest, fastest and bestest language to write web app! The code compiles, therefore it is impossible for bugs! PS: This is my first rust project (real) 🦀🦀🦀🦀🦀 Comment Suggest edit

Author: jro

http://challs.nusgreyhats.org:33333

https://storage.googleapis.com/greyctf-challs/dist-fearless-concurrency.zip


Code Analysis

The web application has a registration function which generates a Random UID.

async fn register(State(state): State<AppState>) -> impl IntoResponse {
    let uid = rand::random::<u64>();
    let mut users = state.users.write().await;
    let user = User::new();
    users.insert(uid, user);
    uid.to_string()
}

We are also able to craft query

async fn query(State(state): State<AppState>, Json(body): Json<Query>) -> axum::response::Result<String> {
    let users = state.users.read().await;
    let user = users.get(&body.user_id).ok_or_else(|| "User not found! Register first!")?;
    let user = user.clone();

    // Prevent registrations from being blocked while query is running
    // Fearless concurrency :tm:
    drop(users);

    // Prevent concurrent access to the database!
    // Don't even try any race condition thingies
    // They don't exist in rust!
    let _lock = user.lock.lock().await;
    let mut conn = state.pool.get_conn().await.map_err(|_| "Failed to acquire connection")?;

    // Unguessable table name (requires knowledge of user id and random table id)
    let table_id = rand::random::<u32>();
    let mut hasher = Sha1::new();
    hasher.update(b"fearless_concurrency");
    hasher.update(body.user_id.to_le_bytes());
    let table_name = format!("tbl_{}_{}", hex::encode(hasher.finalize()), table_id);

    let table_name = dbg!(table_name);
    let qs = dbg!(body.query_string);

    // Create temporary, unguessable table to store user secret
    conn.exec_drop(
        format!("CREATE TABLE {} (secret int unsigned)", table_name), ()
    ).await.map_err(|_| "Failed to create table")?;

    conn.exec_drop(
        format!("INSERT INTO {} values ({})", table_name, user.secret), ()
    ).await.map_err(|_| "Failed to insert secret")?;


    // Secret can't be leaked here since table name is unguessable!
    let res = conn.exec_first::<String, _, _>(
        format!("SELECT * FROM info WHERE body LIKE '{}'", qs),
        ()
    ).await;

    // You'll never get the secret!
    conn.exec_drop(
        format!("DROP TABLE {}", table_name), ()
    ).await.map_err(|_| "Failed to drop table")?;

    let res = res.map_err(|_| "Failed to run query")?;

    // _lock is automatically dropped when function exits, releasing the user lock

    if let Some(result) = res {
        return Ok(result);
    }
    Ok(String::from("No results!"))
}

Looking at this function, we can see that it is vulerable to SQL Injection.


Exploit

I used SQL Map, and was able to perform the sql injection

Next, I attempted to dump the table.

Looking at the source code, I was able to identify how they created the random strings for the table.

I then copied the code, to create the hash of my current user with a simple rust script.

Upon inspecting the dumped table, I noticed that my current user's table is not being deleted for some reason. To gain a better understanding, I proxied my SQL Map to Burp Suite.

SQL Injection payload generated by SQLMap

It appears that there were multiple tables containing my user hashes. I attempted to retrieve the secret from the last table on the list.

SQL Injection payload generated by SQLMap

With the retrieved secret, I was able to get the flag from the /flag endpoint.

Flag: grey{ru57_c4n7_pr3v3n7_l061c_3rr0r5}


Further Discussion

However, this is definitely not the intended solution. Looking at the source code, it will attempt to drop the table after the query ran.

The challenge name Fearless Concurrency also hinted at it being a Race Condition or something similar, where you have 2 payload running concurrently.

After the CTF, while discussing solution in the discord server, the author revealed that the intended solution was to inject a sleep, and use a seperate user account to perform the SQLI to leak the secret.

#web in the Grey Cat The Flag discord server

Lorenz has also kindly shared his proof of concept script in the message linked below.

https://discord.com/channels/969232688521281606/969232688911360035/1231460020412223589

Last updated

Was this helpful?