Web server logs are the front-line record of every request hitting your web application. Every SQL injection XSS
Think of web server logs like a guest book at your front door, except this guest book records exactly what everyone asked for - including the guy who tried every password or asked for /admin a thousand times. The information is there; you just need to know how to read it.
Two Types of Logs
Log Locations and Formats
1Web Server Log Locations:2 3APACHE4├── Access Log: /var/log/apache2/access.log (Debian/Ubuntu)5│ /var/log/httpd/access_log (RHEL/CentOS)6├── Error Log: /var/log/apache2/error.log7├── Virtual Host logs: /var/log/apache2/mysite-access.log8└── Config: /etc/apache2/apache2.conf9 10NGINX11├── Access Log: /var/log/nginx/access.log12├── Error Log: /var/log/nginx/error.log13├── Virtual Host logs: /var/log/nginx/mysite-access.log14└── Config: /etc/nginx/nginx.conf15 16IIS (Windows)17├── Access Log: C:\inetpub\logs\LogFiles\W3SVC1\18├── Format: u_ex<date>.log19└── Config: IIS Manager → Logging20 21Common Log Format (CLF):22─────────────────────────────────────────────────────────────────23192.168.1.50 - admin [15/Jul/2024:14:22:01 +0000] 606070;">#a5d6ff;">"GET /page HTTP/1.1" 200 123424 25Fields:26├── IP Address: 192.168.1.5027├── Identity: - (usually blank)28├── Username: admin (or - if not authenticated)29├── Timestamp: [15/Jul/2024:14:22:01 +0000]30├── Request: 606070;">#a5d6ff;">"GET /page HTTP/1.1"31├── Status Code: 20032└── Response Size: 1234 bytes33 34Combined Log Format (Adds referer and user-agent):35─────────────────────────────────────────────────────────────────36192.168.1.50 - - [15/Jul/2024:14:22:01 +0000] 606070;">#a5d6ff;">"GET /page HTTP/1.1" 200 1234 "http://example.com" "Mozilla/5.0..."Understanding Status Codes
1HTTP Status Codes for Security Analysis:2 32XX - SUCCESS4├── 200 OK │ Request succeeded5├── 201 Created │ Resource created (POST success)6└── 204 No Content │ Success but no body7 83XX - REDIRECTION9├── 301 Permanent │ Resource moved10├── 302 Found │ Temporary redirect11└── 304 Not Modified │ Cached version OK12 134XX - CLIENT ERRORS (Attacker Activity!)14├── 400 Bad Request │ Malformed request (malicious input?)15├── 401 Unauthorized│ Auth required (login attempt)16├── 403 Forbidden │ Access denied (trying protected resources)17├── 404 Not Found │ Resource doesn't exist (enumeration?)18├── 405 Not Allowed │ Wrong HTTP method19└── 429 Too Many Req│ Rate limited (brute force detected)20 215XX - SERVER ERRORS (Possible Exploitation!)22├── 500 Internal │ Server error (crash from exploit?)23├── 502 Bad Gateway │ Backend issue24├── 503 Unavailable │ Server overloaded (DoS?)25└── 504 Gateway TO │ Backend timeout26 27Security Analysis by Status Code:28─────────────────────────────────────────────────────────────────29Many 401s → Brute force authentication30Many 403s → Trying to access restricted resources31Many 404s → Directory/file enumeration32Many 400s → Malformed input, possibly SQLi/XSS testing33500 after 200s → Attacker found something exploitableDetecting Web Attacks
1606070;"># Detecting Common Web Attacks in Logs2 3606070;"># 1. SQL INJECTION ATTEMPTS4grep -iE 606070;">#a5d6ff;">"union.*select|'.*or.*'|'.*and.*'|sleep\(|benchmark\(" access.log5grep -iE 606070;">#a5d6ff;">"1=1|1'='1|' or ''='" access.log6grep -iE 606070;">#a5d6ff;">"information_schema|concat\(|load_file" access.log7 8606070;"># Example malicious request:9606070;"># GET /product?id=1' OR '1'='1 HTTP/1.110606070;"># GET /search?q='; SELECT * FROM users--11 12606070;"># 2. XSS ATTEMPTS13grep -iE 606070;">#a5d6ff;">"<script|javascript:|onerror=|onload=" access.log14grep -iE 606070;">#a5d6ff;">"alert\(|document\.cookie|eval\(" access.log15 16606070;"># Example malicious request:17606070;"># GET /search?q=<script>alert('XSS')</script>18606070;"># GET /profile?name="><img src=x onerror=alert(1)>19 20606070;"># 3. PATH TRAVERSAL21grep -E 606070;">#a5d6ff;">"\.\./|\.\.%2f|%2e%2e" access.log22grep -E 606070;">#a5d6ff;">"/etc/passwd|/etc/shadow|win\.ini|boot\.ini" access.log23 24606070;"># Example malicious request:25606070;"># GET /files?file=../../../etc/passwd26606070;"># GET /download?path=....//....//etc/passwd27 28606070;"># 4. COMMAND INJECTION29grep -iE 606070;">#a5d6ff;">"\||;|\$\(|\x60|cmd=|exec=" access.log30grep -iE 606070;">#a5d6ff;">"\bcat\b|\bls\b|\bid\b|\bwhoami\b" access.log31 32606070;"># Example malicious request:33606070;"># GET /ping?host=127.0.0.1;cat /etc/passwd34 35606070;"># 5. DIRECTORY ENUMERATION36grep -E 606070;">#a5d6ff;">"\.(bak|old|swp|~|backup|sql|config)" access.log37grep -iE 606070;">#a5d6ff;">"/admin|/wp-admin|/phpmyadmin|/manager" access.log38grep 606070;">#a5d6ff;">" 404 " access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head39 40606070;"># 6. SCANNER/BOT DETECTION41grep -iE 606070;">#a5d6ff;">"nikto|sqlmap|nmap|dirbuster|gobuster|burp" access.log42grep -iE 606070;">#a5d6ff;">"nessus|openvas|acunetix|netsparker" access.logURL Encoding
User-Agent Analysis
1606070;"># User-Agent Analysis2 3606070;"># List all unique user-agents4awk -F606070;">#a5d6ff;">'"' '{print $6}' access.log | sort | uniq -c | sort -rn5 6606070;"># Find known scanners by User-Agent7grep -iE 606070;">#a5d6ff;">"nikto|sqlmap|nmap|masscan|gobuster|dirbuster" access.log8grep -iE 606070;">#a5d6ff;">"curl|wget|python|perl|ruby" access.log9 10606070;"># Suspicious User-Agents:11─────────────────────────────────────────────────────────────────12sqlmap/1.0-dev │ SQL injection scanner13Nikto/2.1.6 │ Web vulnerability scanner14DirBuster-1.0 │ Directory enumeration15Mozilla/4.0 (compatible;)│ Old/fake, often bots16- │ Empty user-agent = script17 18606070;"># Search attacks ignore19606070;"># Many scanners set specific user-agents20grep -v 606070;">#a5d6ff;">"Mozilla/5.0" access.log | grep -v "Googlebot" | head -5021 22606070;"># Find requests with no user-agent23awk -F606070;">#a5d6ff;">'"' '$6 == "-" || $6 == "" {print $0}' access.log24 25606070;"># Check if attacker is spoofing26606070;"># Same IP, different user-agents = likely scanner27awk -F606070;">#a5d6ff;">'"' '{print $1, $6}' access.log | \28 sort | uniq -c | awk 606070;">#a5d6ff;">'$1 > 1 {print}'Detecting Reconnaissance
1606070;"># Reconnaissance Detection2 3606070;"># 1. DIRECTORY BRUTE FORCE4606070;"># Many 404s from same IP = enumeration5awk 606070;">#a5d6ff;">'$9 == 404 {print $1}' access.log | \6 sort | uniq -c | sort -rn | head -107 8606070;"># 2. WHAT WERE THEY LOOKING FOR?9606070;"># List 404 URLs by count10awk 606070;">#a5d6ff;">'$9 == 404 {print $7}' access.log | \11 sort | uniq -c | sort -rn | head -2012 13606070;"># Common targets:14606070;"># /admin, /wp-admin, /phpmyadmin, /manager15606070;"># /.git, /.env, /backup, /config16606070;"># /api/v1, /swagger, /graphql17 18606070;"># 3. SPIDER/CRAWLER BEHAVIOR19606070;"># Too many requests per second = automated20awk 606070;">#a5d6ff;">'{print $1, $4}' access.log | \21 awk -F606070;">#a5d6ff;">'[:[]' '{print $1, $3":"$4}' | \22 sort | uniq -c | sort -rn | head23 24606070;"># 4. LOOKING FOR SENSITIVE FILES25grep -E 606070;">#a5d6ff;">"\.(env|config|ini|sql|bak|log|git)" access.log26grep -E 606070;">#a5d6ff;">"robots\.txt|sitemap\.xml|crossdomain\.xml" access.log27 28606070;"># 5. API ENUMERATION29grep -E 606070;">#a5d6ff;">"/api/|/v[0-9]/" access.log | \30 awk 606070;">#a5d6ff;">'{print $7}' | sort | uniq -c | sort -rn31 32606070;"># 6. PARAMETER FUZZING33606070;"># Same URL, many different parameters34grep 606070;">#a5d6ff;">"login" access.log | \35 awk -F606070;">#a5d6ff;">'?' '{print $1}' | sort | uniq -c | sort -rnError Log Analysis
1606070;"># Error Log Analysis2 3606070;"># Apache error log format:4606070;"># [Mon Jul 15 14:22:01.123456 2024] [error] [pid 1234] [client 192.168.1.50:54321]5606070;"># PHP Fatal error: ...6 7606070;"># View recent errors8tail -100 /var/log/apache2/error.log9tail -100 /var/log/nginx/error.log10 11606070;"># PHP errors (often indicate exploitation)12grep 606070;">#a5d6ff;">"PHP" error.log | tail -5013 14606070;"># Critical: SQL errors in logs15grep -iE 606070;">#a5d6ff;">"mysql|sql|syntax error|query" error.log16606070;"># Attackers causing SQL errors while testing payloads17 18606070;"># File permission errors (access attempt)19grep -iE 606070;">#a5d6ff;">"permission denied|access denied" error.log20 21606070;"># File not found (but with context)22grep 606070;">#a5d6ff;">"File does not exist" error.log23 24606070;"># ModSecurity blocks (if installed)25grep 606070;">#a5d6ff;">"ModSecurity" error.log26 27606070;"># Pattern: Normal error vs Attack indicator28606070;"># Normal: "File does not exist: /var/www/html/favicon.ico"29606070;"># Attack: "File does not exist: /var/www/html/../../etc/passwd"30 31606070;"># Critical errors timeline32grep 606070;">#a5d6ff;">"\[crit\]\|\[error\]" error.log | \33 awk 606070;">#a5d6ff;">'{print $1,$2,$3}' | sort | uniq -cCorrelate Access + Error Logs
Log Analysis Tools
1606070;"># Tools for Web Log Analysis2 3606070;"># 1. GOACCESS - Real-time visual analyzer4goaccess access.log -o report.html --log-format=COMBINED5606070;"># Generates beautiful HTML report6 7606070;"># 2. AWK - Flexible log parsing8606070;"># Requests per IP9awk 606070;">#a5d6ff;">'{print $1}' access.log | sort | uniq -c | sort -rn | head10 11606070;"># Requests per hour12awk 606070;">#a5d6ff;">'{print $4}' access.log | cut -d: -f1-2 | sort | uniq -c13 14606070;"># 3. CUT + SORT + UNIQ - Quick counts15cut -d606070;">#a5d6ff;">' ' -f1 access.log | sort | uniq -c | sort -rn | head16 17606070;"># 4. GREP + AWK combo18606070;"># Find all POST requests and their targets19grep 606070;">#a5d6ff;">"POST" access.log | awk '{print $7}' | sort | uniq -c20 21606070;"># 5. JQ for JSON logs (Nginx JSON format)22cat access.json | jq 606070;">#a5d6ff;">'.request_uri' | sort | uniq -c23 24606070;"># 6. LOGREP - Regex-based analysis25606070;"># Great for pattern matching across large log files26 27606070;"># 7. CUSTOM SCRIPT28606070;">#!/bin/bash29LOG=$130echo 606070;">#a5d6ff;">"=== Top 10 IPs ==="31awk 606070;">#a5d6ff;">'{print $1}' $LOG | sort | uniq -c | sort -rn | head32echo -e 606070;">#a5d6ff;">"\n=== Top 10 404s ==="33awk 606070;">#a5d6ff;">'$9==404 {print $7}' $LOG | sort | uniq -c | sort -rn | head34echo -e 606070;">#a5d6ff;">"\n=== Potential SQLi ==="35grep -c 606070;">#a5d6ff;">"union\|select\|'--" $LOG36echo -e 606070;">#a5d6ff;">"\n=== Potential XSS ==="37grep -c 606070;">#a5d6ff;">"<script\|onerror=\|onload=" $LOGInvestigation Example
1606070;"># Investigation: Web Application Compromise2 3606070;"># Scenario: You notice suspicious behavior from a web server4 5606070;"># STEP 1: Get the big picture6wc -l access.log 606070;"># How many requests?7awk 606070;">#a5d6ff;">'{print $1}' access.log | sort -u | wc -l # Unique IPs8 9606070;"># STEP 2: Find top talkers10awk 606070;">#a5d6ff;">'{print $1}' access.log | sort | uniq -c | sort -rn | head -1011606070;"># Output:12606070;"># 15234 192.168.1.100 <- Suspicious volume!13606070;"># 523 10.0.0.5014606070;"># 412 192.168.1.515 16606070;"># STEP 3: Investigate suspicious IP17grep 606070;">#a5d6ff;">"192.168.1.100" access.log > suspect.log18wc -l suspect.log 606070;"># 15234 requests!19 20606070;"># STEP 4: Timeline analysis21head -5 suspect.log 606070;"># First request22tail -5 suspect.log 606070;"># Last request23606070;"># Determine attack window24 25606070;"># STEP 5: What were they requesting?26awk 606070;">#a5d6ff;">'{print $7}' suspect.log | sort | uniq -c | sort -rn | head -2027 28606070;"># STEP 6: Look for attack payloads29grep -iE 606070;">#a5d6ff;">"union|select|script|eval|exec" suspect.log30606070;"># Found SQLi attempts!31 32606070;"># STEP 7: Did they succeed?33awk 606070;">#a5d6ff;">'{print $9}' suspect.log | sort | uniq -c34606070;"># Output:35606070;"># 14500 404 <- Enumeration phase36606070;"># 700 200 <- Found valid pages37606070;"># 34 500 <- Errors (SQLi working?)38 39606070;"># STEP 8: What 200s and 500s hit?40awk 606070;">#a5d6ff;">'$9 == 200 {print $7}' suspect.log | sort | uniq -c41awk 606070;">#a5d6ff;">'$9 == 500 {print $7}' suspect.log | sort | uniq -c42606070;"># Found: /search?q= with SQLi payloads returning 500!43 44606070;"># STEP 9: Check if data exfiltrated45grep 606070;">#a5d6ff;">"192.168.1.100" access.log | grep POST46606070;"># Any POST requests with large response sizes?47 48606070;"># STEP 10: Document findings49606070;"># IP: 192.168.1.10050606070;"># Time: 14:00-14:4551606070;"># Attack: SQLi on /search endpoint52606070;"># Evidence: 500 errors followed by 200s53606070;"># Impact: Possible data extractionWeb Log Analysis Methodology
Web Attack Investigation
Knowledge Check
What does a large number of 404 responses from a single IP typically indicate?
Challenges
Find the SQLi Attack
ChallengeWrite a grep command to find all requests in access.log that contain potential SQL injection payloads (UNION, SELECT, OR 1=1, etc.), showing the full request line.
Need a hint? (4 available)
Key Takeaways
- Access logs record all requests; error logs show what failed
- Status codes tell the story: 404s = enumeration, 500s = exploitation
- SQLi payloads contain UNION, SELECT, OR 1=1, and SQL syntax
- XSS payloads contain <script>, onerror=, javascript:
- User-Agents reveal scanners: sqlmap, nikto, gobuster
- Correlate access and error logs for complete picture