Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Finding an attack in a log

After this lesson: I can find an attack in a log.

50 min lesson6 commandsIncident response and forensics lab: investigate a compromised server in your browser

A quiet server with a loud log

A company's web server looks fine. The site loads, nobody has complained. But last night something got in.

The server wrote down every visit and every sign-in attempt. The story of the attack is in those logs, if you know how to read them.

The one rule

Warning

This lab is a copy of one server's logs, for you alone, with no way out to anything else. Read the evidence here only. Looking at real logs from a system that is not yours, without permission, is a crime under the Computer Misuse Act 1990.

By the end of this lesson

Note

I can find an attack in a log.

Key words

WordWhat it meansExample
Loga file where a program writes down what it did, line by line
Timelineevents put in the order they happened
Brute forcetrying many passwords until one works
Status codethe number a web server answers with404
Noiselog lines that look busy but mean nothing

The cells students fill in are left blank in this preview.

Two logs tell two parts of the story

This server keeps two logs. The auth log records every sign-in over SSH: who tried, from where, and whether it worked.

The access log records every page a visitor asked for, and the status code the server answered. Together they show both the knocking on the door and the walking around inside.

Before you start

Note

Press Start the lab on the bar at the top. Your case is a folder called case in your home folder. The files are read-only, so the evidence stays as it was. Each Run only reads them.

What is in the case?

Command
ls case

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

How big is the auth log?

Command
wc -l case/var/log/auth.log

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

The first line of the access log

Command
head -n 1 case/var/log/nginx/access.log

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Worked example: one access log line

PartValueMeans
Address192.0.2.77Who asked
Time14/Oct/2026:00:01:49When
RequestGET /loginThe page they wanted
Status200Here it is

The cells students fill in are left blank in this preview.

Counting turns thousands of lines into facts

You will not read 1366 lines. You count them. grep -c counts the lines that hold a word.

A failed sign-in says Failed password. A successful one says Accepted. A quick count of each tells you at once whether somebody was guessing.

Count the failed sign-ins

Command
grep -c "Failed password" case/var/log/auth.log

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Count the successful sign-ins

Command
grep -c "Accepted" case/var/log/auth.log

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Count the web log by status code

Command
awk '{print $9}' case/var/log/nginx/access.log | sort | uniq -c

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

What the counts tell you

There are 333 failed sign-ins and 6 accepted. That many failures is a sign of guessing, but it does not say who got in.

The status codes show 259 of 200, then 166 of 404, which is a page not found. A burst of 404s from one address is often somebody looking for a way in.

A common mistake

Warning

Many people blame the address with the most failed sign-ins. That address is often just noise: a bot that never gets in. The real intruder may have only a few failures, then one Accepted. Look for the success, not the loudest failure.

Count failures by address

Code: python
# A few lines like the auth log. Each is address then the word fail or ok.
lines = ["198.51.100.23 fail", "198.51.100.23 fail", "203.0.113.5 ok", "198.51.100.23 fail"]

counts = {}
for line in lines:
    parts = line.split(" ")
    address = parts[0]
    if parts[1] == "fail":
        if address in counts:
            counts[address] = counts[address] + 1
        else:
            counts[address] = 1
print(counts)

Going further

Count the pages that gave a 404 and sort them: awk '$9==404 {print $7}' case/var/log/nginx/access.log | sort | uniq -c | sort -rn | head.

This counts pages, not addresses, so it does not answer the case. Write down the three most-wanted missing pages and say what an attacker might have hoped to find at each.

About this lab and its source: Incident response and forensics lab: investigate a compromised server in your browser

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.