Appointment

A login form that concatenates input into its SQL. The flag falls out of a one-line tautology - the value of the box is understanding exactly what the server-side query does before and after the injection.

OS
Linux
Difficulty
Very Easy
Retired
06 Oct 2021
Skills
web, sql-injection, auth-bypass

Appointment is the SQL-injection box in the Starting Point: a single login form, no password, flag on success. The payload is the canonical ' or '1'='1, which almost everyone can recite. The part worth slowing down on is why it works - what the application does with the two text fields, and the precise shape of the query before and after the injection. Get that right and SQLi stops being a memorised string and becomes something you can reason about on a form you have never seen.

Path to root, at a glance:
  1. nmap -> port 80, Apache, a login page.
  2. A single quote in the username breaks the query -> injectable.
  3. A tautology plus a comment bypasses auth -> flag.

Recon - nmap

┌──(r3vpwnx㉿r3vpwnx)-[~/CTF/HTB]
└─$ sudo nmap -Pn -A 10.129.124.108
PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd 2.4.38 ((Debian))
|_http-title: Login

One service, and nmap even grabs the page title: Login. A login form with no other attack surface is a near-explicit invitation to test the authentication logic itself.

Enumeration - what the form probably does

Before touching it, reason about the back end. A naive PHP login handler builds its query by gluing the two POST fields straight into a string:

$q = "SELECT * FROM users WHERE username='$user' AND password='$pass'";

“Logged in” means that query returned at least one row. There is no separate password check in code - the database is the authority, and the password comparison lives inside the WHERE. That is the whole weakness: if I can make the WHERE clause true without knowing a password, I am in.

Foothold - SQL injection auth bypass

First, confirm the field is injectable by breaking the string with a lone single quote:

Username: admin'

An error (rather than a plain “wrong credentials”) means my ' reached the SQL parser and left it with an unterminated string - the input is not being escaped. Now supply a username that closes the string, adds an always-true condition, and comments out everything after it:

Username: admin' or '1'='1'-- -

Conceptually the query becomes:

SELECT * FROM users WHERE username='admin' AND password='test' OR 1=1#'
  • password='test' is false
  • OR 1=1 is always true
  • # / -- - comments out the remainder

Operator precedence does the rest: AND binds tighter than OR, so the row test collapses to (username='admin' AND password='test') OR 1=1, which is false OR true = true for every row. The query returns the whole users table, the handler sees “at least one row,” and treats me as authenticated. (-- - is the MySQL comment token - it needs the trailing space, which the extra - guarantees.)

Loot - the flag

Congratulations!
Your flag is: [flag redacted]

Detection and hardening

  • Parameterise the query. Prepared statements (WHERE username=? AND password=?) send the SQL and the data on separate channels, so input can never become syntax. This is the fix - not escaping, not a blacklist.
  • Do not let authentication be a row count. Fetch the user by name, then verify a hashed password (password_verify) in application code, so a tautology that returns rows still fails the credential check.
  • Fail closed and quiet. A single quote should not surface a database error; generic failures deny the attacker the oracle that confirmed injectability here.
  • Blue team: ' or '1'='1, -- -, #, UNION SELECT in POST bodies are high-signal WAF/log patterns - but treat the WAF as telemetry, not as the fix.

Lessons

  • SQLi is a reasoning exercise, not a payload. Model the query the server builds, then craft input that changes its meaning rather than its data.
  • The database is doing exactly what it was told. The injection is valid SQL; the bug is that untrusted text was allowed to be SQL at all.
  • Prepared statements end the class. Every mitigation short of separating code from data is a speed bump.