If you are a frequent SSH user, then you’ll be familiar with local port forwarding:
Local forwarding is used to forward a port from the client machine to the server machine. Basically, the SSH client listens for connections on a configured port, and when it receives a connection, it tunnels the connection to an SSH server. The server connects to a configurated destination port, possibly on a different machine than the SSH server.
Creating TCP tunnels by using local port forwarding is not without risks however. One aspect in particular deserves some scrutiny, and that is:
[...] the SSH client listens for connections on a configured port [..]
If the SSH client opens a port, then any client can potentially connect to that port. How do we make sure that malicious users cannot take advantage of this to hijack a tunnel and gain access to the remote machine?
gcloud compute start-iap-tunnel and IAP Desktop
127.0.0.1 when creating IAP TCP forwarding tunnels. That automatically protects
the tunnel from being accessed by remote clients. But what about other local clients, particularly
when you are in a Remote Desktop Services, Citrix, or other kind of multi-user environment where you have no
idea who else is logged in?
Let us consider the example below: Alice has logged on to
multiuser-box (it does not matter whether that is
a Linux or Windows machine) and opens an SSH tunnel to
secure-box. The tunnel forwards connections from
secure-box:80. The tunnel lets Alice access nginx
by pointing her browser to
What if Mallory logs in to
multiuser-box and also connects to
SSH will not stop Mallory and will in fact happily let him use Alice’s tunnel. Mallory could even go one step further and set up a remote forwarding tunnel to expose Alice’s tunnel to others:
Trying to mitigate the risks of local port forwarding in multi-user environments is surprisingly difficult. SSH provides a number of configuration options that control how users can use port forwarding – but these options apply to the server side, not to the client.
gcloud, the story is similar. IAP gives you fine grained control over who is allowed to create tunnels,
which VMs they can target, and which additional conditions need to be met. But once you’ve successfully created
a tunnel by using
gcloud compute start-iap-tunnel, none of these policies prevents a hijacking scenario as
illustrated by the preceding example.
If SSH and
gcloud do not provide any good protections themselves, how about using firewall rules to restrict
who can connect to a TCP tunnel?
One interesting feature of Windows Defender Firewall is that it lets you create user-specific and
program-specific rules. In theory, you could use that to ensure that only Alice can access the port used by the tunnel, and you could even dictate that she is only allowed to use
mstsc.exe to connect to it.
In practice however, using firewall rules to restrict access to tunnels seems like a non-starter: Tunnels are typically short-lived, they often use random local ports, and a user might use multiple tunnels at the same time. Trying to come up with firewall rules that account for all possible variations seems impractical.
How IAP Desktop protects tunnels
When you connect to a remote desktop in IAP Desktop, the app creates an IAP TCP tunnel in the background.
The tunnel is necessary because the Microsoft RDP ActiveX control requires an IP address and port to connect to.
In an ideal world, the control would accept an
IStream interface (or something similar) so that the hosting
application could manage the connection – but that is not the case.
You can see these tunnels and their ports in the Active IAP tunnels window:
But when you try to connect to any of these tunnels by using
mstsc, the connection fails!
This connection failure is not a bug or coincidence – unlike SSH or
gcloud, IAP Desktop can detect and
reject connection attempts from what it considers unauthorized clients. If you have enabled logging
(Tools > Enable logging), you can find evidence for that behavior in the log file:
Warning: 0 : Connection from 127.0.0.1:59941 rejected by policy
In the next post, we will take a closer look at how this mechanism works