MENAS'S SQLi Challenge

Nine SQL injection labs three easy, three difficult, three hard. Every challenge below is a real REST endpoint. Send requests with curl, Burp Suite, Postman, or code of your own, and read the flag straight out of the HTTP response once you've exploited the endpoint. Flags look like MENAS{...}.

1.Login Bypass*

A login form for the staff portal. There is exactly one account you're supposed to know about, and it isn't yours.

Objective

Log in as an account with the admin role without knowing a valid password.

Endpoint

POST/api/challenges/easy-01

Parameters

username, password

Example request

curl -X POST /api/challenges/easy-01 \
  -H "Content-Type: application/json" \
  -d '{"username": "test", "password": "test"}'

Example response

{
  "success": false,
  "message": "Invalid username or password"
}

Hints

Hint 1

What happens to the SQL query if your password field contains a quote character?

Hint 2

You don't need to know a real password if you can make the WHERE clause always true.

2.Product Search*

A storefront search box for a small catalog. Search returns matching products but the catalog has more rows than the storefront ever shows you.

Objective

Use the search box to reveal a hidden product outside the normal catalog.

Endpoint

GET/api/challenges/easy-02?q=

Parameters

q

Example request

curl "/api/challenges/easy-02?q=lamp"

Example response

{
  "results": [
    { "id": 1, "name": "Desk Lamp", "price": 24.99 }
  ]
}

Hints

Hint 1

The search term is dropped straight into a LIKE clause.

Hint 2

Closing the quote lets you add your own condition to the WHERE clause.

3.User Lookup*

A public profile lookup by numeric ID. Only IDs 1 through 5 are advertised in the UI.

Objective

Retrieve a profile that isn't one of the five advertised IDs.

Endpoint

GET/api/challenges/easy-03?id=

Parameters

id

Example request

curl "/api/challenges/easy-03?id=1"

Example response

{
  "results": [
    { "id": 1, "username": "alice", "bio": "Coffee enthusiast." }
  ]
}

Hints

Hint 1

The id parameter is concatenated straight into the query, not bound as a parameter.

Hint 2

You're not limited to a single numeric value.

1.Union Harvest**

The same product search, this time backed by a second, unrelated table you were never given a link to. The frontend only ever shows product rows: id, name, price.

Objective

Use a UNION-based injection to pull data out of a table the product search was never meant to expose.

Endpoint

GET/api/challenges/difficult-01?q=

Parameters

q

Example request

curl "/api/challenges/difficult-01?q=lamp"

Example response

{
  "results": [
    { "id": 1, "name": "Desk Lamp", "price": 24.99 }
  ]
}

Hints

Hint 1

Work out the column count before anything else UNION requires a matching number of columns.

Hint 2

The product columns are (id, name, price). Can you SELECT three columns from somewhere else with matching types?

Hint 3

difficult01_secrets (id, note, value)

2.True or False**

An account-existence checker. It never echoes any data back only { "exists": true } or { "exists": false }. Somewhere in that database is a row that was never meant to be found this way.

Objective

Use boolean-based blind injection to extract a hidden value one character at a time.

Endpoint

GET/api/challenges/difficult-02?id=

Parameters

id

Example request

curl "/api/challenges/difficult-02?id=1"

Example response

{
  "exists": true
}

Hints

Hint 1

You get exactly one bit of information per request: true or false.

Hint 2

You can inject a subquery condition alongside the id check with AND.

3.Order Lookup**

Customers check an order's status by supplying both an order ID and the email it was placed under. Both fields feed the same query.

Objective

Break out of one of the two parameters to view an order that isn't yours.

Endpoint

POST/api/challenges/difficult-03

Parameters

orderId, email

Example request

curl -X POST /api/challenges/difficult-03 \
  -H "Content-Type: application/json" \
  -d '{"orderId": "1001", "email": "customer@example.com"}'

Example response

{
  "success": false,
  "message": "No matching order found"
}

Hints

Hint 1

Both fields are concatenated into the same WHERE clause, joined with AND.

Hint 2

You only need to control one of the two fields to neutralize the other.

1.Ticking Clock***

A username-exists check that always responds with the same generic message and the same JSON shape, regardless of whether the username is real. There is nothing to read in the response except how long it takes to arrive.

Objective

Use time-based blind injection to extract a hidden value using response timing alone.

Endpoint

GET/api/challenges/hard-01?user=

Parameters

user

Example request

curl "/api/challenges/hard-01?user=alice"

Example response

{
  "message": "Request processed"
}

Hints

Hint 1

There's no visible difference between a true and false condition in the response body.

Hint 2

SQLite has no SLEEP(); a heavyweight recursive query can be used to simulate a delay when a condition is true.

2.Delayed Message***

A two-step flow. Step one: register a display name (this write is done safely). Step two: view your public profile, which is rendered by looking your stored name back up in the database. The registration step is not the vulnerable one.

Objective

Register a display name that plants an injection payload, then trigger the profile lookup so the stored payload executes on step two.

Endpoint

POST/api/challenges/hard-02 (register), /api/challenges/hard-02/profile?id= (view)

Parameters

displayName

Example request

curl -X POST /api/challenges/hard-02 \
  -H "Content-Type: application/json" \
  -d '{"displayName": "alice"}'
# -> { "success": true, "id": 1 }

curl "/api/challenges/hard-02/profile?id=1"

Example response

{
  "results": [
    { "displayName": "alice" }
  ]
}

Hints

Hint 1

The registration endpoint uses a parameterized query — it is not where the bug is.

Hint 2

The profile endpoint re-reads your stored name out of the database and drops it into a fresh query unsafely.

3.Filtered Directory***

An employee directory filtered by department. A naive filter strips certain SQL keywords out of your input before running the query but it only strips them once, and it doesn't touch whitespace-free syntax.

Objective

Bypass the keyword filter to pull a row out of a table the directory search never queries directly.

Endpoint

GET/api/challenges/hard-03?dept=

Parameters

dept

Example request

curl "/api/challenges/hard-03?dept=engineering"

Example response

{
  "results": [
    { "id": 1, "name": "Sam Rivera", "department": "engineering" }
  ]
}

Hints

Hint 1

Send the word UNION and see what comes back out the other side.

Hint 2

A filter that does a single, non-recursive string replace can be defeated by nesting the banned word inside itself.