Remotely isolate a compromised endpoint without destroying evidence
For developers and responders who need to contain a suspected compromised workstation or server right now. This walks you through preserving volatile evidence first, then cutting network access remotely with host firewall rules or cloud security groups, and finally proving the endpoint is isolated without powering it off or wiping artifacts.
TL;DR — If you isolate first and collect later, you will lose the most valuable evidence: live network connections, RAM-resident malware, and short-lived process state. The fastest safe pattern is: start a remote evidence capture, sync time, record current connections/processes, optionally grab memory, then apply a deny-all network policy with a single allowlist for your response host or EDR channel. Reading time: ~5 min
Goal
When you finish, the suspected endpoint is remotely contained from normal network traffic while preserving the evidence you still need: current process list, active connections, key logs, and optionally a memory image. You will also have a repeatable way to verify the host is isolated except for your explicitly allowed management path.
Prerequisites
- Administrative access on the endpoint:
- Linux:
rootorsudowithout interactive approval - Windows: local Administrator or equivalent remote management rights
- Linux:
- One trusted responder IP address to allow during isolation, for example
203.0.113.10 - A place to store evidence with enough free space:
- Linux: mounted volume such as
/mnt/ir - Windows: local path like
D:\IRor a pre-mounted trusted share
- Linux: mounted volume such as
- Remote execution channel already working before isolation:
- SSH for Linux — check with
ssh user@host 'id' - PowerShell Remoting/WinRM for Windows — check with
Test-WSMan host
- SSH for Linux — check with
- Tools at hand:
- Linux:
tar,ss,ps,journalctl,iptablesornft,sha256sum - Windows PowerShell 5.1+ or PowerShell 7+,
Get-NetTCPConnection,netsh advfirewall,wevtutil,Get-FileHash
- Linux:
- Optional but recommended for memory capture:
- Linux: LiME or your approved acquisition tool already staged
- Windows: your approved memory acquisition tool already staged and signed
- Host details you need before you start:
- hostname, primary IP, OS version, timezone, cloud instance ID if applicable
Steps
Step 1: Start an evidence directory and capture volatile state first
⚠️ Do not reboot, shut down, log off users, kill suspicious processes, or start a full antivirus scan before this step. Those actions destroy timeline and memory evidence.
Linux:
sudo mkdir -p /mnt/ir/$(hostname)-$(date -u +%Y%m%dT%H%M%SZ)
IRDIR=$(sudo ls -td /mnt/ir/$(hostname)-* | head -1)
sudo bash -c 'date -u > "$IRDIR"/utc_time.txt'
sudo bash -c 'uname -a > "$IRDIR"/uname.txt'
sudo bash -c 'who -a > "$IRDIR"/who.txt'
sudo bash -c 'ps auxwwf > "$IRDIR"/ps_auxwwf.txt'
sudo bash -c 'ss -pantuu > "$IRDIR"/ss_pantuu.txt'
sudo bash -c 'ip addr show > "$IRDIR"/ip_addr.txt'
sudo bash -c 'ip route show table all > "$IRDIR"/ip_route.txt'
sudo bash -c 'journalctl --since "-2 hours" -o short-iso > "$IRDIR"/journal_last_2h.txt'
sudo tar -C / -czf "$IRDIR"/etc.tgz etc
sudo sha256sum "$IRDIR"/* > "$IRDIR"/SHA256SUMS.txt
printf '%s\n' "$IRDIR"
Windows:
$IRDir = "D:\IR\$env:COMPUTERNAME-$(Get-Date -AsUTC -Format yyyyMMddTHHmmssZ)"
New-Item -ItemType Directory -Path $IRDir -Force | Out-Null
(Get-Date).ToUniversalTime().ToString('o') | Out-File "$IRDir\utc_time.txt" -Encoding ascii
systeminfo | Out-File "$IRDir\systeminfo.txt" -Encoding ascii
quser | Out-File "$IRDir\quser.txt" -Encoding ascii
Get-Process | Sort-Object CPU -Descending | Format-Table -AutoSize * | Out-File "$IRDir\processes.txt" -Width 4096
Get-NetTCPConnection | Sort-Object State,LocalPort | Format-Table -AutoSize * | Out-File "$IRDir\tcp_connections.txt" -Width 4096
ipconfig /all | Out-File "$IRDir\ipconfig_all.txt" -Encoding ascii
route print | Out-File "$IRDir\route_print.txt" -Encoding ascii
wevtutil epl Security "$IRDir\Security.evtx"
wevtutil epl System "$IRDir\System.evtx"
wevtutil epl Application "$IRDir\Application.evtx"
Get-ChildItem $IRDir | Get-FileHash -Algorithm SHA256 | Format-Table -AutoSize | Out-File "$IRDir\SHA256SUMS.txt"
$IRDir
Success looks like a new timestamped directory containing process, network, and log artifacts plus a checksum file.
Step 2: If you have an approved memory tool staged, acquire memory now
⚠️ Memory capture is high-value and high-impact: it can consume CPU, disk, and time. Skip it if the endpoint is too unstable or if your organization forbids unapproved kernel modules/drivers.
Linux example with a pre-staged LiME module path:
IRDIR=$(sudo ls -td /mnt/ir/$(hostname)-* | head -1)
sudo insmod /opt/ir/lime.ko "path=$IRDIR/memory.lime format=lime"
sleep 5
ls -lh "$IRDIR"/memory.lime
sudo sha256sum "$IRDIR"/memory.lime >> "$IRDIR"/SHA256SUMS.txt
Windows example with a pre-staged acquisition binary:
$IRDir = Get-ChildItem D:\IR | Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty FullName
& "C:\IRTools\MemoryAcquire.exe" /output "$IRDir\memory.raw"
Get-FileHash "$IRDir\memory.raw" -Algorithm SHA256 | Format-List | Out-File "$IRDir\memory.raw.sha256.txt"
Success looks like a large memory image file and a SHA-256 hash recorded beside it.
Step 3: Apply host-level isolation while keeping one management path
Linux with iptables:
RESPONDER_IP="203.0.113.10"
sudo iptables -I INPUT 1 -s "$RESPONDER_IP" -p tcp --dport 22 -j ACCEPT
sudo iptables -I OUTPUT 1 -d "$RESPONDER_IP" -p tcp --sport 22 -j ACCEPT
sudo iptables -I INPUT 2 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -I OUTPUT 2 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -j DROP
sudo iptables -A OUTPUT -j DROP
sudo iptables -L -n --line-numbers
Linux with nftables:
sudo nft add table inet isolate
sudo nft 'add chain inet isolate input { type filter hook input priority 0; policy drop; }'
sudo nft 'add chain inet isolate output { type filter hook output priority 0; policy drop; }'
sudo nft add rule inet isolate input ip saddr 203.0.113.10 tcp dport 22 accept
sudo nft add rule inet isolate output ip daddr 203.0.113.10 tcp sport 22 accept
sudo nft add rule inet isolate input ct state established,related accept
sudo nft add rule inet isolate output ct state established,related accept
sudo nft list ruleset
Windows with Advanced Firewall:
netsh advfirewall set allprofiles state on
netsh advfirewall firewall add rule name="IR-Allow-WinRM-In" dir=in action=allow protocol=TCP localport=5985 remoteip=203.0.113.10
netsh advfirewall firewall add rule name="IR-Allow-WinRM-Out" dir=out action=allow protocol=TCP remoteport=5985 remoteip=203.0.113.10
netsh advfirewall firewall add rule name="IR-Block-All-In" dir=in action=block remoteip=any
netsh advfirewall firewall add rule name="IR-Block-All-Out" dir=out action=block remoteip=any
netsh advfirewall firewall show rule name=all | findstr /C:"IR-"
Success looks like the allow rule listed first and a deny-all rule active for all other traffic.
Step 4: If the endpoint is in cloud, add network isolation at the instance boundary too
⚠️ Changing a cloud security group or network ACL can cut off your own access immediately. Add the allow rule for your responder IP first, then the deny rules.
In your cloud provider dashboard, edit the instance-attached firewall object:
- AWS: EC2 → Instances → select instance → Security → Security groups
- Azure: Virtual machines → select VM → Networking → Network settings / NSG rules
- GCP: Compute Engine → VM instances → select VM → Network interfaces / Firewall
Set rules to these literal values:
- Inbound allow: source
203.0.113.10/32, protocolTCP, port22for Linux or5985for WinRM - Inbound deny: source
0.0.0.0/0, all protocols, all ports - Outbound deny: destination
0.0.0.0/0, all protocols, all ports
Success looks like only your responder IP remaining allowed in the instance firewall view.
Step 5: Record the isolation action in the evidence set
Linux:
IRDIR=$(sudo ls -td /mnt/ir/$(hostname)-* | head -1)
sudo bash -c 'iptables-save > "$IRDIR"/iptables_after_isolation.txt || true'
sudo bash -c 'nft list ruleset > "$IRDIR"/nft_after_isolation.txt || true'
sudo bash -c 'date -u > "$IRDIR"/isolation_time_utc.txt'
Windows:
$IRDir = Get-ChildItem D:\IR | Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty FullName
netsh advfirewall export "$IRDir\firewall-after-isolation.wfw"
(Get-Date).ToUniversalTime().ToString('o') | Out-File "$IRDir\isolation_time_utc.txt" -Encoding ascii
Success looks like a firewall/ruleset snapshot and a UTC timestamp saved with the rest of the artifacts.
Verify it works
From your responder host, verify the management path still works.
Linux target over SSH:
ssh user@target 'echo ok'
Expected output shape:
ok
Windows target over WinRM:
Invoke-Command -ComputerName target -ScriptBlock { 'ok' }
Expected output shape:
ok
Then verify non-allowed egress is blocked from the endpoint.
Linux target:
curl -I --max-time 5 https://example.com ; echo $?
Expected output shape when isolated:
curl: (28) Connection timed out after 5001 milliseconds
28
Windows target:
Test-NetConnection example.com -Port 443
Expected output shape when isolated:
ComputerName : example.com
RemoteAddress : 93.184.216.34
RemotePort : 443
InterfaceAlias : Ethernet
SourceAddress : 10.0.0.15
TcpTestSucceeded : False
Finally, confirm your evidence directory exists and hashes were written.
Linux:
ls -1 "$IRDIR" | sed -n '1,10p'
Windows:
Get-ChildItem $IRDir | Select-Object Name,Length | Format-Table -AutoSize
Common pitfalls
You blocked your own management session first
Mistake: adding DROP/block all before the allow rule for SSH or WinRM. Symptom: your remote shell freezes immediately and reconnects fail. Fix: use an out-of-band console, then insert the allow rule at the top before any deny-all rule.
You relied on a cloud firewall only, but the host still talks internally
Mistake: changing only the perimeter security group while the host can still reach same-subnet peers or existing sessions. Symptom: ss -pantuu or Get-NetTCPConnection still shows active internal connections. Fix: apply host firewall isolation as well, not just cloud-side rules.
You rebooted to "clear the malware"
Mistake: restarting before capturing volatile data. Symptom: no memory image, no live connections, and process start times reset around boot. Fix: stop and collect what remains; on the next incident, capture process/network state and memory before any reboot.
You exported logs after isolation but not before disk filled up
Mistake: memory acquisition or tarball creation consumed the last free space. Symptom: errors like No space left on device or partial .evtx exports. Fix: write evidence to a mounted volume with headroom, or collect only volatile state first and skip bulky archives until storage is available.
Your firewall rule allowed the wrong direction or wrong port
Mistake: allowing inbound 22 but forgetting outbound established traffic, or allowing WinRM 5985 when the host uses HTTPS 5986. Symptom: TCP handshake starts then stalls, or Invoke-Command returns access/transport errors. Fix: add ESTABLISHED,RELATED on Linux and match the actual management port in the allow rule.
You did not hash what you collected
Mistake: copying artifacts without recording checksums. Symptom: later you cannot prove whether a file changed in transit or during analysis. Fix: run sha256sum or Get-FileHash -Algorithm SHA256 immediately after each acquisition and save the output in the same evidence directory.
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI