Kabinet's GitBook
  • 🚩Kabinet CTF's Writeups
  • Page
  • 2025
    • Thuderdome
      • Emerge through the breach
      • Pulled from the sky
      • An absent defense
      • A new wave (web of deceit)
      • Crossing the great divide
      • Joining forces as one
      • Infiltrate (open the gate)
      • Jaeger
      • Victory
  • 2024
    • GreyCTF 2024
      • Markdown Parser
      • Fearless Concurrency
      • GreyCTF Survey
      • Baby Web
      • Beautiful Styles
      • All About Timing
      • Poly Playground
    • TetCTF 2024
      • Hello from API GW
      • Microservices
  • 2023
    • BSidesSF Cloud Village CTF
      • Tony Tony Tony
      • Plain Sight
      • A Suit of Armor Around The World
      • Sharing is Caring + Sequel
      • Photo Drive
    • DART CTF
      • Flag 1
      • Flag 2
      • Flag 3
      • Flag 4
      • Flag 5
      • Flag 6
      • Flag 7
      • Flag 8
      • Flag 9
      • Flag 10
    • EKS Cluster Games
    • Big IAM Challenge
  • 2022
    • Stack The Flag
      • Secret of Meow Olympurr
  • Authored
    • Cyber League 2025 Major 1
      • Perfect Storage
      • catalog commits
      • pawtainer hub
    • Lag and Crash 2023
      • Managed Secrets
      • Pickle Rick
      • Cloudy with a chance of meatball
    • NYP InfoSec December CTF 2022
      • Super Secure Technology Infrastructure
      • Self Introduction
      • Aww Cuter Cat
      • Obligatory Calc
      • BreadSecurity
  • NYP InfoSec Introduction to Pentesting Workshop
Powered by GitBook
On this page
  • Challenge Description
  • Code Analysis
  • Exploit
  • Further Discussion

Was this helpful?

  1. 2024
  2. GreyCTF 2024

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.

    let res = conn.exec_first::<String, _, _>(
        format!("SELECT * FROM info WHERE body LIKE '{}'", qs),
        ()
    ).await;

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.

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

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

use sha1::{Digest, Sha1};
use hex;

fn main() {
    let mut hasher = Sha1::new();
    hasher.update(b"fearless_concurrency");
    hasher.update(2629577642326695728u64.to_le_bytes());
    let table_name = format!("tbl_{}", hex::encode(hasher.finalize()));
    println!("{}", table_name);
}

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.

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

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.

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

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.

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

PreviousMarkdown ParserNextGreyCTF Survey

Last updated 1 year ago

Was this helpful?

https://discord.com/channels/969232688521281606/969232688911360035/1231460020412223589
SQL Injection payload generated by SQLMap
SQL Injection payload generated by SQLMap
#web in the Grey Cat The Flag discord server