SSH and RDP
SSH and RDP are the two dominant remote access protocols in enterprise environments — and both are high-value targets for attackers. SSH carries interactive shell sessions, file transfers (SCP/SFTP), and port forwarding (tunneling). RDP provides full graphical desktop access. Neither can be decrypted without the server's private key or pre-shared session key. But both leave metadata fingerprints: version strings, key exchange algorithms, cipher selections, login success/failure, session duration, and data volume that characterize the session without decrypting it.
An insider threat suspect is SSH-tunneling data out of the network. They can't exfiltrate via HTTP (blocked by DLP), so they open an SSH connection to an external server and use SSH local port forwarding to tunnel all traffic through it. In PCAP you see: a sustained SSH session to an external IP, then a spike in data volume — 2.3 GB transferred out over 4 hours. The SSH metadata shows the session was established with the corporate username. You can't see what was transferred, but you can prove data moved out via SSH tunneling.
SSH Forensic Analysis
SSH Handshake — Visible Metadata
═══════════════════════════════════════════════════════════════════
TCP Connection: client → server:22
SSH Banner Exchange (PLAINTEXT):
├── Client: SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.5
└── Server: SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1
└── Version strings identify software and OS
Key Exchange (PLAINTEXT message types 20-49):
├── SSH_MSG_KEXINIT (type 20): algorithm negotiation
│ key_exchange_algorithms: ecdh-sha2-nistp256,diffie-hellman-group14-sha256
│ server_host_key_algorithms: ssh-rsa,ecdsa-sha2-nistp256
│ encryption_c2s / encryption_s2c: aes128-ctr, aes256-gcm
│ mac: hmac-sha2-256
├── ECDH/DHE key exchange messages (type 30-32)
└── SSH_MSG_NEWKEYS (type 21): signals encryption start
After NEWKEYS: everything encrypted
Forensic metadata still visible:
├── Client/server software versions (banner)
├── Key exchange algorithms chosen
├── Server host key fingerprint (type 6 = key exchange reply)
├── Session duration (TCP open → close timestamps)
├── Data volume in each direction (bytes transferred)
└── Whether authentication succeeded (session established vs TCP RST after banner)
#!/bin/bash
PCAP="$1"
echo "=== SSH connections ==="
tshark -r "$PCAP" -n -Y "ssh" -T fields \
-E separator="\t" \
-e ip.src -e ip.dst -e tcp.dstport \
| sort | uniq -c | sort -rn | head -20
echo ""
echo "=== SSH version banners (client software identification) ==="
tshark -r "$PCAP" -n -Y "ssh.protocol" -T fields \
-E separator="\t" \
-e ip.src -e ip.dst -e ssh.protocol \
| sort | uniq -c | sort -rn | head -20
echo ""
echo "=== SSH key exchange algorithms ==="
tshark -r "$PCAP" -n -Y "ssh.kex_algorithms" -T fields \
-E separator="\t" \
-e ip.src -e ip.dst -e ssh.kex_algorithms -e ssh.encryption_algorithms_client_to_server \
| head -30
echo ""
echo "=== SSH sessions with large data transfers (exfil indicator) ==="
tshark -r "$PCAP" -n -Y "tcp.port==22" -T fields \
-E separator="\t" \
-e ip.src -e ip.dst -e tcp.len \
| awk -F'\t' '{sum[$1"\t"$2]+=$3} END {for(k in sum) print sum[k]"\t"k}' \
| sort -rn | head -20
echo ""
echo "=== SSH to non-standard ports (evasion) ==="
tshark -r "$PCAP" -n -Y "ssh and not tcp.port==22" -T fields \
-E separator="\t" \
-e ip.src -e ip.dst -e tcp.dstport \
| sort | uniq -c | sort -rn | head -10
echo ""
echo "=== Brute-force indicators (many short SSH sessions) ==="
tshark -r "$PCAP" -n -Y "tcp.port==22 and tcp.flags.syn==1 and not tcp.flags.ack" \
-T fields -e ip.src \
| sort | uniq -c | sort -rn | head -10
RDP Forensic Analysis
RDP Forensic Metadata — Visible Before Encryption
═══════════════════════════════════════════════════════════════════
TCP Connection: client → server:3389
RDP Connection Request (TPKT + X.224 + MCS layer, partially plaintext):
├── Cookie: "mstshash=USERNAME" — the username attempting to connect
│ Visible in first X.224 Connection Request packet
└── Requested protocol: RDP Security=0, TLS=1, NLA=2
NLA (CredSSP) means auth happens outside RDP → no username visible
CredSSP Negotiation (if NLA is used):
└── NTLM or Kerberos negotiation visible before TLS
After CredSSP/TLS: everything encrypted
RDP visible metadata:
├── Cookie with username (unless NLA with Kerberos)
├── Requested client capabilities (screen size, color depth)
├── Session start/end time
├── Data volume (heavy: interactive session; light: disconnected keepalive)
└── Certificate presented by server (may identify server)
Forensic detection opportunities:
├── Failed RDP logins: connection + disconnection after CredSSP = auth failure
├── RDP brute force: many short sessions to same host
├── RDP tunneling: port 3389 traffic from unexpected source IPs
└── RDP session duration: interactive = long duration; automated = short
#!/bin/bash
PCAP="$1"
echo "=== RDP connection attempts ==="
tshark -r "$PCAP" -n -Y "rdp" -T fields \
-E separator="\t" \
-e ip.src -e ip.dst \
| sort | uniq -c | sort -rn | head -20
echo ""
echo "=== RDP cookies (usernames in connection request) ==="
# Cookie is in X.224 Connection Request, before TLS
tshark -r "$PCAP" -n -Y "rdp.rt==0xe0" -T fields \
-E separator="\t" \
-e frame.time_epoch -e ip.src -e ip.dst -e rdp.cookie \
| head -30
echo ""
echo "=== RDP brute force (many connections to same destination) ==="
tshark -r "$PCAP" -n \
-Y "tcp.dstport==3389 and tcp.flags.syn==1 and not tcp.flags.ack" \
-T fields -e ip.src -e ip.dst \
| sort | uniq -c | sort -rn | head -20
echo ""
echo "=== External RDP connections (non-RFC1918 source) ==="
tshark -r "$PCAP" -n \
-Y "tcp.dstport==3389 and not ip.src matches '^(10|172\.(1[6-9]|2[0-9]|3[01])|192\.168)'" \
-T fields \
-E separator="\t" \
-e frame.time_epoch -e ip.src -e ip.dst \
| sort | uniq -c | sort -rn | head -20
Standard RDP runs on TCP 3389. Many organizations block inbound 3389 from the internet but permit outbound connections. Attackers running RDP on alternative ports (443, 8443, 3390, high-random ports) do so to bypass firewall rules. In network forensics: query for TCP connections where the server sent a TLS certificate but the port is not 443 — if the certificate CN or SANs don't match an expected service, this may be RDP over a non-standard port. Zeek's service detection identifies RDP by protocol content regardless of port number: service == "rdp" in Zeek conn.log will flag RDP even on port 443. This makes Zeek's protocol detection more reliable than port-based detection for remote access protocols.
Q & A
Q: An attacker used SSH tunneling to bypass our web proxy. What evidence is in PCAP?
SSH local port forwarding creates a listening port on the client machine; traffic sent to that local port travels through the SSH tunnel to the server, which then forwards it to the target. In PCAP, you'll see: (1) One SSH connection: from the attacker's workstation to an external server on port 22 (or disguised port). This is the tunnel. (2) No direct HTTP/HTTPS connections: the tunneled traffic doesn't appear as separate TCP connections — it's all carried inside the SSH session's encrypted data. (3) Data volume spike in the SSH session: if the attacker is browsing the web or exfiltrating data through the tunnel, the SSH session data volume will be much higher than a normal interactive shell session. A typical SSH interactive session transfers a few MB. An SSH tunnel used for web browsing for an hour might transfer 500 MB. (4) Session duration: tunneling sessions are long (hours) while normal SSH sessions are shorter unless it's a background daemon. Detection strategy: alert on SSH sessions over 1 hour and/or transferring more than 100 MB of data.