🌐Networking
Advanced · Lesson 8/8

Network Security Basics — Attacks and the Defences That Stop Them

43 min 1 quiz Graded labs: 3

What you'll learn

  • The vocabulary of security: assets, threats, vulnerabilities, risk and the CIA triad
  • How spoofing works at each layer (IP, MAC, ARP, DNS) and what stops it
  • How a man-in-the-middle gets into the path, and why encryption with proper authentication defeats it
  • The kinds of denial of service, and defences such as SYN cookies, rate limiting and filtering
  • How port scans look in your logs and how to detect them
  • The principles every security design is built on: least privilege, segmentation and defence in depth
  • You will build three defensive tools: an ARP spoofing monitor, a token-bucket rate limiter and a port-scan detector

⚠️ The line that matters: this lesson explains attacks so you can recognise and stop them. Scanning, spoofing or flooding systems you do not own, or do not have written permission to test, is a crime in most countries, even "just to see". Practise only in your own lab. Every log and address in this lesson is made up.

The castle 🏰

A medieval castle did not rely on one wall. It had a moat, an outer wall, a gate with guards who checked everyone, an inner wall, and a keep where the treasure was. Soldiers could only enter the rooms their duty required. If attackers crossed the moat, the wall stopped them; if they climbed the wall, the guards saw them. That is defence in depth, and it is still the best mental model for network security: no single control is perfect, so you stack several, each one covering the others' gaps.

The vocabulary

  • An asset is something worth protecting: customer data, a payment service, the availability of a website.
  • A threat is something that could harm it: a criminal group, a careless insider, a flood.
  • A vulnerability is a weakness a threat can use: an unpatched service, a weak Wi-Fi password, a flat network.
  • Risk combines how likely harm is with how bad it would be. Security work is about reducing risk to an acceptable level, never to zero.

The CIA triad names what you protect: confidentiality (only the right people can read it), integrity (nobody can change it unnoticed) and availability (it works when needed). Each attack in this lesson aims at one or more of them: eavesdropping at confidentiality, spoofing and man-in-the-middle at integrity (and usually confidentiality), denial of service at availability.

Spoofing: lying about who you are

Spoofing means forging an identity field so a packet seems to come from someone else. It exists at every layer because the basic protocols trust what they are told.

  • IP spoofing. Anyone can write any source address into a packet they build. Replies go to the forged address, so the attacker never sees them; that is why spoofing is useful for one-way attacks (floods and reflection, below) and hard against TCP from outside the path, where the attacker would have to guess the server's unpredictable initial sequence number (RFC 6528). The defence is ingress and egress filtering at network edges (BCP 38, RFC 2827): an ISP drops customer packets whose source is not the customer's prefix, and a network drops incoming packets that claim to come from its own addresses. Unicast reverse path forwarding (uRPF, BCP 84) automates the check with the routing table.
  • MAC spoofing. Changing a network card's MAC address is one command, so "only allow these MAC addresses" is not authentication. Use 802.1X port authentication instead.
  • ARP spoofing (ARP poisoning). Because ARP has no authentication, a device on the LAN can send replies saying "the gateway 192.168.1.1 is at my MAC". Victims update their caches and send their Internet traffic to the attacker, who forwards it on: a man-in-the-middle. Defences: switches with Dynamic ARP Inspection (which checks ARP against the DHCP snooping table), static entries for critical hosts, 802.1X, monitoring for changed mappings, and above all encryption, so intercepted traffic is useless.
  • DNS spoofing (cache poisoning). An attacker races the real answer with forged ones, hoping a resolver caches the lie. Since the attacks published in 2008, resolvers randomise the query ID and source port, which makes blind guessing far harder; DNSSEC validation lets a resolver reject forged records outright, and DNS over TLS or HTTPS protects the path between you and your resolver.

Inbound, a packet from outside cannot honestly carry one of our own addresses or a private one, so both are dropped. Outbound, filtering stops a compromised machine inside from sending spoofed packets: if every network did this, reflection attacks would be nearly impossible. Add 100.64.0.0/10 (shared address space, RFC 6598) to the special list and test 100.64.1.1.

# BCP 38 (RFC 2827) at an edge router: check that source addresses make sense.
import ipaddress

OUR_PREFIXES = [ipaddress.ip_network("203.0.113.0/24")]  # what our ISP assigned to us
NEVER_FROM_INTERNET = [ipaddress.ip_network(n) for n in (
    "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16",  # private (RFC 1918)
    "127.0.0.0/8", "0.0.0.0/8", "169.254.0.0/16",      # loopback, "this network", link-local
)]


def inbound(src):
    a = ipaddress.ip_address(src)
    if any(a in n for n in OUR_PREFIXES):
        return "drop: claims to be one of our own addresses"
    if any(a in n for n in NEVER_FROM_INTERNET):
        return "drop: private or special address"
    return "accept"


def outbound(src):
    a = ipaddress.ip_address(src)
    return "accept" if any(a in n for n in OUR_PREFIXES) else "drop: not our address (spoofed?)"


for src in ("198.51.100.23", "203.0.113.5", "10.1.2.3", "127.0.0.1"):
    print("arriving from the Internet, source %-14s -> %s" % (src, inbound(src)))
for src in ("203.0.113.40", "192.0.2.99"):
    print("leaving for the Internet,  source %-14s -> %s" % (src, outbound(src)))

Lab exercise 1

JavaScript (Node.js)Graded in Code Lab

Write an ARP monitor that watches ARP replies on a LAN and reports suspicious changes, in the spirit of the classic arpwatch tool.

Input: the first line is gateway <ip>. Every following line is an observed reply <time> <ip> is-at <mac>. Treat MAC addresses as lower case.

Rules, for each reply:

  • An IP never seen before: <time> new station <ip> <mac>.
  • The same MAC as the IP has now: print nothing.
  • A different MAC: if it is the MAC the IP had before its current one, the address is bouncing between two machines: <time> flip flop <ip> <current> -> <new>; otherwise <time> changed <ip> <current> -> <new>. Then remember the new MAC (and the old one as the previous).
  • If that IP is the gateway, also print <time> ALERT gateway <ip> moved to <new>, followed by (also used by <ip>,<ip>...) when other IPs currently map to that same MAC, listed in the order they were first seen.

Example

Input:
gateway 192.168.1.1
100 192.168.1.1 is-at 02:00:00:00:00:01
102 192.168.1.66 is-at 02:00:00:00:00:66
130 192.168.1.1 is-at 02:00:00:00:00:01
200 192.168.1.1 is-at 02:00:00:00:00:66

Output:
100 new station 192.168.1.1 02:00:00:00:00:01
102 new station 192.168.1.66 02:00:00:00:00:66
200 changed 192.168.1.1 02:00:00:00:00:01 -> 02:00:00:00:00:66
200 ALERT gateway 192.168.1.1 moved to 02:00:00:00:00:66 (also used by 192.168.1.66)

(The machine at .66 now claims to be the gateway as well: a classic sign of ARP poisoning.)

Visible tests (5)
  • The worked example: a host takes over the gateway's address
  • A host address that flip-flops between two machines
  • The gateway itself flip-flops: an alert every time
  • Capital letters do not count as a change; several IPs share one MAC
  • No replies observed: nothing is printed
Example run
Input
gateway 192.168.1.1
100 192.168.1.1 is-at 02:00:00:00:00:01
101 192.168.1.20 is-at 02:00:00:00:00:20
102 192.168.1.66 is-at 02:00:00:00:00:66
130 192.168.1.1 is-at 02:00:00:00:00:01
131 192.168.1.20 is-at 02:00:00:00:00:20
200 192.168.1.1 is-at 02:00:00:00:00:66
Expected output
100 new station 192.168.1.1 02:00:00:00:00:01
101 new station 192.168.1.20 02:00:00:00:00:20
102 new station 192.168.1.66 02:00:00:00:00:66
200 changed 192.168.1.1 02:00:00:00:00:01 -> 02:00:00:00:00:66
200 ALERT gateway 192.168.1.1 moved to 02:00:00:00:00:66 (also used by 192.168.1.66)
Starter code
const lines = require("fs").readFileSync(0, "utf8").split("\n").map((l) => l.trim()).filter((l) => l !== "");

if (lines.length > 0) {
  const gateway = lines[0].split(/\s+/)[1];
  const current = new Map();  // ip -> MAC it has now
  const previous = new Map(); // ip -> MAC it had before that

  for (const line of lines.slice(1)) {
    const [time, ip, , rawMac] = line.split(/\s+/); // <time> <ip> is-at <mac>
    const mac = rawMac.toLowerCase();
    const now = current.get(ip);
    if (now === undefined) {

More lines in Code Lab: 11

Try in Code Lab

Hints and instant feedback are in Code Lab. Optional practice: it does not block finishing the lesson.

JavaScript and Python also run in your browser without an account; other languages use the secure server runner and need a free account.

Man-in-the-middle: getting into the path

A man-in-the-middle (MITM) attacker sits between two parties, reading and possibly changing what they say, while each side believes it talks directly to the other. Ways into the path include ARP spoofing on a LAN, a rogue Wi-Fi access point with a familiar name (an "evil twin"), a rogue DHCP server that hands out the attacker as gateway or DNS server, a compromised home router, and at Internet scale a BGP hijack of someone's prefix.

You cannot always keep attackers out of the path, so the defence is to make the path not matter: end-to-end encryption with authentication.

  • TLS with certificate validation. An attacker in the middle can relay bytes but cannot produce a valid certificate for the real name. That only works if clients refuse invalid certificates: never click through a certificate warning on a network you do not trust.
  • HSTS (RFC 6797). A site can tell browsers "only ever use HTTPS with me", which defeats SSL stripping, where the attacker quietly downgrades the first plain-HTTP request.
  • SSH host keys. The first connection records the server's key in known_hosts; a later warning that the key changed may mean an interception.
  • VPNs and WPA3 or 802.1X protect the local hop on networks you do not control, and DHCP snooping on switches stops rogue DHCP servers.

The mindset to take away: assume the network is hostile and protect the data itself.

Denial of service: knocking it over

A denial-of-service (DoS) attack attacks availability. When it comes from many machines at once (usually a botnet of hijacked devices) it is a distributed DoS (DDoS). There are three broad kinds:

  • Volumetric: more traffic than the links can carry. Reflection and amplification make this cheap: the attacker sends small UDP requests to open servers (DNS, NTP, memcached and others) with the victim's address spoofed as the source, and the servers send much larger replies to the victim. CISA's alert TA14-017A lists the amplification factors per protocol. Defences sit upstream: ISP filtering, scrubbing services, CDNs and anycast networks that spread the flood over many sites, plus BCP 38 everywhere and closing open resolvers.
  • Protocol / state exhaustion: using up a table rather than bandwidth. A SYN flood sends many SYNs (often spoofed) and never completes the handshake, so the server's queue of half-open connections fills and real clients are refused. SYN cookies (RFC 4987) answer by encoding the connection details in the server's initial sequence number, so no state is stored until the client's final ACK proves it is real.
  • Application layer: expensive requests (search pages, logins) or deliberately slow ones that tie up workers. Defences include rate limiting per client, caching, timeouts and challenge pages.

Rate limiting is usually done with a token bucket: each client has a bucket that holds up to burst tokens and refills at rate tokens per second; every request spends one token, and a request that finds the bucket empty is dropped (or delayed). Short bursts are allowed, sustained floods are not.

Four forged SYNs per tick are enough to keep all eight slots busy, because each one is held for five ticks: after the first tick every real client is refused. With SYN cookies the server keeps no state for a SYN, so the flood has nothing to fill. Real attacks are bigger, but the shape is the same. Try a backlog of 32 and see that a bigger queue only delays the problem.

// A SYN flood against a server whose queue holds 8 half-open connections, each kept for 5 ticks.
function simulate(useCookies) {
  const BACKLOG = 8;
  const TIMEOUT = 5;
  let halfOpen = []; // the tick at which each half-open entry expires
  let served = 0;
  let refused = 0;
  for (let tick = 0; tick < 20; tick++) {
    halfOpen = halfOpen.filter(function (expires) { return expires > tick; });
    // The attacker sends 4 spoofed SYNs per tick; those handshakes never complete.
    for (let i = 0; i < 4; i++) {
      if (useCookies) continue; // with SYN cookies a SYN creates no state at all
      if (halfOpen.length < BACKLOG) halfOpen.push(tick + TIMEOUT);
    }
    // One real client per tick, who will complete the handshake if it gets a SYN-ACK.
    if (useCookies || halfOpen.length < BACKLOG) served++;
    else refused++;
  }
  return "served " + served + ", refused " + refused;
}

console.log("without SYN cookies: " + simulate(false));
console.log("with SYN cookies:    " + simulate(true));

Lab exercise 2

CGraded in Code Lab

Implement a per-source token-bucket rate limiter in C.

Input: the first line is rate <r> burst <b> (whole numbers: r tokens per second, at most b tokens in a bucket). Every following line is a request <time-ms> <source>, with times in milliseconds, never decreasing.

Rules

  • Each source has its own bucket, created full (b tokens) at the time of its first request.
  • Before deciding on a request, refill the bucket with r tokens per second for the time since its last refill, never above b. Partial tokens count: keep the amount in thousandths of a token (r thousandths per millisecond), so no floating point is needed.
  • If the bucket holds at least one whole token, spend it and ACCEPT; otherwise DROP (a dropped request spends nothing).

Output: one line per request, <time-ms> <source> <ACCEPT|DROP>, then one summary line per source in alphabetical (byte) order: <source>: <a> accepted, <d> dropped.

Example

Input:
rate 2 burst 3
0 192.0.2.7
0 192.0.2.7
0 192.0.2.7
0 192.0.2.7
500 192.0.2.7

Output:
0 192.0.2.7 ACCEPT
0 192.0.2.7 ACCEPT
0 192.0.2.7 ACCEPT
0 192.0.2.7 DROP
500 192.0.2.7 ACCEPT
192.0.2.7: 4 accepted, 1 dropped

(After three requests the bucket is empty; 500 ms at 2 tokens per second refills exactly one token.)

Visible tests (5)
  • The worked example, then too little time for another token
  • A flooding bot does not slow down a normal client
  • A long pause refills only up to the burst size
  • Partial tokens add up exactly (3 per second, burst 1)
  • No requests: nothing is printed
Example run
Input
rate 2 burst 3
0 192.0.2.7
0 192.0.2.7
0 192.0.2.7
0 192.0.2.7
0 192.0.2.7
500 192.0.2.7
600 192.0.2.7
Expected output
0 192.0.2.7 ACCEPT
0 192.0.2.7 ACCEPT
0 192.0.2.7 ACCEPT
0 192.0.2.7 DROP
0 192.0.2.7 DROP
500 192.0.2.7 ACCEPT
600 192.0.2.7 DROP
192.0.2.7: 4 accepted, 3 dropped
Starter code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_SOURCES 256

struct bucket {
    char name[64];
    long long millitokens; /* tokens x 1000, so refills never need floating point */
    long long last_ms;     /* time of the last refill */
    long accepted, dropped;
};

More lines in Code Lab: 39

Try in Code Lab

Hints and instant feedback are in Code Lab. Optional practice: it does not block finishing the lesson.

JavaScript and Python also run in your browser without an account; other languages use the secure server runner and need a free account.

Port scanning and how to see it

Before an attack comes reconnaissance: finding which hosts are up and which services they run. A port scan tries many ports and classifies them by the reply, using exactly what you learned in the TCP and troubleshooting lessons. A tool such as nmap reports:

  • open: a SYN-ACK came back, so a service is listening;
  • closed: a RST came back, so the host is up but nothing listens there;
  • filtered: no answer, or an ICMP unreachable, so a firewall is in the way.

Scanning someone else's network without permission can be illegal and often breaks the provider's terms of service. Scanning your own network regularly is good practice: it shows you what an attacker would see, and ss -tlnp on each server tells you which program is behind each open port.

To detect scans, look at connection logs from a firewall or an IDS (Snort, Suricata) or a network monitor (Zeek). A normal client talks to a few services; a scanner touches many distinct targets in a short time:

  • a vertical scan probes many ports on one host;
  • a horizontal scan (a sweep) probes one port, say 22 or 3389, on many hosts;
  • a mixed scan does both.

Counting distinct (host, port) pairs per source in a sliding time window catches most of this. It also has blind spots: a slow scan that sends one probe a minute stays under any short-window threshold, and some legitimate systems (monitoring servers, vulnerability scanners you run yourself) look exactly like scanners, so they need an allow list. Detection only helps if someone acts on it: block or rate-limit the source, check what it reached, and close ports that never needed to be open.

Lab exercise 3

PythonGraded in Code Lab

Write a port-scan detector for connection logs.

Input: the first line is window <w> threshold <n>. Every following line is a connection attempt <time> <source-ip> <destination-ip> <destination-port>, with whole-second times that never decrease.

Rules

  • For each source, keep the attempts of the last w seconds: at time t, an attempt made at time s still counts if t - s < w.
  • A target is a distinct (destination, port) pair: repeating the same one counts once.
  • The first time a source has at least n targets in its window, print one alert and never alert for that source again:
ALERT t=<time> src=<source> targets=<k> hosts=<h> type=<vertical|horizontal|mixed>

where hosts is the number of distinct destinations among the targets, vertical means one host, horizontal means several hosts but a single port, and mixed is anything else.

  • If no source is ever flagged, print no scans detected.

Example

Input:
window 10 threshold 5
0 192.0.2.50 203.0.113.10 443
1 198.51.100.7 203.0.113.10 21
1 198.51.100.7 203.0.113.10 22
2 198.51.100.7 203.0.113.10 23
3 198.51.100.7 203.0.113.10 25
3 198.51.100.7 203.0.113.10 80
5 192.0.2.50 203.0.113.10 443

Output:
ALERT t=3 src=198.51.100.7 targets=5 hosts=1 type=vertical
Visible tests (6)
  • The worked example: a vertical scan next to normal HTTPS traffic
  • A horizontal sweep of port 22
  • A slow scan stays under the window: a known blind spot
  • Repeated connections count once; a mixed scan is still caught
  • The window edge: 4 seconds old counts, 5 seconds old does not
  • An empty log: no scans detected
Example run
Input
window 10 threshold 5
0 192.0.2.50 203.0.113.10 443
1 198.51.100.7 203.0.113.10 21
1 198.51.100.7 203.0.113.10 22
2 198.51.100.7 203.0.113.10 23
2 192.0.2.50 203.0.113.10 443
3 198.51.100.7 203.0.113.10 25
3 198.51.100.7 203.0.113.10 80
4 198.51.100.7 203.0.113.10 110
5 192.0.2.50 203.0.113.10 443
Expected output
ALERT t=3 src=198.51.100.7 targets=5 hosts=1 type=vertical
Starter code
import sys

lines = [l.split() for l in sys.stdin.read().splitlines() if l.strip()]
window, threshold = int(lines[0][1]), int(lines[0][3])

history = {}     # source -> list of (time, host, port), oldest first
flagged = set()  # sources already reported
alerts = 0

for t, src, host, port in lines[1:]:
    now = int(t)
    events = history.setdefault(src, [])

More lines in Code Lab: 11

Try in Code Lab

Hints and instant feedback are in Code Lab. Optional practice: it does not block finishing the lesson.

JavaScript and Python also run in your browser without an account; other languages use the secure server runner and need a free account.

The principles behind every defence

Individual attacks change every year; these principles do not.

  • Least privilege. Every user, program and network flow gets only the access its job needs, and nothing more. A web server can reach the database port, not SSH on every machine. Administrators use separate accounts with multi-factor authentication, and management interfaces are reachable only from a management network or through a VPN.
  • Segmentation. Divide the network into zones (users, servers, guests, IoT, management, the DMZ) and allow only the flows between them that are needed. A compromised printer should not be able to talk to the finance server. Taken further, zero trust (NIST SP 800-207) stops granting trust because of network location at all: every request is authenticated and authorised, wherever it comes from.
  • Defence in depth. Layer independent controls so that one failure is not a disaster:
perimeter      firewall, DDoS protection, filtering (BCP 38)
network        segmentation, VLANs, 802.1X, WPA3, DHCP snooping, DAI
host           patching, host firewall, closed unused ports, endpoint protection
application    TLS everywhere, authentication, input validation, rate limiting
data           encryption at rest, backups you have tested
people         training, least privilege, multi-factor authentication
detection      logs, IDS, alerts that someone actually reads, an incident plan

Add secure defaults (deny unless allowed, services off unless needed), attack surface reduction (every open port and every installed service is something to defend) and monitoring with a response plan, because prevention eventually fails and the question becomes how fast you notice. With this vocabulary you are ready for any security specialty; the Cyber Security track on this site builds on exactly these ideas.

Quick quiz

Question 1 of 5

Why does IP spoofing work well for reflection attacks but poorly for opening a TCP connection from outside the path?

Question 2 of 5

An attacker on a café Wi-Fi poisons ARP caches and relays everyone's traffic. What protects a user's online banking session best?

Question 3 of 5

How do SYN cookies defend a server against a SYN flood?

Question 4 of 5

Your firewall log shows one source connecting to port 22 on 200 different hosts within a minute. What is this?

Question 5 of 5

A company's web server is compromised, but the attacker cannot reach the HR database. Which principle did the most to stop them?

Recap

  • Security protects confidentiality, integrity and availability; risk is likelihood times impact, reduced, never zero.
  • Spoofing forges identities: IP (stopped by BCP 38 filtering and uRPF), MAC (use 802.1X), ARP (Dynamic ARP Inspection, monitoring, encryption) and DNS (randomised IDs and ports, DNSSEC).
  • Man-in-the-middle attacks are defeated by end-to-end encryption with authentication: TLS with certificate validation, HSTS, SSH host keys, VPNs.
  • DoS/DDoS: volumetric floods and amplification (filter upstream, CDNs, anycast), state exhaustion (SYN cookies), application floods (rate limiting with token buckets).
  • Port scans show up as many distinct targets per source in a short window; slow scans and legitimate scanners are the blind spots.
  • Build on least privilege, segmentation and defence in depth, with secure defaults and monitoring that someone acts on.

Practice next: in a lab you own (two virtual machines are enough), scan one from the other with nmap and find the scan in the target's firewall log, then enable rate limiting and see it in action. You have finished the core of the networking track; cloud networking (VPCs, subnets and security groups) is the natural next step, and the Cloud Networking lesson of the Cloud Computing track on this site covers it.

Practice

Reading is not learning until you run something. Pick one.

Graded lab exercises