◇ OpenCyber Academy
Technical · Chapter 7 · English

Chapter 7 — Critical infrastructure and sector security: data and telemetry

Open, free learning for anyone who wants to protect people and technology. No account is required to read this text or its worked answers.

Chapter 7 — Critical infrastructure and sector security: data and telemetry

Lesson map

Lesson 07 published. Map, example, EX025.

flowchart TD
  N0["Learning outcomes and connection"]
  N1["7.1 Sources and blind spots"]
  N2["7.2 Syslog structure"]
  N3["7.3 Time"]
  N4["7.5 Practical activity"]
  N5["Chapter synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5
  N5 --> EX["Worked example"]
  EX --> Q["Exercise EX025"]

Logical drawing

flowchart LR
  N0["Learning outcomes and connection"]
  N1["7.1 Sources and blind spots"]
  N2["7.2 Syslog structure"]
  N3["7.3 Time"]
  N4["7.5 Practical activity"]
  N5["Chapter synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> Q["EX025"]
[Learning outcomes and connection]
    |
    v
[7.1 Sources and blind spots]
    |
    v
[7.2 Syslog structure]
    |
    v
[7.3 Time]
    |
    v
[7.5 Practical activity]
    |
    v
[Chapter synthesis]
    |
    v
[Worked example] --> [Exercise EX025]

Learning outcomes and connection

After this chapter you should be able to:

Chapter 6 said where things are. This chapter is about what they tell you, and whether you can put their stories in the right order.

7.1 Sources and blind spots

Source Shows Does not show
Process data (historian) Values and alarms over time Who caused a change
HMI and SCADA event logs Operator actions, alarm acknowledgements Actions taken directly on the controller
Controller/RTU logs Configuration sessions, mode changes (if supported and enabled) Often nothing; many devices log little
Firewall logs Allowed and denied connections at the boundary Traffic inside a zone
Host logs (jump host, servers) Logins, commands, service events Activity on devices without logging
Passive capture / flow records Who talked to whom, with what function Encrypted content; traffic not passing the sensor

NIST's log management guide describes collection, retention and protection of these records (SP 800-92). The technician's first job is to know which source answers which question, and which questions no source answers.

7.2 Syslog structure

RFC 5424 defines the modern syslog format: <PRI>VERSION TIMESTAMP HOSTNAME APP-NAME PROCID MSGID STRUCTURED-DATA MSG (RFC 5424). The priority value is facility × 8 + severity: <38> is facility 4 (security/authorization), severity 6 (informational). The timestamp is RFC 3339 with an offset, and a dash (-) means "nil" — for a timestamp, the sender did not know the time. Many devices still send the older BSD format (<13>Oct 11 23:15:02 host app: ...) with no year and no zone; count such lines and parse them separately rather than dropping them.

7.3 Time

Every timeline is only as good as its clocks. NTP (RFC 5905) lets hosts measure their offset from a reference (RFC 5905). In control environments some devices have no NTP client, drift by minutes, or are deliberately isolated from time sources. Record, for every host that contributes to a timeline, its measured offset at a known moment, and correct its timestamps by subtracting it: corrected = reported − (host clock − reference). Convert everything to UTC before sorting; local offsets such as +01:00 are correct but easy to misorder by eye.

7.4 Worked example: the 23:14 sequence (synthetic)

On the night of 11 October, an operator reported that pump 2 at KW-04 started unexpectedly. You collected six lines and measured clock offsets the next morning (the RTU was 212 s behind the reference).

Lab 7 (save as lab07.py, run with python3 lab07.py):

# Lab 7 - parse RFC 5424 syslog, correct clock offsets and build a UTC timeline (offline, stdlib)
# Synthetic lines from the Kestrel Vale operations centre; offsets measured against the NTP reference.
import re
from datetime import datetime, timedelta, timezone
lines = [
 "<38>1 2026-10-11T23:14:05.120Z fw-01 filterlog - - - block tcp 10.30.3.7:51544 -> 10.10.4.2:502",
 "<86>1 2026-10-12T00:13:58+01:00 jump-01 sshd 812 - - Accepted publickey for jtan from 10.90.0.40",
 "<134>1 2026-10-11T23:16:40Z hmi-02 hmi - - - operator setpoint change KW-04 PUMP2 start",
 "<38>1 2026-10-11T23:15:31Z rtu-kw04 auth - - - config session opened user=eng",
 "<13>Oct 11 23:15:02 eng-ws-15 app: project download started",
 "<38>1 - hist-02 histd - - - archive rollover",
]
offset_s = {"fw-01": 0.0, "jump-01": 0.4, "hmi-02": 0.0, "rtu-kw04": -212.0}   # host clock minus reference
pat = re.compile(r"^<(\d{1,3})>1 (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) ?(.*)$")
events, unparsed = [], []
for ln in lines:
    m = pat.match(ln)
    if not m: unparsed.append(("not RFC 5424", ln)); continue
    pri, ts, host, app, _pid, _msgid, _sd, msg = m.groups()
    if ts == "-": unparsed.append(("no timestamp", ln)); continue
    t = datetime.fromisoformat(ts.replace("Z", "+00:00")).astimezone(timezone.utc)
    t_corr = t - timedelta(seconds=offset_s.get(host, 0.0))
    fac, sev = divmod(int(pri), 8)
    events.append((t_corr, host, f"fac={fac} sev={sev}", msg, offset_s.get(host, 0.0)))
for t, host, fs, msg, off in sorted(events):
    note = f" (clock corrected {-off:+.1f} s)" if off else ""
    print(f"{t.strftime('%H:%M:%S.%f')[:-3]}Z {host:<9} {fs:<11} {msg}{note}")
for why, ln in unparsed: print("UNPARSED", why, "|", ln)

Expected output (Python 3.13.5):

23:13:57.600Z jump-01   fac=10 sev=6 Accepted publickey for jtan from 10.90.0.40 (clock corrected -0.4 s)
23:14:05.120Z fw-01     fac=4 sev=6 block tcp 10.30.3.7:51544 -> 10.10.4.2:502
23:16:40.000Z hmi-02    fac=16 sev=6 operator setpoint change KW-04 PUMP2 start
23:19:03.000Z rtu-kw04  fac=4 sev=6 config session opened user=eng (clock corrected +212.0 s)
UNPARSED not RFC 5424 | <13>Oct 11 23:15:02 eng-ws-15 app: project download started
UNPARSED no timestamp | <38>1 - hist-02 histd - - - archive rollover

Before correction, the RTU line (23:15:31Z) appeared before the HMI setpoint change (23:16:40Z), suggesting someone configured the RTU and then the operator acted. After correction the RTU session happened at 23:19:03Z — after the operator's start command. The story changes from "someone tampered first" to "an engineering session followed the operator's action", which leads to a different question (who opened a configuration session at 23:19 and why). Two lines could not be placed: the BSD-format line from the engineering workstation has no year or zone, and the historian line has a nil timestamp. They are reported, not discarded; the engineering workstation's "project download started" is potentially the most important line of all, and placing it requires its own clock check and the workstation's local logs.

7.5 Practical activity

Measure the offset of a machine you own against a public or local NTP server (for example chronyc tracking or ntpq -p on Linux, w32tm /stripchart on Windows), record it with the time of measurement, and repeat after 24 hours to see drift.

Record the host, the reference, the offset and the time of measurement. After twenty-four hours, compare the new offset with the old one to see drift. Keep both readings with the timeline work in Lab 7, so a later investigation knows how uncertain a correction made from a single measurement would be.

7.6 Common errors and limits

Exercises — chapter 7

EX025 — Decode priorities. Objective: compute facility and severity. Inputs: <86>, <134>, <11>, <165>. Steps: compute each. Expected evidence: four pairs with names. Rubric: 3 = 86 → 10/6 (authpriv, informational); 134 → 16/6 (local0, informational); 11 → 1/3 (user, error); 165 → 20/5 (local4, notice); 2 = arithmetic right, names missing; 1 = partial; 0 = none.

EX026 — Correct a clock. Objective: apply the offset formula. Inputs: fep-01 logs an event at 02:10:00Z; its offset measured an hour later was +47 s (host ahead). Steps: give the corrected time and the uncertainty you would state. Expected evidence: corrected time and a sentence on uncertainty. Rubric: 3 = 02:09:13Z; uncertainty because the offset was measured later and drift is unknown, e.g. "±a few seconds; offset measured 03:10Z"; 2 = right time without uncertainty; 1 = wrong direction; 0 = none.

EX027 — Place the BSD line. Objective: handle legacy formats safely. Inputs: the eng-ws-15 line in Lab 7. Steps: list what you need to place it on the timeline and write the parsing rule you would add. Expected evidence: list and rule. Rubric: 3 = year from collection context, the host's time zone setting and its measured offset; parse with explicit zone assignment and mark the result "assumed zone"; 2 = rule without the assumptions recorded; 1 = drop it; 0 = none.

EX028 — Blind-spot question. Objective: match questions to sources. Inputs: "Did anyone write to KW-04 directly from the station's local laptop port?" Steps: say which sources could answer it, which cannot, and what you would need to add. Expected evidence: short analysis. Rubric: 3 = control-centre firewall and capture cannot see local station traffic; RTU logs might, if it logs configuration and writes; add a station-level sensor or RTU logging and physical access records; 2 = partly; 1 = "check the firewall"; 0 = none.

Chapter synthesis

Telemetry is a set of partial witnesses. Know what each source can see, parse formats strictly while counting what you cannot parse, and correct every clock against a reference before you order events. A timeline built on uncorrected clocks can reverse cause and effect; one built on corrected clocks, with its uncertainties stated, is evidence.

Continue studying — chapter 07 of 18

← Chapter 06 · Course chapter index · Chapter 08 →

Continue to the next authored lesson.

Worked answers

EX025 — Decode priorities. <86>: 86 = 10 × 8 + 6 → facility 10 (authpriv), severity 6 (informational). <134>: 16 × 8 + 6 → local0, informational. <11>: 1 × 8 + 3 → user, error. <165>: 20 × 8 + 5 → local4, notice.

EX026 — Correct a clock. Offset +47 s means the host is ahead; corrected = 02:10:00 − 47 s = 02:09:13Z. State the uncertainty: "offset measured at about 03:10Z; drift over the hour unknown; treat as ±5 s unless drift data are available".

EX027 — Place the BSD line. Needed: the year (from collection context — the export date), the workstation's configured time zone, and its measured offset. Rule: lines matching ^<\d+>[A-Z][a-z]{2} [ \d]\d \d\d:\d\d:\d\d are parsed with an explicit year and zone from a per-host table, and the event is flagged assumed_zone=true on the timeline. If the host's zone or offset is unknown, place it in a separate "unplaced" list rather than guessing.

EX028 — Blind-spot question. Control-centre firewall logs and the Z2 sensor cannot see traffic between a laptop and the RTU on the station's local switch or serial port. The RTU's own logs might, if it records configuration sessions and writes and if logging is enabled. Add: RTU event logging forwarded when the link is up, a station-level passive sensor where justified, and physical access records (door alarms, site visit log).