DEV Community

José David Ureña Torres
José David Ureña Torres

Posted on

How SSH Works—and How It Breaks: A Practical Guide to Secure Remote Access

Introduction

SSH (Secure Shell) is a cornerstone of modern computing. It’s no exaggeration to say that almost every software engineer and IT administrator has used an SSH client to manage a remote machine at some point of their careers. This protocol has been around since 1995 and it remains the gold standard of secure shell access.

SSH provides encrypted tunnels over insecure networks, allowing secure access to remote machines. While the protocol itself is very robust and mature, its security ultimately depends on how it is used. Misconfigurations, inexperience and lack of knowledge can be an open door for attackers.

And I’m speaking from experience. I've used SSH in insecure ways in the past, mainly because I didn’t fully understand how to protect my systems and applications correctly.

In this series of three parts, we will explore how SSH can be misused, how attackers exploit common pitfalls, and how to avoid them.

In the first part, we’ll discuss why you shouldn’t blindly trust the server you’re connecting to, and provide a high-level overview of how host key verification works.

In the second part, we'll go through a small lab exercise to demonstrate a Man-in-the-Middle (MITM) attack, showing how an attacker can capture credentials and monitor live user activity.

The last part focuses on hardening and best practices. We’ll cover practical, actionable security measures to protect your infrastructure and applications.

Let’s get started.

Why hostnames and IP addresses cannot be trusted for SSH authentication

When you SSH to a remote machine, are you sure you are connecting to the right server? You might think that double-checking the hostname or IP address eliminates the risk, but that's not true. Verifying a destination address is not enough to guarantee a server's identity.

At first glance, trusting an SSH connection based on a hostname seems secure. If you point your client toward server.example.com, it feels like you must be connecting to the right place, right? However, hostnames and IP addresses are not inherently trustworthy, especially outside private networks; they depend on multiple layers of infrastructure that can be manipulated.

For this reason, SSH security relies on cryptographic host key verification rather than simply trusting where the network appears to be routing your traffic.

Common Attack Vectors

There are different methods that can be used to redirect a connection to a malicious server without you even noticing, including:

  • DNS poisoning
  • Host file poisoning
  • Network-level and routing attacks

We will cover these attacks in the following sections.

DNS poisoning

When you connect using a hostname (e.g., ssh server.example.com), your system relies on DNS to resolve it to an IP address. Because low-level network communication requires IP addresses rather than human-readable names, this resolution step is a critical point of failure.

In a DNS poisoning attack, an actor corrupts this resolution process so that a legitimate hostname resolves to a malicious IP address. This silently redirects your SSH connection, making the redirection nearly impossible to detect if you rely solely on the hostname for trust.

Hosts file poisoning

Operating systems can override external DNS resolution using a local hosts file (e.g., /etc/hosts on Linux). If an attacker gains sufficient access to your machine, they can modify this file to force trusted hostnames to point to malicious IP addresses.

While an attacker with this level of access could likely cause even more damage to your machine, this technique remains effective because it is simple and redirects traffic without raising immediate suspicion. It is important to note that hosts file poisoning is not a network attack, but rather a post-compromise persistence or redirection technique that bypasses DNS entirely.

Network-level and routing attacks

Even if your DNS and local configurations are secure, the network path itself may be compromised. Attackers who control or compromise routers and switches can intercept or alter traffic in transit. This enables man-in-the-middle attacks, particularly on untrusted networks like public Wi-Fi or poorly secured internal LANs.

At a larger scale, global internet routing can be manipulated as well. Attacks such as Border Gateway Protocol (BGP) hijacking allow malicious actors to redirect traffic through attacker-controlled networks, even when the destination IP address seems correct. Furthermore, IP addresses themselves are not permanent guarantors of a server's identity.

In cloud environments, for example, IPs are frequently reassigned, meaning an address you trusted yesterday may belong to a completely different system today.

Host Key Verification: How SSH Verifies the Server's Identity

When you connect to a remote machine for the first time using SSH, your client likely does not know the server's public key yet. That is because the client has no record of this server, it will present a cryptographic fingerprint and ask if you want to proceed:

The authenticity of host 'ssh-lab-server (172.18.3.102)' can't be established.
ED25519 key fingerprint is SHA256:lgJpFZwUmV3iwY7kp9+gsiFrczV2/o3dIzP9kBq5czE.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
Enter fullscreen mode Exit fullscreen mode

When you type yes, the server's public key is saved to the ~/.ssh/known_hosts file in your home directory. You can inspect this file to see the stored key:

$ cat ~/.ssh/known_hosts
ssh-lab-server ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEOQy4+6J3BirR3SuwLPRweCiTvkC/oVSdw/4XPBvm4t
Enter fullscreen mode Exit fullscreen mode

When the Fingerprints Don't Match

The next time you connect, the SSH client will compare the key provided by the server against the one stored in your known_hosts file. If they match, the connection proceeds.

However, if the host key does not match, the OpenSSH client blocks the connection and displays an alarming warning:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
SHA256:8o/qY3/YXiEUh5yHMH5Xe6Zpekiroj5QTVSBt7cF03o.
Please contact your system administrator.
Add correct host key in /home/myclient/.ssh/known_hosts to get rid of this message.
Offending ED25519 key in /home/myclient/.ssh/known_hosts:1
Host key for ssh-lab-server has changed and you have requested strict checking.
Host key verification failed.
Enter fullscreen mode Exit fullscreen mode

This is the default behavior of the OpenSSH client, and it is your primary line of defense. It forces you to stop and investigate what happened, before continue and potentially send data or credentials to a malicious server.

But What Is a Fingerprint in the First Place?

An SSH key fingerprint is a condensed, hash-based and fixed-length representation of a much larger public key.

You might wonder: Why do we need a fingerprint if we can just check the public key directly? Essentially, for human readability and consistency. Public keys can be incredibly long and vary in size depending on the algorithm used (RSA vs ED25519). Displaying a short, fixed-length fingerprint is much more convenient for manual verification, logging, and monitoring.

For example, consider this known_hosts file generated by pulling the public keys for GitHub:

$ ssh-keyscan github.com >> ~/.ssh/known_hosts
$ cat ~/.ssh/known_hosts
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
Enter fullscreen mode Exit fullscreen mode

As you can see, the keys have different lengths, especially the RSA key, which is quite huge. Trying to visually compare those strings would be a nightmare. However, if we compute the fingerprints using ssh-keygen, the data becomes much more manageable:

$ ssh-keygen -l -f ~/.ssh/known_hosts
3072 SHA256:uNiVztksCsDhcc0u9e8BujQXVUpKZIDTMczCvj3tD2s www.github.com (RSA)
256 SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM www.github.com (ECDSA)
256 SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU www.github.com (ED25519)
Enter fullscreen mode Exit fullscreen mode

Much easier to read, isn't it?

Common Questions and Misconceptions

If I see a host key verification error, am I under a man-in-the-middle attack?

Not necessarily, but you should never ignore the error. A server's public key can change for legitimate reasons, such as:

  • The server was rebuilt or reinstalled, generating new keys.
  • The OS was upgraded or reconfigured.
  • You are connecting to a new instance behind a load balancer or an autoscaling group.
  • The infrastructure is ephemeral (e.g., cloud instances frequently destroyed and recreated).
  • The administrator intentionally rotated the keys for security.

You must verify that this change is valid before proceeding.

Why would anyone intentionally disable host key verification?

It often happens because skipping the check feels "simpler" during development, especially when learning to use SSH libraries. These libraries often include warning logs specifically to alert developers who forget to implement verification. While disabling it might save time during a "Hello World" project, it creates a massive security hole if that code ever reaches production.

What if I’m connecting via VPN or a private network?

You might argue that internal infrastructure is "safe," but that is exactly the assumption attackers rely on. However, once an attacker is inside your VPN, they can compromise one or more machines. The risk is lower than the open internet, but it is never zero.

What about automation tools like Ansible?

This is one of the most common places where verification is disabled. Developers are often tempted to bypass host key verification to avoid friction when configuring and managing environments.

This is typically done in one of three ways:

  • In the configuration file: By setting host_key_checking = False inside the [defaults] section of the ansible.cfg file.
  • As an environment variable: By exporting ANSIBLE_HOST_KEY_CHECKING=False in the terminal session before running a playbook.
  • Directly at SSH level: By specifying StrictHostKeyChecking=no as a connection argument, via ansible_ssh_common_args.

While this simplifies the first stages of a project, it is a habit that must be broken. Eventually, you should implement a proper verification strategy (like using a Certificate Authority or pre-populating known_hosts), especially when connecting to machines outside your immediate control.

Do testing environments really need this level of caution?

You might think a test server has no useful data, but that depends on your configuration. Attackers often use "low-value" environments as a stepping stone. If your test environment uses the same credentials or has network access to your production database, a stolen SSH session on a testing machine could potentially compromise the rest of the company's infrastructure.

The risk is naturally lower when working with ephemeral and immutable infrastructure, where machines are frequently destroyed and recreated. In contrast, environments with long-lived infrastructure should always apply strict SSH host key verification practices.

Conclusion

In this first part, we discussed how SSH verifies a server's identity and why hostnames and IP addresses are not reliable guarantors of that identity. We explored how attackers can manipulate network infrastructure via DNS poisoning, host file modification, or routing attacks to take advantage of poor SSH configurations. Knowing the risks of improper SSH configurations is the first step toward hardening applications and infrastructure that rely on SSH.

In the next article, we will move from theory to practice. We’ll walk through a lab exercise to demonstrate exactly how an attacker can steal credentials and monitor live user activity using a man-in-the-middle attack.

Stay tuned for Part 2!

Bibliography

Top comments (0)