Restarting the CSI Linux Certified Investigator: Hardening My Investigation VM Before Module 1

Before restarting the CSI Linux Certified Investigator course on EchothisLabs, I verified my CSI Linux VM's big upgrade, closed five network services an investigation box doesn't need, fixed group memberships, and mapped out where the course goes from here.

Why I’m restarting
I’m restarting the CSI Linux Certified Investigator (CSI CI) course from Module 1, and before opening a single lesson I made sure my investigation VM was in a state I could trust. This post documents that prep: what I checked, what I shut down, what surprised me, and the road map from here.
CSI CI is a free, 27-module course on EchothisLabs that teaches investigators to use CSI Linux, a distro built for case management, OSINT, digital forensics, and incident response. It is not an attack distro like Kali. I published a full reference summary of the course earlier; this post is the practical companion to it.
The theme of the day turned out to be one idea the course itself teaches: don’t assume, verify. Nearly every step below had a moment where the obvious reading of the output was wrong.
Where I left off: the October 9 upgrade
My CSI Linux VM runs in VMware Workstation on my Debian desktop. CSI Linux is built on Ubuntu 24.04 (Noble), and when I sat down to update it on October 9, I found a mess:

  • A “Frankenubuntu” sources list. The main package list mixed old Ubuntu 22.04 (jammy) lines with 24.04 (noble) lines.
  • Stale third-party repos. Kismet (wireless monitoring), Oxen (privacy networking), and WineHQ still pointed at 2023-era releases.
  • A permissions problem. The Kismet repo file was owned by the normal user with group-write access. Because apt runs as root, anyone who could edit that file could steer what root installs. That’s a privilege-escalation path, so I fixed it with chown and chmod first.
    After backing up, cleaning the sources, and pointing everything at noble, the upgrade ran: 308 packages upgraded, 27 newly installed, 0 removed, plus a new kernel (6.8.0-138 to 6.8.0-146).
    The session ended with four checks still undone: confirm every package finished, reboot, verify the new kernel, and review the network ports. That’s where this post picks up.
    Step 1: Verify the upgrade actually finished
    All three checks passed, but only after I ran them on the right machine.
    Check
    Command
    Result
    Every package configured
    sudo dpkg –audit
    No output, so nothing half-installed
    New kernel loaded
    uname -r
    6.8.0-146-generic
    VMware integration running
    systemctl status open-vm-tools
    active (running), enabled at boot
    dpkg is the low-level tool under apt, and –audit lists packages that were unpacked but never finished setting up. The open-vm-tools status also showed the service had started 12 minutes earlier, which quietly confirmed the reboot had happened.
    The lesson I didn’t expect: my first dpkg –audit ran on my Debian host, not the VM. It came back clean, which told me nothing about the machine I’d just upgraded. The giveaway was the prompt: the host’s prompt has my host username and Debian, while the VM’s reads csi@csi. Now I read the prompt before trusting any output. In forensics, that’s the difference between collecting evidence and collecting evidence from the wrong system.
    Step 2: See what the VM exposes to the network
    The out-of-the-box VM had five services reachable from other machines that an investigation workstation has no use for.
    The command was sudo ss -tulpn: tcp, udp, listening, process, numeric. It lists every open port and the program behind it, which is the inside view of what an nmap scan would find from outside. sudo matters here because without it the Process column comes back mostly empty.
    The column that matters is Local Address:
  • 127.x.x.x or [::1]: only the VM itself can connect. Safe.
  • 0.0.0.0, [::], or *: any machine on the network can connect.
  • 192.168.122.1: libvirt’s internal virtual network inside the VM. Not reachable from outside.
    What was open to the network:
    Port
    Service
    What it is
    Decision
    TCP 22
    SSH
    remote login
    Off: logs showed no logins or attempts, ever
    TCP 80
    Apache
    web server serving only the 2023 default page
    Off
    TCP/UDP 111
    rpcbind
    NFS helper, a classic scan target (also on Metasploitable2)
    Off
    UDP 1701
    xl2tpd
    L2TP VPN server
    Off
    UDP 5353
    avahi-daemon
    announces the machine and its services on the LAN
    Off: an investigator’s box shouldn’t advertise itself
    UDP high port
    lokinet
    its own outbound network port
    Kept: lokinet needs it
    The localhost-only services stayed: Postfix mail (port 25), Tor (9050, 9051, 9040, which I’ll need in Module 11), lokinet’s control port, and local DNS. Listening on localhost is exactly how those should be set up.
    Step 3: Shut them down, and prove they stayed down
    All five are now off and won’t start at boot, but three of them taught me something on the way.
    The basic command was sudo systemctl disable –now . disable stops a service starting at boot, and –now also stops the copy that’s running. Every change is reversible with sudo systemctl enable –now .
    Lesson 1: socket activation. In the first scan, ports 22 and 111 were owned by systemd, not by SSH or rpcbind. That’s socket activation: systemd holds the port open and launches the real program only when a connection arrives. Disabling just the .service isn’t enough, because the .socket will wake it right back up, so I disabled both (ssh.socket ssh.service, rpcbind.socket rpcbind.service, avahi-daemon.socket avahi-daemon.service). Avahi also had a third way back in, a D-Bus alias that let other programs start it on demand. Disabling removed that too.
    Lesson 2: “disabled” and “running” are two different states. xl2tpd has no native systemd unit, only an old SysV init script, so disable was handed off to a legacy tool that changed the boot setup and never touched the running process. systemctl is-active xl2tpd.service still said active. A separate sudo systemctl stop finished it. Checking a system’s boot configuration doesn’t tell you what’s running in memory, which is a core idea behind Incident Response (Module 18) and Memory Forensics (Module 19).
    Lesson 3: read the whole timeline. One disable printed a scary “triggering units are still active” warning, and an SSH disable printed no Removed lines at all. The warning turned out to be harmless timing, and the missing lines were from me running the command a second time, after the first run had already done the work. I only saw that once I had the full scrollback with timestamps in order.
    Every change got two checks:
  • systemctl is-active → want inactive (off now)
  • systemctl is-enabled → want disabled (off after reboot)
    The final ss -tulpn showed nothing bound to all interfaces except lokinet’s own UDP port. A new localhost-only listener had also appeared: the helper for Hunchly, a tool that automatically captures web pages during investigations and the one behind Module 12.
    Sidebar: why not just use sudo for everything?
    Because least privilege applies to me at the keyboard too. I asked this mid-session after a log search without sudo came back empty, and an empty result can mean “nothing happened” or “you weren’t allowed to look.”
    Reasons not to sudo by default:
  • Mistakes cost more. A typo as a normal user damages your files. As root it can damage the whole system.
  • You hand the program full control. If a script has a bug or was tampered with, it now runs as root. A lot of privilege escalation, the attacker’s side of Black Hat Python, depends on exactly that.
  • Side effects. Files created with sudo are owned by root. My backup of the apt sources from October 9 is root-owned for that reason.
  • Habit. Typing sudo on everything trains you to stop noticing when it matters.
    Use sudo to read protected things (system logs, other users’ processes) or to change system state (install packages, enable or disable services, edit /etc). Skip it for read-only status checks like uname -r or systemctl is-active.
    A correction worth admitting: it turned out my csi user is in the adm group, which on Ubuntu can read the system journal. So the first, non-sudo search was valid after all, and both runs agreed that SSH had never been used. The habit still holds: know which files are protected rather than guessing.
    AWS DVA-C02 connection: this is IAM least privilege. You wouldn’t attach AdministratorAccess to a Lambda function just in case it needs it.
    Step 4: Group memberships
    I added my csi user to the kismet group, and the group list turned up two root-equivalent memberships worth knowing about.
    Kismet. During the October 9 upgrade, I let Kismet install small “suid-root” capture helpers instead of running the whole program as root. Only kismet group members can run those helpers, so:
    sudo usermod -aG kismet csi
    The -a (append) is critical. -G without it replaces all your groups, including sudo. Group changes only apply to a new login, so groups showed no kismet until I logged out and back in. That’s not a failure, just the old session’s list. After logging back in: csi adm cdrom sudo dip plugdev lxd kismet libvirt vboxsf. That’s ready for Module 22 (SIGINT, SDR & Wireless).
    Root-equivalent groups. Members of lxd (in the VM) and docker (on my host) can mount the whole filesystem inside a container and change anything, without a password. It’s a staple of privilege-escalation labs. Since both accounts already have sudo, these groups add no new power for me. They do give anyone who compromises those accounts a quiet route to root. I’m keeping them for now and noting the trade-off. vboxsf is a harmless VirtualBox leftover from the CSI Linux image.
    DVA-C02 connection: the docker group risk is why AWS guidance says not to run containers privileged, and why ECS task roles are scoped narrowly.
    Step 5: Where the course stands
    EchothisLabs shows 0 of 21 activities completed, 0%, so I’m starting cleanly from Module 1.
    Three things I worked out from the course pages:
  • 21 activities vs. 27 modules. EchothisLabs runs on Moodle, which counts tracked activities, not modules. In the Course Menu, items with an empty circle are tracked and items without one aren’t. They’re spread unevenly: the opening Information and Announcements section has none, Module 1 has five (network jargon, networking devices, Professor Messer Network+ videos, a CSI CI overview, computer forensics terms), and Module 2 has none. My 27-module reference summary is still the map; the progress bar just moves in uneven steps.
  • Why 0% after an earlier start. Many Moodle activities only count when you click Mark as done. If I skipped that last time, this is exactly what the dashboard would show. This time I’m looking for that button on every tracked page.
  • Start from the top, not Resume. With nothing saved, Resume has no place to return to. I’m opening Operation Investigator Launch, the first item in Module 1, and working down in order.
    Course on the host, investigations in the VM
    I watch and read the course on my Debian host and keep the CSI Linux VM for hands-on work only. That’s compartmentalization, one of the OPSEC ideas in Module 8.
  • Identity. My EchothisLabs account is tied to my real name. The VM is where sock-puppet accounts and investigation browsing will live, and the two shouldn’t mix.
  • Clean evidence. Hunchly logs the pages visited inside the VM. Course browsing there would get mixed into what should be case material.
  • Resources. Videos run better on the host, and my notes live there.
    Activity
    Where
    Lessons, videos, flashcards
    Debian host
    Tools, exercises, case management
    CSI Linux VM
    Notes about what I did
    Host
    Files moving between them
    VMware shared folder
    DVA-C02 connection: the same idea as separate AWS accounts for dev and prod under AWS Organizations.
    The way forward
    The next few sessions are reading; the hands-on work starts at Module 7.
    Modules
    Mostly
    Where
    My focus
    1–2
    Reading and videos: orientation, networking basics, terms
    Host
    Notes in my own words; don’t rush the networking
    3–6
    Install, setup, network troubleshooting, settings
    VM
    Largely done by this prep, so a quick review
    7–12
    Case management, OPSEC, OSINT, secure comms, Tor, web evidence capture
    VM + host
    Chain of custody habits; Hunchly; Tor through localhost
    13–24
    Video evidence, geolocation, disk and memory forensics, malware, IR, SIGINT, threat intel and hunting
    VM
    The deep forensics core
    25–27
    Extra tools, wrap-up, exam
    Both
    Review my reference summary and flashcards
    Before Module 1:
    [ ] Take a VMware snapshot of the cleaned-up VM (CSI-CI-start-2026-10-16) as a known-good baseline
    [ ] Read the three course notifications
    [ ] Set up a notes file for Module 1 vocabulary
    Habits for this run:
    [ ] Click Mark as done on every tracked activity
    [ ] Check the prompt before trusting any output
    [ ] Verify both states after any service change: running now, and set to start at boot
    Connections to my other tracks:
  • Black Hat Python: Module 1’s networking material is the vocabulary behind Chapter 2’s sockets, clients, and proxies. Module 12’s page capture and hashing is a natural small project with Python’s hashlib.
  • AWS DVA-C02: VPCs, subnets, security groups, and DNS all rest on the same fundamentals. The snapshot is the same idea as an EBS snapshot or a golden AMI.
    The next post in this series will be the build post: working through Modules 1–7.
    Lessons learned and command reference
    Five lessons from one prep session:
  1. Check which machine you’re on. The prompt tells you; read it before you trust the output.
  2. An empty result is only evidence if the search could have found something. Permissions and filtering can hide the warning that tells you so.
  3. Disabled is not stopped. Boot configuration and the current state are separate claims, and each needs its own check.
  4. Socket activation hides the real owner. If systemd owns a port, disable the .socket along with the .service.
  5. Least privilege applies at the keyboard. sudo when the command needs it, not by reflex.
    Commands used, in order (run one at a time):
    sudo dpkg –audit
    uname -r
    systemctl status open-vm-tools
    sudo ss -tulpn
    sudo systemctl disable –now rpcbind.socket rpcbind.service
    sudo systemctl disable –now xl2tpd.service
    sudo systemctl stop xl2tpd.service
    sudo systemctl disable –now avahi-daemon.socket avahi-daemon.service
    sudo journalctl -u ssh –no-pager | grep -iE “accepted|failed”
    sudo systemctl disable –now ssh.socket ssh.service
    ls -la /var/www/html
    sudo systemctl disable –now apache2.service
    systemctl is-active
    systemctl is-enabled
    sudo usermod -aG kismet csi
    groups
    To undo any service change: sudo systemctl enable –now .