____ _ _ __ _ __ _____ ___ _|___ \ ___ ___| |__ | '_ \| '__/ _ \ \/ / | | | __) / __/ __| '_ \ | |_) | | | (_) > <| |_| |/ __/\__ \__ \ | | | | .__/|_| \___/_/\_\\__, |_____|___/___/_| |_| |_| |___/ [ http://wari.mckay.com/~rm/proxy2ssh/ ] Copyright 2006 Robert McKay proxy2ssh allows you to ssh over a web proxy using only GET/POST (not CONNECT) requests. Background: I've been using the http proxy CONNECT method for years to ssh through http proxies. In the early days I used to run a script from the local inetd server that would connect to the proxy server and sent the necessary preamble to connect to my home machine. Something like this: #!/bin/bash HOST=myserver.com PROXY=proxy.acme.com PROXY_PORT=8080 ( echo -en "CONNECT ${HOST}:443 HTTP/1.1\n" echo -en "Host: ${HOST}:443\n" echo -en "\r\n" cat ) | ( nc ${PROXY} ${PROXY_PORT} ) | ( head -3 >/dev/null # eat unwanted proxy http headers cat ) Then I'd ssh -p 2222 localhost and the script would connect me to my home machine through the http proxy. These days it's even easier; openssh's ssh client has an option -oProxyCommand that will let you run the proxy script directly from ssh (instead of having to run it from inetd): ssh -oProxyCommand=proxy.sh user@host There are actually several programs for doing this now. proxytunnel, ssh-https-tunnel, corkscrew and probably many others. None of them really do anything different than the above shell script. They all use CONNECT. On some proxies CONNECT just isn't available or is restricted to a limited number of whitelisted hosts and ports. The ports problem is usually easy to overcome by making sshd listen on port 443 (as I did for the old shell script above). If CONNECT is enabled at all it will normally be allowed for 443 as that is the https port which is the reason d'etre for CONNECT in the first place. Normally regular HTTP GET/POST are less restricted and can often connect to pretty much anywhere. In theory http is request/response, but in practice it isn't. Although proxies and apache generally enforce either a read or a write mode they always allow end-to-end transmission before the http "transaction" is complete. This presents us with an opportunity for rather a nice hack: We can get around the pseudo request/response limitation by using just two simultaneous http requests. One continuously reading, the other continuously writing. The obligatory ASCII diagram: ssh<--. .--> sshd | | | | | (continuous GET request to ssh.cgi) | v curl <--- proxy <--- apache <--- ssh.cgi <--- nc <---' .-----------. | ^ | |<--' |p | client.sh |---. |i | | | |p ~~~~~~~~~~~ v |e nc ---> proxy ---> apache ---> ssh.cgi -----' (continuous POST request to ssh.cgi) The dirty details: client.sh spawns a curl client and makes it GET the ssh.cgi. When ssh.cgi receives this request it reads the hostname from the query_string args, creates a pipe and a session key. It writes out the session key and then spawns nc, making it connect to the hostname supplied and having it take input from the newly created pipe. nc's stdout will go to ssh.cgi's stdout (ie: back to apache) and apache will send it to the proxy server and the proxy will send it to curl and curl will write it to it's stdout. The next thing the client.sh script needs is the session key which curl (or rather head -2 > /tmp/session ) has saved in a local session file in /tmp. It then reads this back in and then uses nc to make a second http request, this time a POST request specifying the session key as a query_string argument to the ssh.cgi script. ssh.cgi recognizes that this is a POST request and uses the session key to find the corresponding pipe that nc is already trying to read from. Then this instance of ssh.cgi simply does a cat > pipe. Now we've got a fully bidirectional tcp connection from client.sh to the remote ssh server over an http proxy without using CONNECT. The overhead is very low - once estbalished it's about the same as CONNECT or even straight ssh, except for the fact that you're using two tcp connections instead of one. There are other implementation advantages to this as well.. you can just dump the back-end script on any webserver and instantly gain access to it and anything that webserver can connect too from outside. That means it's as great for getting in through a webserver as well as getting out through a proxy. Usage information: ssh.cgi : This is the server side script. It runs as a regular CGI on a webserver. It requires nc (netcat) to be installed. client.sh : This is the client script. It requires both nc and curl to be installed. You use client.sh as an ssh ProxyCommand like so: ssh -oProxyCommand="client.sh %h" user@host.com I generally like to make short aliases such as: alias scpx='scp -oProxyCommand="client.sh %h"' alias sshx='ssh -oProxyCommand="client.sh %h"' So that I can just type sshx user@host to ssh via the proxy scripts. Thanks: Tet -- for improved proxy padding shell loop Mr. T -- for helping to figure out why virus scanning proxies weren't streaming application/octet-stream content. Possible fix: use a streaming media content-type instead.