From 07c1d9f590e8de064e9b527c3d425eb898f7e59e Mon Sep 17 00:00:00 2001 From: Stefan Kreutz Date: Mon, 6 Jul 2020 22:40:21 +0200 Subject: Add initial version This commit adds the first published version of the website including the first blog post, Unix Domain Socket Forwarding with OpenSSH. --- .../unix-domain-socket-forwarding-with-openssh.md | 173 +++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 posts/unix-domain-socket-forwarding-with-openssh.md (limited to 'posts') diff --git a/posts/unix-domain-socket-forwarding-with-openssh.md b/posts/unix-domain-socket-forwarding-with-openssh.md new file mode 100644 index 0000000..295b0c2 --- /dev/null +++ b/posts/unix-domain-socket-forwarding-with-openssh.md @@ -0,0 +1,173 @@ +--- +title: Unix Domain Socket Forwarding with OpenSSH +description: How to control access to OpenSSH forwardings using Unix domain sockets instead of TCP ports. +published: 2020-07-06 +--- + +[OpenSSH](https://www.openssh.com/) is well-known for its ability to forward TCP ports from a local host to a remote host and vice versa. +Typical use cases include: + +* Access an otherwise unreachable server via a bastion host. + +* Access the loopback interface of a remote host. + +* Expose a local network service to a remote host. + +Recently, I had an interesting variant of the third use case: +I wanted to expose a local network service --- in this case, an inherently insecure [RFB](https://www.iana.org/assignments/rfb/rfb.xml) server --- via a remote host on condition that only a select user could connect to the exposed service. +That is, I wanted to control access to the remote forwarding. +It turned out that Unix domain socket forwardings are better suited than TCP port forwardings in this case. + +In the remainder of this post, I explain why it is difficult to control access to a TCP port forwarding, and how you can control access using a Unix domain socket forwarding instead. +Moreover, I describe how to restrict forwardings to select TCP ports respectively socket files. + +## TCP port forwarding + +The following command forwards port 8080 on a remote host to port 80 on the local host:[^localhost] + + $ ssh -nNT \ + -R 8080:localhost:80 \ + -o "ExitOnForwardFailure yes" \ + remote_host + +Of course, you can restrict port forwardings by means of the [`sshd_config(5)`](https://man.openbsd.org/OpenBSD-6.6/sshd_config) or an [`authorized_keys`](https://man.openbsd.org/OpenBSD-6.6/sshd#AUTHORIZED_KEYS_FILE_FORMAT) file.[^manuals] +For example, the following settings restrict the user Alex to listen to --- and thus forward --- remote port 8080 only: + + Match User alex + AllowTcpForwarding remote + PermitOpen none + PermitListen 8080 + PermitTTY no + ForceCommand /bin/echo 'ssh command forced by sshd_config(5)' + +However, as the [man page clearly states](https://man.openbsd.org/OpenBSD-6.6/sshd_config#AllowTcpForwarding), users with access to a shell can generally bypass this restriction: + +> **AllowTcpForwarding** +> +> Specifies whether TCP forwarding is permitted. +> The available options are **yes** (the default) or **all** to allow TCP forwarding, **no** to prevent all TCP forwarding, **local** to allow local (from the perspective of ssh(1)) forwarding only or **remote** to allow remote forwarding only. +> Note that disabling TCP forwarding does not improve security unless users are also denied shell access, as they can always install their own forwarders. + +For example, the following command uses the infamous [netcat](https://nc110.sourceforge.io/) utility --- in this case OpenBSD's widespread reimplementation, [`nc(1)`](https://man.openbsd.org/OpenBSD-6.6/nc) --- to query the exposed HTTP service at port 8080: + + $ printf "GET / HTTP/1.0\r\n\r\n" \ + | ssh -T remote_host nc localhost 8080 + +As far as I know, you have two practical options to control access to forwardings: + +1. Add user-specific rules to your firewall of choice, if supported. +For example, the [owner module](http://ipset.netfilter.org/iptables-extensions.man.html#lbBP) of [`iptables(8)`](http://ipset.netfilter.org/iptables.man.html) enables you to match the user ID and the group ID of a local packet creator. + +1. Use [Unix domain sockets](https://en.wikipedia.org/w/index.php?title=Unix_domain_socket&oldid=949050080) instead of TCP ports, and protect the special socket files just like regular files --- i.e., set the file owner, group, and mode using [`chown(8)`](https://man.openbsd.org/OpenBSD-6.6/chown) and [`chmod(1)`](https://man.openbsd.org/OpenBSD-6.6/chmod). + +## Unix domain socket forwarding + +OpenSSH supports Unix domain socket forwarding out of the box.[^windows] +Simply specify a file name instead of a TCP port. +For example, the following command creates, binds to, and forwards a socket on a remote host to port 80 on the local host: + + $ ssh -nNT \ + -R /var/run/http.sock:localhost:80 \ + -o "ExitOnForwardFailure yes" \ + remote_host + +With this, you can query the exposed HTTP service again using [`nc(1)`](https://man.openbsd.org/OpenBSD-6.6/nc): + + $ printf "GET / HTTP/1.0\r\n\r\n" \ + | ssh -T remote_host nc -U /var/run/http.sock + +There are two caveats, though: + +1. The file name of the socket must contain a forward slash. Otherwise, [`ssh(1)`](https://man.openbsd.org/OpenBSD-6.6/ssh) misinterprets the socket name as a TCP port. + +1. The file name of the socket should not rely on [tilde expansion](https://man.openbsd.org/OpenBSD-6.6/ksh#Tilde_expansion). +That is, use `/home/alex/http.sock` instead of `~/http.sock` or `~alex/http.sock`. + +1. You must not re-bind an existing socket by default.[^unlink] Set `StreamLocalBindUnlink yes` in the [`sshd_config(5)`](https://man.openbsd.org/OpenBSD-6.6/sshd_config) to allow this. + +1. There are no [`sshd_config(5)`](https://man.openbsd.org/OpenBSD-6.6/sshd_config) options equivalent to `PermitOpen` and `PermitListen` for Unix domain sockets. +That is, you cannot restrict the file name of the socket. + +However, if you really want to restrict the socket's file name, then we can build upon the netcat trick from above. + +## Restricting the socket's file name + +This time we'll use [`socat(1)`](http://www.dest-unreach.org/socat/doc/socat.html), another successor of netcat.[^backpipe] + +The following command effectively establishes the same forwarding as the previous one: it creates, binds to, and forwards a socket on a remote host to port 80 on the local host. + + $ socat \ + EXEC:'ssh -T remote_host socat "UNIX-LISTEN:/var/run/http.sock,fork,unlink-early" STDIO' \ + TCP4:127.0.0.1:80,fork + +Essentially, the command builds the following bidirectional stream: + +1. [`socat(1)`](http://www.dest-unreach.org/socat/doc/socat.html) pipes the remote socket to standard input/output on the remote host. + +1. [`ssh(1)`](https://man.openbsd.org/OpenBSD-6.6/ssh) pipes this input/output from the remote host to the local host. + +1. [`socat(1)`](http://www.dest-unreach.org/socat/doc/socat.html) pipes the input/output from [`ssh(1)`](https://man.openbsd.org/OpenBSD-6.6/ssh) to port 80 on the local host. + +With this, we can restrict the socket's name by means of the [`sshd_config`](https://man.openbsd.org/OpenBSD-6.6/sshd_config) or an [`authorized_keys`](https://man.openbsd.org/OpenBSD-6.6/sshd#AUTHORIZED_KEYS_FILE_FORMAT) file as follows: + + Match User alex + DisableForwarding yes + PermitTTY no + ForceCommand /usr/local/bin/socat UNIX-LISTEN:/var/run/http.sock,fork,unlink-early STDIO + +With these settings, the previous commands comes down to the following, where `forced-command` is an optional no-op reminder: + + $ socat \ + EXEC:'ssh -T remote_host forced-command' \ + TCP4:127.0.0.1:80,fork + +Finally, a client may connect to this socket as follows --- regardless of how we created the socket: + + $ ssh -nNT \ + -L 3000:/var/run/http.sock \ + -o "ExitOnForwardFailure yes" \ + remote_host + $ curl http://localhost:3000/ + +Unfortunately, [`ssh(1)`](https://man.openbsd.org/OpenBSD-6.6/ssh)'s `ExitOnForwardFailure` option does not catch missing permissions to access the socket file. +Thus, if the final [`curl(1)`](https://curl.haxx.se/docs/manpage.html) command fails and you cannot actually use the forwarding, please check the group and the mode of the socket file created by [`socat(1)`](http://www.dest-unreach.org/socat/doc/socat.html) on the remote host. +You can set the group and mode using the corresponding `UNIX-LISTEN` options. + +## Conclusion + +OpenSSH is able to forward TCP ports and Unix domain sockets. +The server can be configured to restrict who may open respectively listen to which port. +However, users with access to a shell can generally bypass this restriction. +In particular, any user with access to a shell can open any forwarded port. +Thus, you might want to use Unix domain sockets instead of TCP ports for remote forwardings. +This way, you can control access to the special socket file, and thus to the forwarded network service. +You can restrict the socket's file name by forcing a special command instead of using the built-in forwarding. + +[^localhost]: + I use the descriptive `localhost` hostname instead of actual addresses on the loopback interface such as `127.0.0.1` or `::1` throughout this blog post. + However, the truly paranoid hacker might want to use the latter to save the hostname resolution. + +[^manuals]: + OpenSSH is developed as a part of [OpenBSD](https://www.openbsd.org/). + That's why I'm referring to OpenBSD's man pages here. + Besides, I'm referring to the man pages at the time of writing. + +[^windows]: + Notably, the OpenSSH version shipped with Windows 10 does not support Unix domain socket forwarding, see [here](https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_server_configuration). + +[^unlink]: + There is no system call to re-bind an existing Unix domain socket. + Instead, you have to [`unlink(2)`](https://man.openbsd.org/OpenBSD-6.6/unlink) --- and thereby remove --- the socket file before you can [`bind(2)`](https://man.openbsd.org/OpenBSD-6.6/bind) and [`listen(2)`](https://man.openbsd.org/OpenBSD-6.6/listen) again. + +[^backpipe]: + In my opinion, the following two commands should be equivalent: + + $ socat UNIX-LISTEN:foo.sock,fork TCP4:127.0.0.1:8080,fork + + $ mkfifo backpipe + $ nc -lkU foo.sock 0backpipe + + However, in practice, the second command did not work reliably on [OpenBSD 6.6](https://www.openbsd.org/66.html) and [Arch Linux](https://www.archlinux.org/) in June 2020. + I suspect, it's got something to do with an early EOF or standard output buffering. + Please drop me a mail if you can help me out. -- cgit v1.2.3