Registry Parser
All articles

PuTTY and WinSCP saved sessions: the RegRipper putty plugin

8 min read

SSH and SFTP clients are the quiet half of a lateral-movement case. An attacker or a curious insider who reaches internal hosts from a Windows box almost always does it through PuTTY or WinSCP, and both of them write their saved sessions straight into the user's NTUSER.DAT. The RegRipper putty and winscp plugins pull those saved sessions out: hostnames, ports, usernames, and — in PuTTY's case — the SSH host keys that distinguish a profile someone merely typed from a host someone actually connected to. That last distinction is the whole point of this artifact, and it is the thing analysts most often miss.

PuTTY: where the sessions live

PuTTY stores everything under HKCU\Software\SimonTatham\PuTTY in NTUSER.DAT. Saved sessions sit beneath:

HKCU\Software\SimonTatham\PuTTY\Sessions\<session name>

Each session is a subkey, and the subkey name is the label the user gave the profile — "prod-db", "jumphost-dmz", "10.0.4.12", whatever they typed into the saved-sessions box. One operational wrinkle worth knowing: PuTTY percent-encodes characters that are illegal in a registry key name, so a session named web 01 is stored as web%2001. A decoder that does not URL-decode the key name will show you the raw encoded string. The parser on this site decodes it before display; if you are reading the raw hive, do the decode in your head or your tooling.

Inside each session subkey, the values that matter for an investigation are:

  • HostName (REG_SZ): the target host. Hostname or IP, exactly as the user entered it.
  • PortNumber (REG_DWORD): the port. 22 for stock SSH; a non-22 port is itself a finding, since it often means a tunnelled or deliberately relocated service.
  • UserName (REG_SZ): the saved login user, if one was configured.

The RegRipper putty plugin walks the Sessions subkeys and prints these per session. The plugin on this site does the same and renders them as a table with the columns Session, Host, Port, User — one row per saved profile, with the session name URL-decoded. There are dozens of other values in a PuTTY session (terminal colours, keepalives, proxy settings, the configured key-exchange list), and most of them are noise for DFIR. The four above are the spine.

But a saved session, on its own, only proves intent. The user — or someone at the keyboard — created a profile pointing at a host. It does not prove a connection ever happened. For that you need SshHostKeys.

SshHostKeys is the gold

This is the part of the PuTTY artifact that earns its place in a report. Sitting next to Sessions, not under it, is:

HKCU\Software\SimonTatham\PuTTY\SshHostKeys

The values here are the cached host keys PuTTY collected during real connections. Each value name encodes the key type, the port, and the host, in a form like:

ssh-ed25519@22:10.0.4.12
rsa2@2222:jumphost.corp.local

The value data is the host's public key fingerprint material. The mechanics matter less than the meaning. PuTTY writes an SshHostKeys entry only after it has actually contacted the host and received its key during the SSH handshake. The first time you connect, PuTTY shows the "host key not cached" prompt; if the user accepts, the key is written here. There is no other code path that populates this key. You do not get an entry by saving a profile, by editing settings, or by opening and cancelling the connection dialog.

So the logic is clean: a Sessions\<name> entry says someone configured a target; an SshHostKeys entry for that host:port says someone reached it and completed enough of a handshake to cache the key. When you can pair a saved session with a matching host-key entry — same host, same port — you have moved from "they had this in their list" to "they connected here." That is a meaningfully stronger statement to put in front of an incident lead or a court.

The host:port in the value name is independent of the saved sessions, which is the useful part. A user can connect to a host they never saved a profile for — typing the address into the PuTTY box once and hitting Open. No Sessions subkey is created, but an SshHostKeys entry is. So SshHostKeys is frequently a superset of the saved-session list, and it is where you find the ad-hoc, one-off connections that an attacker enumerating a network leaves behind. Enumerate SshHostKeys, parse out every distinct host:port, and you have a list of every SSH endpoint this user's PuTTY ever shook hands with.

One honest caveat: the plugins here surface the saved-session table (host, port, user). If your tool's putty output does not separately enumerate SshHostKeys, read that key directly from the hive — its value names are plain text and need no decoding beyond splitting on @ and :. RegRipper's putty.pl is the reference for what a full extraction looks like; read the source if you want to see exactly which values it pulls.

WinSCP: the SFTP/SCP side of the same story

WinSCP is the file-transfer counterpart, and where there is one there is often the other — operators use PuTTY for the shell and WinSCP to move files. Its saved sessions live under:

HKCU\Software\Martin Prikryl\WinSCP 2\Sessions\<session name>

Note the literal WinSCP 2 with the space and the version digit; that has been the path for years and is easy to fat-finger in a grep. Each session subkey carries:

  • HostName: the remote target.
  • UserName: the login account.
  • PortNumber: the port.

The winscp plugin walks these subkeys and emits a table with columns Session, Host, User, Port — note the column order differs from the PuTTY table; same data, different layout. WinSCP sessions also tend to carry remote-directory state — the last remote path the session was pointed at — which tells you not just which host but which directory tree the user was working in. That is the kind of detail that turns a host list into a narrative: "connected to the finance file server and was sitting in /exports/payroll."

WinSCP does not maintain a separate host-key cache in the registry the way PuTTY does by default, so you usually cannot make the same airtight "they connected" claim from WinSCP alone — treat the WinSCP session list as configured targets and corroborate the actual connection elsewhere.

A note on saved passwords

WinSCP can store credentials inside the session. When a session contains a saved password, flag it. Its mere presence is a finding: it tells you the user opted to persist credentials for that remote host, which is both a security-posture observation and a signal that this was a routine, repeated destination rather than a one-off. How those stored secrets can or cannot be recovered is out of scope here; for the investigation, what matters is that a credential was saved and for which host and user.

The lateral-movement story

Put the two artifacts side by side and you reconstruct a user's remote-access footprint from a single hive. The questions they answer:

  • Which internal hosts did this user reach? Every distinct HostName across PuTTY and WinSCP sessions, plus every host in SshHostKeys, is a target the user had in hand or actually touched.
  • Jump-host enumeration. Names like jumphost, bastion, or dmz in the session list, combined with SshHostKeys entries for hosts on internal RFC 1918 ranges, sketch the pivot path: workstation, to bastion, to the internal estate.
  • Insider scope. For a departing-employee or data-exfil case, the WinSCP session list — with its remote directories and any saved-password flags — shows exactly which file servers and paths the person was working against.
  • Non-22 ports and odd usernames. A session on port 2222 with user root against an internal IP is a different risk story than port 22 with the user's own domain account.

Pairing with other artifacts

These plugins are strongest when correlated. Cross-reference the host list against the networklist plugin: the networks the machine joined tell you which segment it was on when the SSH connections happened, which constrains where a given internal IP was reachable from. And check TypedPaths and TypedURLs — internal hostnames and admin shares (\\fileserver01\c$) typed into Explorer often name the same hosts that show up in the SSH session list, confirming the target set from a second, independent artifact.

Tools

  • RegRipper's putty plugin (source). Reads the saved sessions and the SSH host keys. The companion winscp plugin does the WinSCP Sessions tree.
  • Eric Zimmerman's Registry Explorer / RECmd. Browse Software\SimonTatham\PuTTY and Software\Martin Prikryl\WinSCP 2 directly; the bookmarks cover the common values.
  • You can analyze NTUSER.DAT in your browser here — the putty_sessions and winscp plugins render both session tables, with PuTTY session names URL-decoded.

For the full list of what RegRipper extracts from a user hive, see the RegRipper plugins reference.

A saved PuTTY profile is a hint. A matching SshHostKeys entry is proof of a connection. Keep the two separate in your notes, enumerate the host keys for the connections that never got saved as profiles, and pair the resulting host list with networklist and typedpaths. That combination turns a single NTUSER.DAT into a map of everywhere the user pointed their SSH client — and, for the hosts with cached keys, everywhere they actually went.