OpenSSH ProxyUseFdPass
Published:
Updated:
While looking at the OpenSSH ssh_config
manpage, I found the
ProxyUseFdpass
configuration I did not know about.
It is apparently not widely known or used.
Update (2017-08-02): netcat (nc
) has an option
(nc.openbsd -F www.example.com 80
) to pass the created
file descriptor using the "fdpass" mechanism.
In addition to the straightforward connect()
,
it can pass a file descriptor having initiated a connection through a SOCKS
proxy (with -x proxy.example.com
) or with HTTP connect
(-x proxy.example.com -X connect
).
Update (2021-02-09):
replaced fds.fromstring()
with fds.frombytes()
.
Update (2025-03-28): add reference to systemd-ssh-proxy
.
ProxyCommand
OpenSSH client has a ProxyCommand
configuration which can be used to
use a command as a transport to the server:
Specifies the command to use to connect to the server. The command string extends to the end of the line, and is executed using the user's shell ‘exec’ directive to avoid a lingering shell process.
Instead of opening a socket to the server itself, the OpenSSH client spawns the specified command and use its standard input and output to communicate with the server.
The man page suggests to use (the OpenBSD variant of) netcat to connect through an HTTP (or SOCKS) proxy:
ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p
A typical usage is to use a relay/bastion/jump/gateway[^jump] SSH
server with ssh -W
[1]:
Host gateway.example.com
ProxyCommand none
Host *.example.com
ProxyCommand ssh gateway.example.com -W %h:%p
Using ProxyJump
instead
OpenSSH 7.3 includes
special support for SSH jump servers with the ProxyJump
configuration,
Host *.example.com
ProxyJump gateway.example.com
or the -J
flag:
ssh -J gateway.example.com foo.example.com
Code injection through ProxyCommand
(update 2023-12-21)
Using ProxyCommand
with hostname (%h
) or username (%u
) expansions
may expose you to shell command injections:
it is possible
to inject arbitrary shell commands through
the hostname (%h
) or username (%u
)
(CVE-2023-51385).
For this to be exploited,
an attacker would have to trick you into SSH-ing
into a host with a weird (containing shell metacharacters) name
or using a weird username.
This can for example happen
when doing a recursive git clone
of a malicious git repository.
This has been mitigated in OpenSSH 9.6.p1 by rejecting hosts and usernames containing shell meta-characters (which should mostly work on a regular shell but might not work if using an exotic shell).
ProxyUseFdPass
While looking at the new ProxyJump
configuration, I found a
ProxyUseFdpass
option which:
Specifies that ProxyCommand will pass a connected file descriptor back to ssh(1) instead of continuing to execute and pass data. The default is “no”.
When enabled, instead of communicating with the server through the
ProxyCommand
standard input and output, the SSH client expects the
command to give it a file descriptor to use. The idea is to avoid
having an uncessary lingering process and extra write/reads when it is
not necessary[2].
The documentation does not explain how it is supposed to work exactly and I did not find any working example or any suggestion of a program which would be able to pass the file descriptor.
The spawned command is expected to:
- setup a file descriptor;
- send this file descriptor to the OpenSSH client process through
its (own) standard output
(
sendmsg
withSCM_RIGHTS
using a one-byte message; exit(0)
.
A minimal program which does the job is:
#!/usr/bin/env python3
import sys
import socket
import array
# Create the file descriptor:
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, 0)
s.connect((sys.argv[1], int(sys.argv[2])))
# Pass the file descriptor:
fds = array.array("i", [s.fileno()])
ancdata = [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)]
socket.socket(fileno = 1).sendmsg([b'\0'], ancdata)
Which can be used with:
ProxyCommand /path/to/passfd %h %p
ProxyUseFdpass yes
In its current form, it does not do much. It creates a socket the same way the OpenSSH client would have and pass it to the OpenSSH client. However, it can extended in order to do things such as:
- use a custom way to resolve the server address;
- bind the socket to a network device;
- set some socket options (
SO_DONTROUTE
, etc.); - open up firewalling rules before connecting to the server;
- connect to the server over UNIX socket, SCTP, etc.;
- connect to the server over HTTP proxy, SOCKS proxy, TCPMUX, etc.;
- choose different connection strategies depending on the current network environment;
- etc.
For testing purpose this receiving program can be used:
#!/usr/bin/env python3
import os
import sys
import socket
import array
(a, b) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0)
pid = os.fork()
def recv_fd(sock):
fds = array.array("i")
cmsg_len = socket.CMSG_LEN(fds.itemsize)
msg, ancdata, flags, addr = sock.recvmsg(1, cmsg_len)
for cmsg_level, cmsg_type, cmsg_data in ancdata:
if (cmsg_level, cmsg_type) == (socket.SOL_SOCKET, socket.SCM_RIGHTS):
fds.frombytes(cmsg_data)
return fds[0]
sys.exit(1)
if pid == 0:
# Exec specified command in the child:
a.close()
os.dup2(b.fileno(), 0)
os.dup2(b.fileno(), 1)
b.close()
os.execvp(sys.argv[1], sys.argv[1:])
else:
# Receive file descriptor and wait in the parent:
b.close()
s = recv_fd(a)
os.waitpid(pid, 0)
print(s)
Which can be used as:
fdrecv fdpass localhost 80
References
Backlinks:
Example of usage of ProxyUseFdpass
:
- systemd-ssh-proxy
is a tool shipped with systemd which can be used to connect OpenSSH
over Unix domain sockets (UDS), VSOCK
and firecracker's VSOCK-over-UDS.
(by using
ProxyCommand
andProxyUseFdpass
)
It is often suggested to use this configuration instead:
ProxyCommand ssh gateway.example.com nc %h %p
This requires netcat to be available on the server.
ssh -W
only needs client-side support which is available in OpenSSH since 5.4 (released in 2010) and the SSH server to accept TCP forwarding. ↩︎It is not usable for a SSH jump server but can be used in simpler cases. ↩︎