✳ Field lesson A5c / 16 min

Classic application attacks and their root fixes

Each classic application attack has one root cause, and the lasting fix sits at that cause. Parameterized queries end SQL injection. One atomic check and action ends a race. Filters in front of the flaw help, but they leave it in place.

appsecinjectionxssrace-conditionsvulnerabilities

Helpful first: Database integrity and what answers reveal

Thirty days to Halcyon · Case 11: Prove it · Episode 9 of 9

The auditors are in the building. Kael Cho, Staff Infrastructure Engineer, turns his screen toward you. A test tenant has paid for one burn's worth of fuel. He sends two bookings in the same instant. Both succeed.

"Each one checked the quota," he says. "Each one was right."

Vera Vance, your mentor, reads the timestamps, then looks at you. "Both checks passed. So where exactly did the fuel go missing?"

By the end you can

  • Trace a time-of-check to time-of-use race and name the fix that closes it
  • Explain why parameterized queries end SQL injection at its root
  • Match each classic application flaw to its root cause and its fix

Two bookings, one fuel quota

The maneuver-booking API handles a booking in two steps. First it checks that the tenant has enough fuel quota. Then it debits the quota. Each step is correct. The trouble is the gap between them.

Kael's two requests arrive together. The first one checks the quota and finds enough. Before it debits, the second one checks too, and it also finds enough. Then both debit. The tenant has reserved twice the fuel it paid for.

This flaw has a name. A race condition is a bug whose result depends on the timing of events. Time-of-check to time-of-use (TOCTOU) is one kind of race condition. The system checks a security precondition at one moment, T1. It uses the resource at a later moment, T2. An attacker acts in the window between T1 and T2.

A concurrent request is one way to use that window. Another is to swap a file's symlink after the system has verified the file's ownership. In both cases the check was true when it ran. It was no longer true when the system acted on it.

  1. Booking A checksQuota is enough
  2. Booking B checksQuota is still enough
  3. Booking A debitsQuota is now used up
  4. Booking B debitsQuota is spent a second time
How one quota is spent twice. Look at steps two and three. The second check runs before the first debit, so it reads a quota that is about to change.

SPOT THE PROBLEM

This is the booking log for Kael's test. The tenant has quota for one burn. Flag the lines that show the race.

Show the answers
  • 14:02:07.105 booking B quota check ok remaining=1 Booking A has checked but not yet debited. This check reads a quota that booking A is about to spend.
  • 14:02:07.110 booking B debit ok remaining=-1 Booking B acts on a check that is no longer true. The quota goes below zero.

Make the check and the action one step

You cannot fix this race by making the check better. The check was already right. The flaw is that the check and the action are separate steps, and something can happen between them.

So the fix is to remove the gap. The check and the action must become a single atomic operation. Atomic means nothing else can run in the middle. Two tools provide that. One is an exclusive lock, which makes every other request wait. The other is a serializable database transaction, which runs as if no other transaction were running beside it.

Vera uses both on the booking API. She wraps the fuel check and the debit inside one serializable transaction with row locking. Booking A locks the tenant's quota row, checks it, debits it, and commits. Booking B waits. When it runs its own check, the quota is gone, and it is refused.

PUT IT IN ORDER

Vera's fix is live. Kael sends bookings A and B together again. Put the events in the order they now happen.

  1. Booking B asks for the same row and has to wait
  2. Booking A checks the quota, debits it, and commits
  3. Booking B gets the row and checks the quota
  4. Booking B finds no quota left and is refused
  5. Booking A opens a transaction and locks the tenant's quota row

Show the answer
  1. Booking A opens a transaction and locks the tenant's quota row
  2. Booking B asks for the same row and has to wait
  3. Booking A checks the quota, debits it, and commits
  4. Booking B gets the row and checks the quota
  5. Booking B finds no quota left and is refused

The lock makes the check and the debit one step. Booking B cannot check until booking A has finished, so it reads the quota as it really is.

When input becomes part of the command

Kael's second bug is in the booking search. The endpoint takes a filter string from the user. It joins that string onto a PostgreSQL query and sends the result to the database.

That is an injection vulnerability. Injection arises whenever untrusted data is concatenated into a command or query that a parser will read. The parser cannot tell which characters the developer wrote and which the user sent. A single quote or a semicolon in the filter can change the logic of the query. This form is called SQL injection.

The fix is the parameterized query, also called a prepared statement. The database compiles the SQL syntax tree first. Then it binds the user's parameters as plain values. The shape of the query is fixed before any input arrives, so no character in the input can alter it.

Input validation helps, and so does a web application firewall (WAF). Both are worth having as extra layers. Neither is the root fix, because the query is still built by joining strings. Vera replaces the concatenation with a prepared statement. She also adds a static application security testing (SAST) rule that blocks unparameterized SQL in continuous integration (CI).

Concatenated string

  • Input is joined onto the query text
  • The parser reads code and input together
  • A quote or semicolon can alter the logic

Parameterized query

  • The syntax tree is compiled first
  • Input is bound afterward as a value
  • Input stays data, whatever it contains
Two ways to build the search query. Compare the last point in each column. Only one of them lets input change what the query does.

YOUR CALL

Forty minutes before the auditors test search

The auditors will test the booking search endpoint at 15:00. It is 14:20. Kael asks you which fix to ship.

  1. Kael has three patches ready. Which one do you ask him to ship?

  2. The fix is in. Sloane asks how Meridian knows this bug will not come back in another service.

Show the strong calls
  1. Rewrite the query as a prepared statement with a bound parameter. The auditor sends a filter full of quotes and semicolons. The search returns no rows and nothing else happens. The finding is closed at its cause.
  2. Add a pipeline rule that blocks any query built without parameters. Two days later the rule stops a merge in another service. The developer fixes it in ten minutes. Sloane files the blocked build as evidence.

The rest of the list follows the same pattern

The race and the injection share a pattern. Each has one root cause, and the lasting fix sits at that cause. The other classic flaws follow it too.

Cross-site scripting (XSS) happens when untrusted data is rendered in the browser's page without escaping. The fix is context-aware output encoding, which encodes data for the exact place it lands in the page. A Content Security Policy (CSP) backs that up.

Cross-site request forgery (CSRF) uses the browser's own habit. The browser attaches session cookies automatically, even to a state-changing request that came from another origin. The fix is an anti-CSRF token on each such request, along with the SameSite cookie attribute. Server-side request forgery (SSRF) sits on the same list. As the name says, the forged request comes from the server instead of the browser.

A buffer overflow is a memory flaw. A program writes past the bounds of the memory it allocated. The extra bytes overwrite adjacent pointers on the stack or the heap. Memory-safe languages and bounds checking remove the cause. Two memory protections add to them, and the table names both.

Classic flaws, where each starts, and the fix at that point
FlawRoot causeRoot fix
SQL or command injectionUntrusted input concatenated into an interpreter's command stringParameterized queries (prepared statements) and strict APIs
Cross-site scripting (XSS)Untrusted data rendered in the browser DOM without escapingContext-aware output encoding and a Content Security Policy (CSP)
Cross-site request forgery (CSRF)The browser automatically attaches ambient session cookies to cross-origin, state-changing requestsAnti-CSRF cryptographic tokens and SameSite cookie attributes
TOCTOU race conditionState changes between a separate authorization check and the use of the resourceAtomic check-and-use operations, with mutex or transactional row locks
Buffer overflowWriting past allocated memory bounds, overwriting adjacent stack or heap pointersMemory-safe languages, bounds checking, address space layout randomization (ASLR), and data execution prevention (DEP) or no-execute (NX) bits

SORT IT OUT

The auditors read out four findings from older Meridian services. Which classic flaw is each one describing?

  1. A crew note typed by one user is shown to other users in the page exactly as typed, with no escaping.

  2. A page on another site submits a cancel-booking request, and the browser adds the user's session cookie.

  3. A telemetry parser copies a long station name into a short block of memory and keeps writing.

  4. A report tool joins the tenant name a user typed onto the text of a database query.

Show the answers
  1. Cross-site scripting. Untrusted data rendered in the browser without escaping is XSS. Encode output for its context and add a CSP.
  2. Cross-site request forgery. The browser attached the cookie on its own. Anti-CSRF tokens and SameSite cookies are the fix.
  3. Buffer overflow. Writing past allocated bounds overwrites adjacent pointers. Memory-safe languages and bounds checking prevent it.
  4. Injection. Untrusted input concatenated into a query is injection. Use a parameterized query.

CHECK YOUR JUDGMENT

After the review, you find the same pattern in Meridian's antenna-time scheduler. It checks that a tenant has minutes left, then deducts them in a separate step. Two requests sent 3 milliseconds apart both pass the check. Which change removes the root cause?

CASE FILE · CASE 11 · ENTRY 9 OF 9

Answer the closing question correctly to add this entry to your case file.

Check and debit, one step

The fuel check and debit now run as one locked transaction, the search query is parameterized, and the readiness review is clean: fix the cause, then prove it.

Open the case file ↗

Review sheet: exam notes and quick reference

Put your learning to work ↗

Find your next idea.

Tip: press / to open search. Escape closes this window.