Analyzing the Current SSH Threat Landscape
During a recent audit of a Tier-1 Indian financial service provider's infrastructure, I discovered over 4,500 unique public keys across 300 instances. None of these keys had expiration dates, and 15% belonged to employees who had left the firm over two years ago. This is the reality of "SSH Key Sprawl." While most teams focus on firewall rules, the lack of identity lifecycle management for SSH keys creates a permanent back door into the environment. Static keys are effectively "infinite passwords" that bypass modern IAM controls and Multi-Factor Authentication (MFA) if not managed correctly.
We are also seeing a resurgence in sophisticated SSH-based attacks. CVE-2024-6387, known as "regreSSHion," highlighted a signal handler race condition in OpenSSH's server (sshd). This vulnerability allows unauthenticated remote code execution as root on glibc-based Linux systems. My testing showed that while the exploitation window is narrow, automated scanning tools now target this specifically. Relying on default configurations in this environment is a liability. We must move toward a short-lived, identity-bound access model using Certificate Authorities (CA) rather than static key pairs.
In the Indian context, the Digital Personal Data Protection (DPDP) Act 2023 and CERT-In's April 2022 directions mandate strict identity attribution and log maintenance for 180 days. Static SSH keys fail the identity attribution test because they are often shared via insecure channels like Slack or WhatsApp (the "Shared PEM" problem). Implementing an SSH CA allows us to sign keys with specific metadata, ensuring every session is tied to a verified identity and a specific ticket or reason, often managed through a platform for secure SSH access for teams.
Essential Server-Side Hardening
Hardening the sshd_config file is the first line of defense. Most default installations are optimized for compatibility, not security. I recommend moving away from the default port 22. While this does not stop a determined attacker, it drastically reduces the noise in your logs from automated "script kiddie" bots. In my experience, shifting to a high-numbered port like 2222 or 4433 reduces brute-force attempts by over 95%, allowing SIEM, log monitoring, and threat detection systems to focus on high-fidelity signals.
Configuring Secure SSH Defaults
The following configuration directives should be standard across your fleet. We disable root login entirely and enforce the use of sudo for administrative tasks to maintain an audit trail. We also limit the authentication window to prevent long-running brute-force processes from tying up server resources.
# Edit /etc/ssh/sshd_config
Port 2222 Protocol 2 PermitRootLogin no MaxAuthTries 3 LoginGraceTime 30 PermitEmptyPasswords no PasswordAuthentication no PubkeyAuthentication yes IgnoreRhosts yes HostbasedAuthentication no
After modifying the configuration, always validate the syntax before restarting the service. A single typo can lock you out of a remote instance. I use the test mode flag to verify changes:
# Test configuration syntaxsudo sshd -t
If no errors, restart the service
sudo systemctl restart ssh
Enforcing Protocol 2 and Modern Ciphers
Older SSH protocols and weak ciphers are susceptible to man-in-the-middle (MITM) attacks. We must explicitly define the allowed KEX (Key Exchange) algorithms and Ciphers to exclude legacy options like 3DES or MD5-based MACs. I prefer the following hardened set for modern environments:
KexAlgorithms [email protected],diffie-hellman-group-exchange-sha256
Ciphers [email protected],[email protected],[email protected] MACs [email protected],[email protected]
Transitioning from RSA to Ed25519
Many organizations still default to RSA 2048-bit keys. While not inherently "broken," RSA is slower and requires larger key sizes (4096-bit) to maintain security parity with modern elliptic curve algorithms, as noted in various NIST NVD vulnerability reports. I recommend Ed25519 for all new deployments. It offers higher security, better performance, and shorter keys that are easier to handle.
Generating and Securing Ed25519 Keys
When generating keys, I always enforce a passphrase. A private key without a passphrase is a plaintext credential waiting to be exfiltrated. We use the -a flag to increase the number of KDF (Key Derivation Function) rounds, making the private key more resistant to offline brute-force attacks if the file is stolen.
# Generate a high-security Ed25519 key pair
ssh-keygen -t ed25519 -a 100 -C "user@internal-dev-env"
For existing RSA keys, ensure they are at least 4096 bits. You can audit the key lengths of your current authorized_keys files using this command:
# Audit existing keys on a server
ssh-keygen -l -f ~/.ssh/authorized_keys
The Risks of Agent Forwarding
CVE-2023-38408 demonstrated that ssh-agent forwarding can be exploited if you connect to a compromised remote host. The remote host can use your forwarded agent to sign requests, effectively impersonating you on other systems. I advise using ProxyJump (the -J flag) instead of agent forwarding. This encapsulates the connection without exposing your local agent to the intermediate host.
# Securely jump through a bastion host
ssh -J bastion.example.com internal-server.lan
Implementing an SSH Certificate Authority (CA)
The most effective way to eliminate key sprawl and improve security is to stop using authorized_keys files entirely. Instead, we use an SSH CA. In this model, the server trusts the CA's public key. Users present a certificate signed by the CA. These certificates are short-lived (e.g., 8 hours), meaning even if a developer's machine is compromised, the stolen certificate will expire by the end of the shift.
Step 1: Creating the CA Key Pair
The CA key should be stored on a dedicated, offline machine or a Hardware Security Module (HSM). For this demonstration, we will generate it on a secure management node. We do not use a passphrase for the CA key in automated pipelines, but the file permissions must be strictly 600.
# Create the CA key pair
ssh-keygen -t ed25519 -f /etc/ssh/ssh_ca_key -C "Internal_SSH_CA" -N ""
Step 2: Configuring Servers to Trust the CA
Distribute the ssh_ca_key.pub (public part only) to all servers in your fleet. You then update the sshd_config to trust this CA for all incoming user authentications. This eliminates the need to manage individual keys on every server.
# On the target server:echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... Internal_SSH_CA" > /etc/ssh/trusted-user-ca-keys.pem
Add to /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pem
Step 3: Signing User Certificates
When a user needs access, they provide their public key. The CA signs it and returns a certificate. We can restrict the certificate to specific usernames and set a strict expiration time. I use the -V flag to set a +8h validity window.
# Sign a user's public key
ssh-keygen -s /etc/ssh/ssh_ca_key -I "access_request_12345" -n "ubuntu,admin-user" -V +8h -z 1 id_ed25519.pub
The user now has a file named id_ed25519-cert.pub. They can inspect the certificate details to verify the constraints:
# Inspect the certificate
ssh-keygen -L -f id_ed25519-cert.pub
The output will show the "Principals" (allowed users), "Valid" (time window), and "Extensions" (like permit-pty). This provides granular control that static keys simply cannot match.
Advanced Hardening with MFA and Fail2Ban
For critical production systems, a single factor (the SSH key/cert) is insufficient. We must implement Multi-Factor Authentication. On Linux, this is typically done via PAM (Pluggable Authentication Modules) and Google Authenticator or a hardware token like a YubiKey.
Configuring MFA for SSH
First, install the PAM module. In an Indian enterprise context, where mobile-based TOTP is common, this is a cost-effective way to meet the MFA requirements of the DPDP Act. However, administrators must remain vigilant against MFA proxy and AiTM attacks that can bypass traditional second factors.
sudo apt install libpam-google-authenticator
Update /etc/pam.d/sshd to include the requirement for the authenticator. You must place this after the standard system authentication includes.
# Add to /etc/pam.d/sshd
auth required pam_google_authenticator.so
Finally, update sshd_config to require both the public key AND the PAM code. This "Multiple Authentication" flow is significantly more secure than either factor alone.
# In /etc/ssh/sshd_config
AuthenticationMethods publickey,keyboard-interactive
Automated Brute-Force Mitigation with Fail2Ban
Fail2Ban monitors /var/log/auth.log and dynamically updates iptables rules to ban IP addresses that exhibit suspicious behavior. For a server exposed to the public internet, this is mandatory. I use the following configuration in /etc/fail2ban/jail.local:
[sshd]
enabled = true port = 2222 filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 3600 findtime = 600
AWS EC2 Specific SSH Security
In AWS environments, the risk often comes from overly permissive Security Groups (0.0.0.0/0 on port 22). We should treat Security Groups as the first layer of a Zero Trust model. If your team has a static office IP in Bengaluru or Mumbai, whitelist only those CIDR blocks.
Leveraging AWS Systems Manager (SSM)
The most secure way to manage EC2 instances is to stop opening port 22 entirely. AWS Systems Manager (SSM) Session Manager allows you to access instances via the AWS CLI or Console without an open inbound SSH port. This uses the IAM role of the instance and the user to authorize access, providing a full audit log in AWS CloudTrail.
# Connect via SSM (No SSH port needed)
aws ssm start-session --target i-0123456789abcdef0
If you must use SSH, use the EC2 Instance Connect (EIC) service. It allows you to push a one-time-use public key to the instance's metadata service, which is valid for only 60 seconds. This effectively implements a "Just-in-Time" (JIT) access model.
SIEM Integration and Monitoring
Hardening is useless without visibility. We need to ingest SSH logs into a SIEM like Wazuh, ELK, or Splunk. To get high-fidelity logs, we must increase the logging verbosity in sshd_config. VERBOSE level logs include the fingerprint of the key used, which is critical for incident response.
# In /etc/ssh/sshd_config
SyslogFacility AUTHPRIV LogLevel VERBOSE
Monitoring with Wazuh
Wazuh provides out-of-the-box rules for SSH. To ensure the agent is picking up the correct logs, verify the ossec.conf configuration. In an Indian regulatory environment, this setup helps satisfy the 180-day log retention requirement by shipping logs to a centralized, backed-up storage layer.
# Wazuh Agent Configuration (ossec.conf)
syslog /var/log/auth.log
You can use a simple awk script to perform a quick audit of your logs for failed attempts or unusual logins. This is useful for a "quick look" before diving into the SIEM dashboard.
# Extract key authentication events
grep -E "Accepted|Failed|Invalid" /var/log/auth.log | awk '{print $1, $2, $3, $9, $11}'
Detecting Certificate Usage in Logs
When using an SSH CA, the logs will look different. You will see the ID provided during signing (the -I flag). This is where identity attribution becomes powerful. Instead of seeing "Accepted publickey for user1", you see "Accepted certificate ID access_request_12345". This ID can be cross-referenced with your internal ticketing system (Jira/ServiceNow) to prove the access was authorized.
Auditing and Continuous Maintenance
Security is a state, not a goal. We must automate the auditing of our SSH configurations. I use Ansible to enforce the sshd_config state across the fleet. If a developer manually changes a setting to "debug" and forgets to revert it, Ansible will correct it within the next execution cycle.
# Ansible snippet for SSH hardening
- name: Enforce SSHD security settings
lineinfile: path: /etc/ssh/sshd_config regexp: "^#?{{ item.key }}" line: "{{ item.key }} {{ item.value }}" with_items: - { key: "PermitRootLogin", value: "no" } - { key: "PasswordAuthentication", value: "no" } - { key: "LogLevel", value: "VERBOSE" } notify: Restart SSH
Regularly check for revoked certificates. If a CA-issued certificate needs to be invalidated before it expires, you must use a Key Revocation List (KRL). The server must be configured to check this list for every connection.
# Update /etc/ssh/sshd_config to use a KRLRevokedKeys /etc/ssh/revoked_keys
Generate a KRL from a compromised certificate
ssh-keygen -k -f /etc/ssh/revoked_keys compromised-cert.pub
Check the status of your SSH service and version regularly. Given the severity of vulnerabilities like regreSSHion, keeping the openssh-server package updated is the most critical maintenance task. In Ubuntu/Debian, we automate this with unattended-upgrades focused specifically on security patches.
# Check current OpenSSH versionssh -V
Manually trigger security updates
sudo apt-get update && sudo apt-get install --only-upgrade openssh-server
Next Command: ssh-keygen -s /etc/ssh/ssh_ca_key -h -I "server_hostname" -n "server.internal.lan" -V +52w ssh_host_ed25519_key.pub — Use this to sign host keys to eliminate "Unknown Host" warnings for your users.
