The Problem
During incident response, you have an attacker IP and need to reconstruct their complete session history -- every connection, authentication attempt, session open, command execution (if logged), and disconnect. These events are spread across thousands of lines in auth.log, interleaved with legitimate traffic.
grep pulls the individual lines, but you lose the session structure. You need to see the progression: connection accepted, session opened, sudo attempted, session closed. sed's address ranges and hold space let you extract these multi-line blocks intact.
Extracting Session Blocks
An SSH session in auth.log follows a predictable pattern: it starts with a connection or authentication line and ends with a session closed or disconnect line. For a specific IP:
#!/bin/bash
# extract_sessions.sh -- Reconstruct SSH sessions for a given IP
# Usage: ./extract_sessions.sh 203.0.113.45 /var/log/auth.log
ATTACKER_IP="${1:?Usage: $0 <ip> [logfile]}"
LOGFILE="${2:-/var/log/auth.log}"
SESSION_NUM=0
echo "=== SSH Session Timeline for $ATTACKER_IP ==="
echo ""
# Phase 1: Extract all lines for this IP, preserving order
grep "$ATTACKER_IP" "$LOGFILE" | sed -nE '
# Match session start patterns
/Accepted (password|publickey)|Connection from/ {
# Print session separator
i\--- SESSION START ---
# Start printing until session end
:collect
p
n
# Continue printing lines that reference the session
/session closed|Disconnected from|Connection closed|pam_unix.*session.*closed/ {
p
i\--- SESSION END ---
i\
b
}
# If we hit another session start, print separator and loop
/Accepted (password|publickey)|Connection from/ {
i\--- SESSION END ---
i\--- SESSION START ---
b collect
}
# Print intermediate lines (sudo, commands, etc)
b collect
}
'
The :collect label creates a loop. After matching a session start, sed reads lines one at a time with n, printing each until it hits a session-end pattern. The b collect branches back to continue reading.
Adding Timeline Context with Hold Space
The hold space lets sed remember data across lines. We can use it to track timing:
#!/bin/bash
# session_timeline.sh -- Build annotated session timeline
ATTACKER_IP="${1:?Provide attacker IP}"
LOGFILE="${2:-/var/log/auth.log}"
grep "$ATTACKER_IP" "$LOGFILE" | sed -nE '
# Store first timestamp in hold space for duration calc later
/Accepted|Connection from/ {
# Save the timestamp portion to hold space
h
s/^([A-Z][a-z]{2} +[0-9]+ [0-9:]+).*/[SESSION START: \1]/
p
x
p
x
d
}
# Annotate key events
/Failed password/ {
s/^([A-Z][a-z]{2} +[0-9]+ [0-9:]+).*Failed password for (.*) from.*/ [\1] AUTH_FAIL: \2/
p
d
}
/Accepted/ {
s/^([A-Z][a-z]{2} +[0-9]+ [0-9:]+).*Accepted (\w+) for (\w+).*/ [\1] AUTH_OK: \3 via \2/
p
d
}
/sudo:/ {
s/^([A-Z][a-z]{2} +[0-9]+ [0-9:]+).*sudo: +([^ ]+).*/ [\1] SUDO: \2/
p
d
}
/session closed|Disconnected/ {
s/^([A-Z][a-z]{2} +[0-9]+ [0-9:]+).*/[SESSION END: \1]/
p
# Retrieve start timestamp from hold space for comparison
x
s/^([A-Z][a-z]{2} +[0-9]+ [0-9:]+).*/ (started: \1)/
p
x
d
}
'
Sample output:
[SESSION START: Mar 15 09:23:41]
Mar 15 09:23:41 webserver sshd[28104]: Accepted password for deploy from 203.0.113.45 port 52341 ssh2
[Mar 15 09:23:41] AUTH_OK: deploy via password
[Mar 15 09:24:02] SUDO: deploy
[Mar 15 09:31:17] SUDO: deploy
[SESSION END: Mar 15 09:45:33]
(started: Mar 15 09:23:41)
You now have a structured timeline showing exactly what the attacker did during each session -- authentication method, privilege escalation attempts, and session duration.
Handling Orphaned Sessions
Sometimes sessions do not close cleanly (network drops, kill -9). Add a timeout heuristic:
# Find sessions that started but never closed
grep "$ATTACKER_IP" "$LOGFILE" | sed -nE '
/Accepted|session opened/ {
h
p
:wait
n
/session closed|Disconnected/ {
p
b
}
# If we hit EOF without closing, flag it
$ {
p
i\[WARNING: Session never closed - possible persistence or crash]
x
s/^/ (opened: /
s/$/)/
p
b
}
p
b wait
}
'
Orphaned sessions are a red flag -- the attacker may still have access, or they crashed a service on exit. Either way, it goes in your report.
Putting It Together
Combine session extraction with the redaction script from the blog post before sharing externally. Extract sessions first (preserving structure), then pipe through redaction:
./extract_sessions.sh 203.0.113.45 /var/log/auth.log \
| sed -E -f redaction_rules.sed > attacker_timeline_sanitized.txt
This gives your IR firm a clean, structured attacker timeline without leaking internal infrastructure details.
Top comments (0)