... ooga booga tunnelception
Writeup start: 2024-12-29
Writeup finish: 2025-01-09
I'm having a great time: It is late, and Tschunk is flowing though my veins. I'm definetly not going to put out any useful code anmore. What code? That's secret! The only thing I can say is that this blog will be available via the HTTP protocol, without me writing any content in HTML. At least I will have much hackathon-like code to review when I'm back from congress. If it is usable, I will of course publish it under the same license as:
But I am definetly going to write a blogpost! Hell yeah 8) Tschunk go brr!
Some weeks ago I have found myself in a dire situation.
Behind a corporate firewall.
I could not connect to my wireguard VPN! I was helpless! ...until I wasn't. (Vsauce music playing.) Now apparently, there is some motivation [shtrophic of two weeks after congress continuing to write this blog post :b] for some businesses to disallow any internet-connections to ports other than, like 80 (http) and 443 (https).
Their reasons? I am not sure. Maybe, they just outsource their IT-"security" to some third party that tries to be overly restrictive in an effort to appear as offering a "very secure" product. But I am not here to reason about their motivaiton. Much rather, I'm interested in how to penetrate this firewall!
Now, for the internet to be accessible at all, aforementioned HTTP(S)-ports remain open. This isn't likely to change anytime soon. Thus, if it could be possible for our wireguard traffic to look like some incarnation of HTTP, while connecting to a remote 443, we'd be good.
Now, I really need to stop teasing everybody with storytelling. I know. So there go the technical details.
=> wstunnel
makes it possible to tunnel any traffic through websocket, which is a HTTP/1.1-based protocol that can be used for transmitting arbitrary data. Since it is used by video-conferencing software, it is a widely accepted protocol that (should) survive(s) deep-packed-inspection.
You'll need one instance on your wireguard server and on your client, the latter being behind the corporate firewall. To get a working copy of that software, you can have a look at the release page of its github repository, as many platforms are covered there.
Except one!
Yes, OpenBSD is not covered! Sike! But wait! I've got you covered. Anyone here on OpenBSD and feeling adventurous can try the port that I've submitted to the ports mailing list:
=> openbsd-ports mailing list - NEW: net/wstunnel
it is still waiting for another OK to be imported though. It builds fine however. Quite an interesting thing to see how OpenBSD folk manage to package rust with makefiles. They don't even call cargo! (If I understand correctly.) They actually pull each crate specified in Cargo.lock into a sourcedir and build from that; The upside here is that these locked dependencies can then easily be patched locally.
Given that you have a wstunnel binary on your server, you can make it listen for websocket connections like this:
# wstunnel server --restrict-to 127.0.0.1:51820 wss://0.0.0.0:443
Or equivalently, if you use the OpenBSD port:
# rcctl set wstunnel flags server --restrict-to 127.0.0.1:51820 wss://0.0.0.0:443 # rcctl start wstunnel # rcctl enable wstunnel
This will now make it listen on your server's HTTPS port. Depending on your setup, this is not desirable - for example, what if you have some webserver running on that port already? We will come back to that later. For now, just assume that we will connect to your server over port 443. The --restrict-to
argument tells that any wstunnel client may only connect to port 51820 of localhost; This is the standard wireguard port. I also assume that you have wireguard setup already - I am not covering this. If you need any hints, feel free to check out
=> ~solene: Full WireGuard setup with OpenBSD
as an example. There are many others for Linux, I just found this one to be the best for OpenBSD. In fact, you don't even need wg-quick to keep your sanity there.
Now, to have some comfortable automation on your client, a few more steps are needed. As this calls for scripting and I like to script in fish, because it feels the most similar to "general programming languages", I'll provide examples in the fish scripting language.
Lets start with the basics and set our endpoint that we will connect to. This may as well be some IP address, it only needs to point to our server running the listening instance of wstunnel:
#!/usr/bin/env fish set -l endpoint wss://gateway.example.org
I personally only connect through wstunnel if I have to, as wrapping tunnels in tunnels adds unnecessary overhead that I'd like to not have when not necessary. That is why I keep any wireguard over websocket tunnels as seperate configurations in /etc/wireguard
- actually, the only thing that needs changing there is the Endpoint =
entry which from now on needs to be changed to Endpoint = 127.0.0.1:51820
, compared to configuration that you'd use for "usual" wireguard connections.
Also, I'd like my script to toggle the connection. For this, we first check if our wireguard-over-websocket tunnel is already running:
if ip addr show wswg0 2>&1 > /dev/null wg-quick down wswg0 eval "sudo ip route del $(cat $XDG_RUNTIME_DIR/wswg-quick.route)" systemctl --user stop wstunnel exit end
This actually spoilers a lot what will come in the following. Line by line:
The problem for our websocket tunnel is that it will connect to our server over the "regular internet". But usually, wireguard clients are configured by AllowedIPs = 0.0.0.0/0
to route all their traffic through our tunnel. Of course, we cannot route our websocket tunnel through our wireguard tunnel that is supposed to be tunneled through websocket. For this, we need to manually set our routes. I have come up with some commands to ease this process:
set -l remote "$(getent ahosts gateway.example.org | head -1 | cut -d' ' -f1)"; or exit set -l gateway "$(getent ahosts _gateway | head -1 | cut -d' ' -f1)"; or exit set -l device "$(ip route get $remote | grep -Po "(?<=dev )[a-z0-9]+")"; or exit
Note that this will probably not work for IPv6-only networks. It hasn't failed me yet, though.
We now need to run the wireguard client instance. I like to run it as a transient systemd service under my current user, so I do:
systemd-run --user --unit wstunnel --description "wstunnel to $endpoint" -- \ wstunnel client \ -L 'udp://51820:127.0.0.1:51820?timeout_sec=0' \ $endpoint
And finally to apply and save (for it to be easily removed again) the route for our websocket tunnel, to then up our wireguard tunnel:
echo "$remote dev $device via $gateway" > $XDG_RUNTIME_DIR/wswg-quick.route sudo ip route add $remote dev $device via $gateway wg-quick up wswg0
And that's it!
Running that script a second time should cleanly tear down your two tunnels, including the custom route.
As previously mentioned, it might not be possible to make the server instance listen on 443, as you might have some webserver already running on that port. However, we need to connect over 443 since otherwise, we might not be able to pass through picky firewalls. Do not worry though! Reverse-proxying wstunnel over nginx is totally doable, and you can even enable HTTP basic auth for some additional foolproofing, as this is supported in the wstunnel client implementation:
map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 443 ssl; listen [::]:443 ssl; server_name gateway.example.org; # add ssl-certs, etc. gzip off; location / { proxy_pass http://127.0.0.1:4443; auth_basic "gateway.example.org" auth_basic_user_file /path/to/.htpasswd; proxy_buffering off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
This snippet should get you started. In case you add basic auth, you need to provide the --http-upgrade-credentials
option to your client. Otherwise, you can just remove both lines from the nginx configuration. The server instance of wstunnel also needs adjusting - since we now proxy and terminate SSL through nginx, listening on ws://127.0.0.1:4443
is enough. And while we are at it, adding --tls-verify-certificate
to your client's options makes sure that nobody is impostering your server. Finally, the last added header X-Forwarded-For
will add the client's IP address to the logs of your server's wstunnel, because wstunnel actually looks for that header. Nice!
Happy firewall penetration everyone!
=> View comments | Home This content has been proxied by September (ba2dc).Proxy Information
text/gemini