Windows SSH Misconfiguration Discovery Tool — Map lateral movement paths through misconfigured SSH services in Active Directory environments.
Windows servers and workstations can run SSH services — whether it's the built-in OpenSSH Server (Server 2019+), Bitvise SSH Server, an IBM mainframe SSH bridge, or any number of third-party SFTP solutions. When these services are misconfigured (and the defaults are generous), the result is the same:
- Any authenticated domain user can log in. Regular users, service accounts, computer accounts. No special group membership required.
- On default Windows OpenSSH installations (pre-Server 2025), GPO "Deny Logon" policies are ignored entirely by the SSH daemon.
- SSH logon events use Type 8 (NetworkCleartext), which may not be covered by existing detection rules depending on the environment.
These SSH access paths are invisible to standard BloodHound collection. WinSSHound finds them.
- Discovers targets with SSH enabled (via LDAP, manual targeting, or nmap XML import)
- Tests SSH authentication with provided credentials across multiple ports
- Reads
sshd_configto determine who can actually SSH in (--invasivemode) - Generates BloodHound CE OpenGraph JSON with
WinSSH_CanSSHedges - Uploads findings to BloodHound CE for attack path visualization
# Install
git clone https://github.com/1r0BIT/WinSSHound.git
cd WinSSHound
pip install -e .
# Basic scan
winsshound -u cloud.strife -p 'Buster$word97!' -d shinra.local -t reactor01.shinra.local
# Auto-discover targets via LDAP, scan multiple ports
winsshound -u cloud.strife -p 'Buster$word97!' -d shinra.local \
--auto-targets --dc-ip 10.0.0.1 --port 22,2222,8022
# Invasive mode — read sshd_config to determine actual access policy
winsshound -u cloud.strife -p 'Buster$word97!' -d shinra.local \
--auto-targets --dc-ip 10.0.0.1 --invasive --no-confirm
# Full scan with BloodHound CE upload
winsshound -u cloud.strife -p 'Buster$word97!' -d shinra.local \
--auto-targets --dc-ip 10.0.0.1 --invasive --no-confirm \
--bh-url http://localhost:8080 --bh-username admin --bh-password 'bhpass'
# Import nmap results (targets + ports from service scan)
nmap -sV -p- --open -oX ssh_scan.xml 192.168.1.0/24
winsshound -u cloud.strife -p 'Buster$word97!' -d shinra.local --nmap-xml ssh_scan.xmlRequires Python 3.11+ and a Linux attack box.
This is the flag that matters. Without it, you learn "this user can SSH here." With it, WinSSHound reads sshd_config from each target and determines whether access is restricted or wide open. The edges change accordingly:
- Default sshd_config (no restrictions) -> Edge from
Authenticated Usersto Computer - AllowGroups configured -> Edge from each allowed group to Computer
- AllowUsers configured -> Edge from each allowed user to Computer
--invasive executes a command on the target (reading the config file), so it comes with an OPSEC warning and confirmation prompt. Use --no-confirm to skip the prompt in scripts.
Policy parsing only covers OpenSSH-syntax configs (Microsoft's OpenSSH for Windows + Cygwin / copssh). Bitvise stores its policy in the registry; Tectia uses its own sshd2_config syntax; MFT products like Serv-U, MOVEit, GoAnywhere and Globalscape EFT are GUI / DB administered. WinSSHound still detects these from the SSH banner and tags every CanSSH edge with an ssh_product property — but for non-OpenSSH products, the edge stays at user→computer (the scanning user's access path) and is flagged policy_unparseable: true so you know you're looking at it blind. See docs/bloodhound-integration.md for the full product table.
The parser does not follow Include directives (Windows OpenSSH 9.5+) — if the real access policy lives in included config fragments, the tool overstates access. Check for Include lines manually if results look too permissive.
For the most accurate results, scan with the lowest-privilege account you have:
- Bare domain user (member of Domain Users only): If this account can SSH in, everyone can.
- Computer account: If MachineAccountQuota is > 0, create one with
ImpacketorPowerMad. A computer account is about as unprivileged as it gets — unless Domain Computers has been granted additional rights, in which case the customer has bigger problems than SSH.
Use a separate privileged account for --auto-targets LDAP discovery if needed.
| Guide | Description |
|---|---|
| BloodHound Integration | Setup, --invasive edge semantics, starter queries |
| OpenGraph Nodes & Edges | WinSSH_CanSSH edge reference — abuse info, remediation, OPSEC |
| OPSEC Considerations | Protocol impact, detection footprint, scan profiles |
| Starter Queries | Pre-built Cypher queries for BloodHound CE |
| Attack Path Diagram | Import into arrows.app |
Usage: winsshound [-h] [-V] -u USERNAME -p PASSWORD -d DOMAIN [-t TARGET]
[--targets-file FILE] [--nmap-xml FILE] [--auto-targets]
[--dc-ip HOST] [--ldaps] [--resolve-hostnames] [--ns IP]
[--port PORTS] [--timeout TIMEOUT] [--threads THREADS] [-v]
[--invasive] [--no-confirm]
[--bh-url URL] [--bh-username USER] [--bh-password PASS]
[--bh-api-key KEY] [--bh-api-key-id ID] [--no-upload]
[--allow-orphans] [--output FILE] [--json-only]
Authentication:
-u, --username Username for SSH authentication
-p, --password Password for SSH authentication
-d, --domain Domain name (e.g., corp.local)
Targets:
-t, --target Target(s): single IP, comma-separated, CIDR, or hostname
--targets-file File containing targets (one per line)
--nmap-xml Import targets from nmap XML (hosts with open SSH ports)
--auto-targets Discover Windows computers via LDAP
--dc-ip Domain controller IP for LDAP queries
--ldaps Use LDAPS (port 636) instead of LDAP
--resolve-hostnames Resolve IP addresses to hostnames via reverse DNS
--ns DNS server for reverse lookups
Options:
--port SSH port(s), comma-separated (default: 22)
--timeout Connection timeout in seconds (default: 5)
--threads Number of parallel threads (default: 10)
--invasive Read sshd_config to determine who can actually SSH in
--no-confirm Skip confirmation prompts
-v, --verbose Enable verbose output
BloodHound Integration:
--bh-url BloodHound CE URL (default: http://localhost:8080)
--bh-username BloodHound username
--bh-password BloodHound password
--bh-api-key BloodHound API key
--bh-api-key-id BloodHound API key ID
--no-upload Generate JSON but don't upload
--allow-orphans Include edges with placeholder SIDs
Output Options:
--output Write OpenGraph JSON to file
--json-only Only output JSON, skip console table
- Add
AllowGroupsorAllowUserstosshd_config— This is the direct fix for default-open SSH access. Applies to OpenSSH; third-party SSH servers have their own ACL mechanisms. - Use the OpenSSH Users group (Server 2025+) — Verify the group name matches your locale. Non-English Windows localizes the group name but the config references the English one.
- Monitor Type 8 logon events (Event ID 4624) — SSH's logon type. Worth adding to detection rules if not already present.
- Disable SSH services that aren't in use — Applies to both built-in OpenSSH and third-party SSH/SFTP servers.
- Network segmentation — Can't attack what you can't reach. Restrict SSH ports between VLANs that don't need SSH connectivity.
- Use SSH on Windows, they said... — Evgenij Smirnov (Part 1)
- SSH in Windows Server: Better, Still Not Good Enough — Evgenij Smirnov (Part 2)
Contributions welcome. Please feel free to submit a Pull Request.
MIT License — see LICENSE.
0xr0BIT
WinSSHound is intended for authorized security testing and research only. Always obtain proper authorization before testing.