Back to the course
Test yourself

Advanced Linux security

Final exam · 53 questions · answers explained as you pick
The attacker on the host
8 questions
01sudo getcap -r / on a default 26.04 host prints /usr/lib/snapd/snap-confine with a long list that includes cap_setuid and cap_sys_admin and ends in =p. How should you read that line?
Incorrect — snap-confine is part of the expected set on a snap-enabled image; a baseline records it, and only a change to the line is worth an alert.
Incorrect — File capabilities have no pending state. p stands for the permitted set.
Incorrect — Permitted capabilities can be raised by the program itself; they are not blocked.
Correct — =p puts them in the permitted set without the effective bit. snapd manages this binary, so it belongs in the baseline and a change to the line is what you alert on.
02A runbook written on RHEL 10 says to review the day's privileged activity with ausearch. On a default Ubuntu 26.04 server, command -v auditctl finds nothing. What is the situation?
Correct — Ubuntu leaves auditd out of a default install (sudo apt-get install auditd adds it); RHEL 10 installs and enables it out of the box.
Incorrect — journald does not replace auditd. Its audit socket is disabled by default, and nothing converts the runbook's searches.
Incorrect — The audit subsystem is in the kernel: AppArmor's audit-format messages already appear in the kernel log of a default server.
Incorrect — sudo and coreutils were replaced by Rust versions; auditd was not. The package is simply not installed.
03An intruder copies the SSH key of the first administrative account on a default 26.04 cloud image. Which part of the surface mapped in this course turns that key straight into root, with no exploit?
Incorrect — ssh-keysign is a protocol helper in the normal SUID set; a stolen user key does not make it hand out a root shell.
Incorrect — cap_net_raw lets ping open raw sockets. It does not change user IDs.
Correct — On a cloud image cloud-init writes that grant into /etc/sudoers.d for its default user; it runs anything as root with no password prompt, so owning the account's key or session is owning root.
Incorrect — The stub resolver answers DNS queries; it does not run commands for anyone.
04On Ubuntu 26.04 the sudo log shows deploy opening a root shell with sudo -i at 03:00, then nothing from deploy for an hour. auditd runs with execve rules for logged-in users. Where do you find what was done as root in that hour?
Incorrect — sudo logs the elevation it performs. Commands typed inside the root shell never pass through sudo, so the sudo log holds one line for the whole hour.
Correct — The login identity survives sudo -i, so the audit records of every program the root shell started still name deploy.
Incorrect — sudo-rs on Ubuntu 26.04 has no session I/O logging (no log_output in its sudoers); classic sudo on RHEL has it.
Incorrect — Bash writes history when the shell exits and adds times only with HISTTIMEFORMAT; root can also edit the file. It is a lead, not the record.
05Endpoint telemetry shows the web service account kc-www running ss -tlnp. The output lists the listeners on ports 22 and 53, but the Process column is blank on every line. What does the blank column tell you?
Incorrect — No hiding is needed to explain it: ss fills the column for sockets the caller is allowed to inspect.
Incorrect — The resolver has a running process and so does the socket-activated SSH listener; root sees both in the same output.
Correct — The blank column marks a low-privilege caller, and a burst of id, uname and ss from a web service account is the tell.
Incorrect — -p is the option that asks for processes. Run by root, the same -tlnp prints them.
06sudo cat /home/kc-mover/.ssh/authorized_keys prints one line, an ed25519 key with no options in front of the key type. What would make that line a finding on a managed host?
Incorrect — Options are good hygiene, but most legitimate keys in real fleets carry none, so a key without them is not the signal.
Correct — A key that is not in the inventory, with or without options, is a persistence and lateral-movement marker (T1098.004).
Incorrect — authorized_keys holds public keys in clear by design. Hashing applies to host names in known_hosts.
Incorrect — ed25519 is a modern, recommended key type; the algorithm is not the problem.
07You found an unexpected key in kc-mover's authorized_keys, and the journal holds "Accepted publickey for kc-mover from 127.0.0.1 port 37448 ssh2: ED25519 SHA256:Ano9QTZq...". How do you show that this login used the planted key and not one of the account's other keys?
Incorrect — The client's network stack picks the port; it says nothing about which key line was used.
Incorrect — The session line names the user and uid, not the key comment. The key is identified by its fingerprint.
Incorrect — known_hosts records host keys of servers the account connected to, not keys used to log in to it.
Correct — The log records the fingerprint of the key that authenticated, which ties "a key was planted" to "that key was used".
08After a lateral move, the journal holds "session opened for user kc-mover(uid=1002) by kc-mover(uid=0)" from sshd-session. A colleague reads the uid=0 as proof that the intruder logged in as root. What does it show?
Correct — The login is kc-mover's; the root-owned monitor process opens the PAM session on its behalf, so uid=0 appears on every normal SSH login.
Incorrect — The Accepted publickey line names the account that authenticated, kc-mover, and the key's fingerprint; key files have no owner in the log.
Incorrect — A duplicate UID 0 would show as uid=0 for the user itself; here the user's own UID is 1002, and the (uid=0) belongs to the process after "by".
Incorrect — The line is written once, when the session opens, before anything runs in it; sudo writes its own lines under the sudo tag.
8 questions · explanations appear as you answer
Privilege escalation
13 questions
01Two execve records with the key pl_sudo appear a moment apart. One has auid=1001 uid=1001 euid=0 exe="/usr/lib/cargo/bin/sudo", the other auid=1001 uid=0 euid=0 exe="/usr/bin/gnuid". How do you read the pair?
Incorrect — Both records carry auid=1001, the same login. A process started by a root login would carry that login's auid instead.
Incorrect — sudo is SUID-root, so every normal run shows the uid/euid split; the split alone is not tampering.
Correct — The first record is the SUID split, the second the target command with uid=0, and auid ties both to the person who logged in.
Incorrect — A file capability changes no user ID at execve and leaves a BPRM_FCAPS record; neither applies to these two records.
02A developer needs to change /etc/nginx/nginx.conf as root. Which sudo arrangement allows that without handing over a root shell?
Correct — With sudoedit no root-owned editor process exists, so there is no root editor to escape from.
Incorrect — The editor itself runs as root, and an editor can run other commands. Naming the file does not change that.
Incorrect — A pager run as root can spawn a shell, and less cannot save edits anyway.
Incorrect — The wildcard widens the grant to every file under the directory, and tee run as root writes whatever it is given.
03Your baseline diff prints "> /var/tmp/pl-lab/capprobe cap_setuid=ep" and "> /var/tmp/pl-lab/showid", and no change record explains either line. What comes first?
Incorrect — mount, su, sudo and passwd need SUID and ping needs cap_net_raw; a blanket strip breaks the host and destroys the evidence as well.
Incorrect — chmod and setcap update the change time, the one timestamp that dates when the bits were set. Removal is eradication, and it comes after the capture.
Incorrect — Accepting would hide an unexplained escalation path. An addition is a finding to explain, not noise.
Correct — Only root can make a SUID-root file or set cap_setuid, so capture the birth and change times, the content hashes and the package ownership before anything is removed.
04Your secret scan (grep -rIlE for AWS key IDs and PASSWORD= across /etc and /opt) returns only /etc/overlayroot.conf, where the match is a commented example. Can you report the host free of exposed credentials?
Incorrect — Credentials also sit in home directories, shell history, process environments and unit settings, none of which this scan read.
Correct — A pattern search is a floor, not a proof; the lesson reads process environments and unit settings with their own commands and adds shell history and unprotected private keys to the search.
Incorrect — The file is a harmless example; removing it changes nothing about secrets the patterns could not match.
Incorrect — -I skips binary files, not large ones; the gap is in what the patterns can recognise and where the scan looked.
05A Rocky Linux 10.2 server runs 6.12.0-211.16.1 while rpm -q kernel-core also lists 211.60.1, and the host has not rebooted since the update. Your package-based scanner reports the host fully patched. What is the real exposure?
Incorrect — dnf does not change the running kernel. New kernel code takes effect after the host boots it.
Incorrect — Backports are built into the new package. The running 211.16.1 image stays as it was until a reboot.
Incorrect — Red Hat tags backports with CVE identifiers; comparing the two changelogs counted 375 fixes present in the newer package alone.
Correct — An intruder's uname -r reads 211.16.1, and until the host boots 211.60.1 (or a live patch covers a given bug) those fixes do not protect it.
06A root cron job runs /opt/pk-scripts/backup.sh. namei -l shows "drwxrwxr-x root pk-ops pk-scripts" and "-rwxr-xr-x root root backup.sh". The script is root-owned and not group-writable. Why is the job still a way to root?
Incorrect — Cron's writability check covers files in /etc/cron.d, not the scripts they call, and nothing falls back to a shell.
Incorrect — Running it by hand gives the caller's own identity. An execute bit is not a SUID bit.
Correct — Write access to a directory is the right to remove and create names in it, whoever owns the files inside.
Incorrect — tar is not SUID. The risk is the writable directory above the script, which is also first on the job's PATH.
07A search with grep -rIlE for AWS key and PASSWORD= patterns found /opt/pk-app/.env, mode -rw-r--r--, holding a cloud access key. You change it to 0640 root:pk-app. The host had been running an internet-facing app under its own service account while the file was readable. Is the exposure closed?
Correct — A mode change stops future reads; it cannot recall a copy already taken, so a key readable by untrusted code must be replaced.
Incorrect — It removes future exposure. A key copied earlier still works until it is rotated.
Incorrect — History may hold other secrets worth checking, but it has nothing to do with copies of this key already taken.
Incorrect — -l prints file names, not matching lines, which is why the audit used it.
08The escalation-audit service fails, its journal names a new SUID-root file with "suid-sgid: + 407b14c4... /var/tmp/pd-lab-helper", and no package claims that path. Why is this an incident lead rather than an upgrade to wait out?
Incorrect — dpkg -S searches the file lists of installed packages. A path no package installed is not found, damaged or not.
Correct — Packages do not install into /var/tmp, and only root can create a SUID-root file, so it is an incident to capture before anything is removed.
Incorrect — dpkg -S searches every path a package installed, wherever it is, so the answer means something.
Incorrect — Nothing points at a stale database. The file was simply never installed by a package.
09The pd-privesc-audit script sets umask 077 and keeps its baseline in a root-only directory under /var/lib. What does that protect against?
Correct — The baseline is the detector's memory. It does nothing against root, which is why its hashes are also kept off the host.
Incorrect — umask sets the mode of files the script creates. It does not change what diff compares.
Incorrect — systemd has no such rule. The choice is about who may read or change the baseline.
Incorrect — umask is not a lock; it controls the default permissions of new files.
10Porting the audit script, a colleague deletes /etc/sudoers-rs from its sudoers grep on Ubuntu 26.04, calling it a typo. What could the audit then miss?
Incorrect — There is no compiled cache. It is a policy file that sudo-rs reads.
Incorrect — RHEL 10 uses classic sudo. The -rs file belongs to Ubuntu's sudo-rs.
Incorrect — It is not a backup; rules in it are live.
Correct — When /etc/sudoers-rs exists, sudo-rs reads it instead of /etc/sudoers, so an intruder could replace the whole policy without touching /etc/sudoers.
11Two containers run on an Ubuntu host. For ct-web, /proc/PID/status shows CapEff 00000000800405fb and attr/current reads "containers-default-0.66.0 (enforce)". For ct-priv, CapEff is 000001ffffffffff and the label is "crun (unconfined)". How do you read that?
Incorrect — Eleven is podman's reduced default. The full set and the unconfined profile on ct-priv are the danger.
Incorrect — These rootful containers share the host's user namespace, so the capability set and the label are what hold root back.
Correct — Ubuntu ships a crun AppArmor profile with flags=(unconfined), so it restricts nothing; ct-web keeps podman's eleven capabilities and an enforced profile.
Incorrect — No rule to deny means nothing is confined, which is the opposite of safe.
12On Rocky Linux 10.2, ps -eZ shows a container's process as system_u:system_r:container_t:s0:c788,c802. What does the c788,c802 part do?
Incorrect — Those are SELinux categories, not CPU numbers. CPU placement lives in cgroups.
Correct — Each container gets different categories, so even as root one container's process cannot touch another's files, while container_t confines them all from the host.
Incorrect — Capabilities show in CapEff. This part of the label holds SELinux categories.
Incorrect — UID mapping belongs to user namespaces; this is part of the SELinux context.
13On an Ubuntu host, lsns -t pid shows the host's PID namespace, ct-web's and one more, but ct-priv, started with --pid=host, is not listed. How do you still find every container process from the host?
Correct — --pid=host removes the PID namespace, not the cgroup: podman still places the container in its own scope, which marks every one of its processes.
Incorrect — Rootful containers share the host's user namespace, as the namespace comparison showed, so this finds none of them.
Incorrect — NSpid has one number per PID namespace the process is in; with --pid=host that is only the host's, so the line looks like any host process.
Incorrect — Its cgroup path still names the container, and podman inspect lists it too.
13 questions · explanations appear as you answer
Detection engineering
13 questions
01sudo auditctl -s prints "failure 1", "backlog_limit 8192", "lost 0", "backlog 0" and "backlog_wait_time 60000". Which value do you alert on to learn that records were dropped, and what does failure 1 do when it happens?
Incorrect — backlog is the current queue and moves up and down in normal work, rising briefly with any burst of matching activity on a healthy host. The panic setting is 2.
Incorrect — backlog_limit is a configured size that does not fall by itself, and failure does not stop the daemon.
Incorrect — That field is the configured wait (a minute here); backlog_wait_time_actual is the counter that grows. The silent setting is 0.
Correct — lost counts records dropped after the backlog filled and the wait ran out; 1 is printk, 2 panics, 0 is silent.
02Applications on an audited server freeze for seconds at a time. sudo auditctl -s shows lost 0, while backlog_wait_time_actual keeps rising. What is happening?
Incorrect — lost counts records the kernel dropped from its backlog; plugin overflows are reported separately in auditd's state report.
Correct — The kernel holds a process whose call produced a record until there is room, up to backlog_wait_time, and totals that wait in backlog_wait_time_actual.
Incorrect — It totals time processes spent waiting on a full backlog, which is exactly what a freeze on an audited host looks like.
Incorrect — failure 1 writes a message and carries on; it acts only when a record is lost, and lost is still 0.
03A SIEM filter for file opens was written on x86_64 servers and matches raw records with syscall=257. On your aarch64 hosts the matching record reads "arch=c00000b7 syscall=56 success=yes exit=3". Why does the filter miss it?
Incorrect — success=yes and exit=3, a file descriptor, show the open worked. 56 is the call's number.
Incorrect — c00000b7 is the audit constant for aarch64, which a b64 rule covers; the record exists.
Correct — Match on names (ausearch -i shows syscall=openat) or keep a table per architecture, and never hard-code one architecture's numbers.
Incorrect — Numbers are stable for an architecture across kernel versions. The difference here is the CPU architecture.
04The rule "-a always,exit -F arch=b64 -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged" matched ap-ana's run of chage. Why include the two auid filters?
Correct — auid is set at login and kept through sudo; processes systemd starts never had a login, so their auid is unset and the rule skips them.
Incorrect — auid is not the real uid. deploy's sudo read kept auid=1001 while uid was 0.
Incorrect — root logging in on a console or over SSH gets auid 0, not unset; unset marks processes with no login.
Incorrect — The filters change what matches, not the cost. The point is to keep service activity out.
05SELECT count(*) AS all_rows, sum(path LIKE '/usr/%') AS under_usr FROM suid_bin returns 54 and 27 on Ubuntu 26.04. Why does half the table sit outside /usr?
Incorrect — S and G programs appear under both prefixes alike. The split is by path, not by bit.
Correct — The usr merge makes /bin/passwd and /usr/bin/passwd the same file; filter on /usr/ paths or every count and alert doubles.
Incorrect — suid_bin walks a fixed list of system directories, and /snap is not in it.
Incorrect — osqueryi collects its answer at query time; nothing is cached from an earlier run.
06A host already runs auditd with your rules, and a teammate wants osquery to record process executions as events instead of by polling. Which option does the lesson point to?
Incorrect — osquery's documentation says auditd must not run alongside its audit-based publisher, since both want the kernel's audit connection.
Incorrect — Polling misses whatever starts and exits between runs, however short the interval, and costs more CPU.
Incorrect — That gives up the audit pipeline the rest of the course relies on, and the eBPF publisher avoids the choice.
Correct — The audit-based publisher needs the kernel audit connection that auditd holds; the eBPF one reads events through its own programs.
07As deploy, bpftool prog list fails with "Error: can't get next program: Operation not permitted", and sysctl shows kernel.unprivileged_bpf_disabled = 2. What does the value 2 mean?
Correct — 1 would lock it off until reboot, and 0 would allow some unprivileged program types. Both lab platforms default to 2.
Incorrect — Root loaded bpftrace programs on the same host; 2 applies to unprivileged callers.
Incorrect — That describes nothing on this host. 0 is the value that allows some unprivileged use.
Incorrect — The sysctl controls who may call bpf(), not how strictly the verifier checks.
08A bpftrace one-liner on openat for the account eb-dev prints "cat /etc/shadow -13" next to "cat /etc/hostname 3". What does the shadow line show that the program's own logs would not?
Incorrect — The value is the return code of openat. A negative return is an error, not a byte count.
Incorrect — A missing file gives ENOENT (-2), as the translation file did. -13 is EACCES.
Correct — Application logs never show a refused open, because the program wrote nothing; an audit rule on that file would record it, but only for files chosen in advance.
Incorrect — A successful open returns a non-negative descriptor, like the 3 for /etc/hostname. -13 is an error.
09auditd is running on an Ubuntu host with no rule for bpf(). ausearch -m BPF shows "type=BPF ... prog-id=1001 op=LOAD", and its SYSCALL record names comm=bpftrace and auid=deploy. Where did the record come from?
Incorrect — auditd adds no rules of its own; rules come from rules.d or auditctl.
Incorrect — auditctl -m makes USER records. This is a kernel BPF record tied to the bpf() call.
Incorrect — An execve record shows syscall=execve; this one shows syscall=bpf and the BPF record type.
Correct — auditd switched auditing on here; on a default Ubuntu install without auditd, audit starts disabled and the same loads leave no BPF record at all.
10A detection built on bpftrace's tracepoint:syscalls:sys_enter_openat never fires for a file an intruder's tool opened, although the file was read. The tool submits its I/O through io_uring. Why the miss, and what closes it?
Incorrect — io_uring requests are carried out by the kernel; they skip the per-call system-call tracepoints, not the kernel.
Correct — Hooks deeper in the kernel, on the file-open path itself, fire however the request arrived, which a system-call tracepoint cannot promise.
Incorrect — System-call tracepoints fire for every process; the probe filters, if any, are the ones you write.
Incorrect — Dropped events are counted and reported as lost; they are not tied to io_uring, and the miss here is complete, not partial.
11After installing Falco 0.45.0 on an aarch64 server, the service journal shows "Opening 'syscall' source with modern BPF probe." followed by "failure while attaching TOCTOU mitigation program for 'open' system call. Detection will continue to work". What does that mean?
Correct — Capture started with the intended driver; the notices describe the architecture, and detection continues.
Incorrect — The Opening line confirms the modern probe started. The module is for kernels without BTF and the BPF ring buffer.
Incorrect — The modern probe needs no headers, and BTF is present; file opens are captured through openat.
Incorrect — Several BPF programs can attach to one hook. These programs failed because the calls do not exist.
12falco --version prints "Falco version: 0.45.0" and "schema validation: ok" for each configuration file. Does that show the sensor is watching the host?
Incorrect — The version command reads the configuration and exits. It does not open the capture source.
Incorrect — falco.service is an alias, and enabled says the unit starts at boot, not that capture opened.
Correct — Filter the journal by the unit's current InvocationID and look for "Opening 'syscall' source with modern BPF probe".
Incorrect — Falco's stdout and syslog outputs land in the unit's journal, which is where the start-up lines are.
13Falco reports "Warning Sensitive file opened for reading by non-trusted program | file=/etc/shadow ... user=root user_uid=0 user_loginuid=1001 process=cat ... parent=sudo command=cat /etc/shadow". How do you triage it?
Incorrect — Disabling it removes the detection for every other process that reads the file.
Incorrect — user is the effective account. user_loginuid=1001 names the login behind the sudo.
Incorrect — An exception for cat would hide later reads of /etc/shadow by cat, by anyone. Verify first, and keep exceptions to specific benign tools.
Correct — The loginuid names the human behind root. A root read by an administrator is often legitimate, which is a question for triage, not for the rule.
13 questions · explanations appear as you answer
Hunting and detections
8 questions
01On David Bianco's Pyramid of Pain, where does a distinctive file path such as /var/tmp/.cache/miner sit, and what does that mean for a rule built on it?
Incorrect — Techniques are what the intrusion does. A path is one choice the attacker can change with a single mv.
Correct — Paths and names cost more than hashes or addresses but far less than the technique, which is why path rules go blind after a rename.
Incorrect — The base holds hashes, then IP addresses and domains. A path is a host artefact higher up, though still cheap.
Incorrect — The pyramid has a level for host and network artefacts, which is where file names and paths sit.
02You load the path rule (key det_ioc) and the directory rule (key det_behav) in one file, path rule first. After running /var/tmp/det-lab/sysmon, ausearch -k det_behav shows nothing, and a teammate declares the behaviour rule broken. What is happening?
Correct — auditctl(8) says the event triggers on the first matching rule. Test detection rules one at a time, and treat order as part of the design.
Incorrect — augenrules --load loads the merged file into the kernel, and dir= rules match straight away.
Incorrect — Nothing in the rule grammar filters on names; the watch covers the whole tree.
Incorrect — The event carries the key of the first matching rule, which is why det_ioc won.
03After routine admin commands, sudo aureport -k --summary prints "171 det_all" and "2 det_behav". det_all records every program started from a login session; det_behav is the scratch-directory execution rule. How do you read the numbers?
Incorrect — det_all counts ordinary programs such as id and ls, which det_behav is meant to ignore.
Incorrect — det_all logs every program a logged-in person starts; its volume is ordinary work.
Correct — The two det_behav events are the sysmon and kswapd0 runs. Low volume on normal activity is what a rule needs before it pages anyone.
Incorrect — Rule-load records add one or two events, not the gap between 171 and 2.
04The scratch-directory rule is "-a always,exit -F arch=b64 -F dir=/var/tmp -F perm=x -F key=det_behav". An intruder runs sh /var/tmp/x.sh instead of executing a binary from there. What does the rule record?
Incorrect — perm=x on a directory rule becomes an execve match on files in that tree. Reading a script is not executing it.
Incorrect — The kernel executed /usr/bin/sh, and the shell then read the script as data.
Incorrect — perm=r covers reads. This rule asks for execution alone.
Correct — That gap is why a behaviour rule belongs in a portfolio, paired with other rules and a written list of what you cannot yet see.
05A planted timer shows "det-hunt-refresh.timer enabled enabled" in systemctl list-unit-files on Ubuntu 26.04, and "enabled disabled" on Rocky Linux 10.2. What does the PRESET column tell you on each?
Correct — RHEL ends its presets with "disable *", while Ubuntu has no catch-all and systemd treats a unit no preset line matches as enable (systemd.preset(5)).
Incorrect — Ubuntu's enabled preset covers every unit no preset file mentions, and a hand-enabled unit on Rocky is often an ordinary local change.
Incorrect — PRESET shows what the vendor's preset policy would do, not who acted. STATE shows the current state.
Incorrect — They differ for every locally enabled unit on RHEL. The mismatch narrows the field; it proves nothing.
06Two processes show PPID 1. One is the MainPID of a unit that systemctl status lists; the other runs from "/var/tmp/det-hunt/.systemd-worker (deleted)" and belongs to no unit. What does PPID 1 tell you about the second one?
Incorrect — That fits the first process. The second belongs to no unit, so systemd did not start it as a service.
Correct — ps loses the lineage once a process is re-parented, while execve records keep the ppid it had when it started.
Incorrect — Kernel threads descend from kthreadd (PID 2) and have no executable; this one runs a file from /var/tmp.
Incorrect — The hunt showed it running as deploy. A parent's identity is not inherited that way.
07Which of these is a hunting hypothesis in the sense this course uses?
Incorrect — A wander has no finish line; nothing tells you when the host has answered.
Incorrect — That is the question hunting serves, far too broad for a host to answer yes or no.
Incorrect — Attackers do not label their activity, and a word search is not tied to one technique's trace.
Correct — It names one behaviour and the trace it must leave, so find -newerct plus dpkg -S or rpm -qf can answer it; mtime is a cheap first filter that a backdated plant defeats.
08sudo ss -tlnp shows a listener on 127.0.0.1:8081 owned by users:(("python3",pid=84684,fd=3)), and dpkg -S on the target of /usr/bin/python3 answers "python3.14-minimal: /usr/bin/python3.14". What is the finding?
Incorrect — Loopback listeners serve local relays and staging ports. Unreachable from outside is not the same as explained.
Incorrect — dpkg -S shows the interpreter is the packaged file; the question is what it was told to run.
Correct — No managed unit accounts for port 8081, so follow the process's arguments, owner and origin, and capture before you stop it.
Incorrect — A default server listens on 22 and the resolver stub, and a packaged interpreter can run anything.
8 questions · explanations appear as you answer
Forensics and incident response
11 questions
01On an Ubuntu 26.04 host under investigation, who | wc -l prints 0 while loginctl list-sessions --no-legend | wc -l prints 2. Your old collection script saves who -a. What goes wrong?
Correct — There is no /run/utmp on this release, so who has nothing to read while systemd-logind tracks the live sessions.
Incorrect — They read different sources: who reads utmp, loginctl asks systemd-logind.
Incorrect — utmp is absent by design on this release, so an empty who is expected, not tampering.
Incorrect — Privilege is not the issue; there is no utmp file for who to read.
02Your runbook says to acquire memory with AVML. On the suspect host, /sys/kernel/security/lockdown reads "[none] integrity confidentiality", sig_enforce reads "N" and uname -m prints "aarch64". What changes?
Incorrect — AVML is built for x86_64 per its documentation, so it will not run on this CPU at all.
Incorrect — N means unsigned modules are not rejected, so an LKM such as LiME could load here.
Correct — The architecture rules out AVML; with lockdown off and signing not enforced, LEMON or LiME are options on this host.
Incorrect — Confidentiality mode blocks the reads such tools rely on, and AVML cannot acquire under lockdown.
03The quarantine table's chains use "type filter hook input priority -400; policy drop;". Why run them at priority -400?
Incorrect — In nftables a lower number runs earlier at the hook, so -400 puts the quarantine first.
Correct — An accept in one chain can still be dropped by a later chain, but a drop ends the packet's path, so running first and dropping by policy makes the quarantine hold.
Incorrect — Priorities can be positive or negative; -400 is a choice, not a requirement.
Incorrect — Priority has nothing to do with persistence; a ruleset loaded with nft lives in memory until it is saved.
04The nft quarantine is loaded: the collector at 198.51.100.1 still answers, and a ping to the C2 address 198.51.100.9 shows "100% packet loss". A manager asks whether the host is now isolated for good. What do you answer?
Incorrect — Anyone with root on the host, which may be the attacker, can list and delete the table with nft.
Incorrect — Saving makes it persistent. It does not stop root on the host from removing it.
Incorrect — The drop policy applies to every protocol not explicitly accepted; the concern is who can undo it.
Correct — A quarantine VLAN or a locked-down security group sits where the intruder's root cannot reach it.
05With an anchor of 11:05:05, find -newermt lists .bash_history and .cache/motd.legal-displayed, find -newerct also lists det-art-notes.txt, and -newerBt fails with "invalid predicate". What explains the difference?
Incorrect — Reading can move atime, never ctime. ctime moves when the inode changes.
Incorrect — Both tests compare against the same anchor. The difference is which timestamp they compare.
Correct — touch set mtime to 2025 and the kernel set ctime to the moment of the touch; stat, through statx, can still show the birth time.
Incorrect — The file is not hidden and both searches walk the same tree. Its mtime alone kept it out.
06sudo journalctl --verify on system.journal prints "PASS", and ls of the journal's fss path says "No such file or directory". What has the check proven?
Correct — Forward Secure Sealing needs journalctl --setup-keys and a verification key kept elsewhere, and neither platform sets one up by default.
Incorrect — Tamper checks need a sealing key and --verify-key. Without them, PASS covers the file's structure.
Incorrect — --verify runs on the host's own files, and it checked their consistency.
Incorrect — The file sits in /var/log/journal, so the journal is persistent. fss is about sealing, not storage.
07In auth.log, "Accepted publickey for det-art-sam from 127.0.0.1 port 37296" and "session opened" come from sshd-session[254914], while "Disconnected from user det-art-sam 127.0.0.1 port 37296" comes from sshd-session[255008]. How do you tie them to one session?
Incorrect — The two processes are the privileged monitor and its unprivileged child for the same connection.
Correct — The monitor logs authentication and PAM, the child logs the disconnect, so the port links them where the PID does not.
Incorrect — The fingerprint appears on the Accepted line; the Disconnected line carries the address and port.
Incorrect — The session number is in logind's own lines, not in sshd-session's.
08The same session typed " cat /etc/hostname" with a leading space. The .bash_history on Rocky Linux 10.2 keeps that line; the one on Ubuntu 26.04 does not. Why?
Incorrect — No content filter exists; the leading space is what matters.
Incorrect — Both save at shell exit by default. The difference is which lines are kept.
Incorrect — The later id and uname lines were saved, so the shell exited normally.
Correct — ignoreboth includes ignorespace, which skips lines that begin with a space, while RHEL's /etc/profile sets ignoredups alone.
09systemctl status for an unexpected unit shows "Loaded: loaded (/etc/systemd/system/det-ir-cache.service; enabled; preset: enabled)". A colleague reads "preset: enabled" as proof that Ubuntu shipped the unit. What is the better reading?
Correct — Packages install units under /usr/lib/systemd/system, and on Ubuntu a unit no preset file names defaults to enabled anyway.
Incorrect — preset shows what policy would do, not who acted, and no package owns this path.
Incorrect — A masked unit is linked to /dev/null and shows as masked. This one is running.
Incorrect — systemd units are not signed; preset is a policy default.
10On Rocky Linux 10.2 the planted unit never runs: status shows "status=203/EXEC", and ausearch shows "avc: denied { execute } ... scontext=system_u:system_r:init_t:s0 tcontext=unconfined_u:object_r:user_tmp_t:s0". How should you treat this?
Incorrect — An intruder with root can relabel the file or move it into a system directory, so the block is not a control to rely on.
Incorrect — ExecStart already has a full path, and the AVC record names SELinux as the cause.
Correct — A crash-looping unit and repeated denied execute records make a strong alert, not a guarantee.
Incorrect — RHEL uses SELinux; avc records with scontext and tcontext are SELinux decisions.
11After eradication, systemctl status reports "Unit det-ir-cache.service could not be found.", grep finds no file that names it, and nothing listens on port 47001. The unit had been written into /etc/systemd/system. Can the host go back into production?
Incorrect — The checks prove the service is gone. They cannot prove the host holds nothing else an intruder with root left behind.
Correct — No command proves a negative. Recovery from a root compromise means a rebuild, clean data, a closed way in and rotated secrets, with an indicator check before return.
Incorrect — A reboot tests one persistence path; it neither clears nor reveals the rest.
Incorrect — Waiting repeats checks for the pieces you already know about.
11 questions · explanations appear as you answer