Skip to main content0xAdham

Command Palette

Search for a command to run...

OliCyber — Dependency Hell: Racing SQLite Session Store

Written by
Avatar of 0xAdham
0xAdham
Published on
--
OliCyber — Dependency Hell: Racing SQLite Session Store

Event: OliCyber Category: Web Vulnerability: TOCTOU in SQLite-backed session management


The Vulnerable Logic

The app is a key-escalation shop: buy red_keygreen_keyblue_key → flag. Each purchase costs 10 coins. You start with 20. You need 40. The math is brutal by design.

The /buy endpoint looks safe:

Sequential. Logical. Fundamentally broken under concurrency.


Why It Breaks

The session store is SQLite via connect-sqlite3. SQLite handles concurrency with file-level locking, but express-session defaults to resave: false. The critical gap: session reads and writes are not atomic.

When two requests hit simultaneously:

  1. Both read balance: 20
  2. Both pass the check
  3. Both deduct 10
  4. Both write back — result is balance: 10 instead of balance: 0

You spent 10 coins, got two purchases worth of keys.


The Exploit Architecture

The Healer Swarm

40 threads hammering GET /:

Why? The initialization middleware:

Every time a session is reloaded from SQLite in a race window, if the read is corrupted or the session row is locked, the middleware may trigger re-initialization — resetting balance back to 20. The healers keep session state in constant flux, maximizing collision probability.

The Purchase Loop

The exploit treats "Not enough balance" as a transient lock collision, not a hard failure. When balance runs out, pause — let the healers corrupt the session back into a usable state — retry.


The Chain

The economics don't matter when the ledger is lying.


The Fix

Minimum viable patch — still not truly atomic:

Real fix: database-level transaction wrapping the entire check-deduct-respond flow, or move balance to Redis with atomic DECR operations.


Takeaway

SQLite was never meant for high-concurrency session storage. express-session's read-modify-write pattern assumes the store is atomic or single-threaded. Violate that assumption with 40 concurrent threads and the abstraction leaks catastrophically.

The "safe" sequential code becomes theater. The real logic is in the timing — the microseconds between SQLite's BEGIN and COMMIT.

Exploit source: github.com/0xAdham/olicyber


0xAdham

Edit on GitHub
Last updated: --