HTTPS Stealth-VPN

Threat models

Blind blocking of ports (or IPs)

Targeted blocking of your server due to information leakage

Deep package inspection

Other things to consider

Impossibility of SNI Confidentiality

Abnormalities in browsing behavior

Legal and moral implications

False sense of anonymity

Hey, actually now that you say it: Why not just use the Tor network in the first place?

Formulating design goals for our stealth VPN

  1. must use port 443 or 80 for VPN connection to circumvent port blocking
  2. the VPN traffic must look like HTTPS traffic when analyzed
  3. server, client and connection should not leak any information that would make it look like non-standard traffic

Outlining the internal workings

http { 
ssl_certificate /path/to/cert;
ssl_certificate_key/path/to/key;
server{
server_name atlantishq.de;
listen [::]:443 ipv6only=off;
}
}

nginx configuration

stream { 
ssl_certificate /path/to/cert;
ssl_certificate_key /path/to/key;
}
map $ssl_preread_server_name $name {
default https;
vpn.atlantishq.de vpn;
}
upstream https { 
server unix:/path/to/location/nginx/can/write/to_https ssl;
}
upstream vpn {
server unix:/path/to/location/nginx/can/write/to_vpn ssl;
}
server {
listen unix:/path/to/location/nginx/can/write/to_https
# openvpn doesn't support unix-sockets
proxy_pass 127.0.0.1:VPNPORT
}
server{
listen unix:/path/to/location/nginx/can/write/to_https
# could also use a unix-socket here
proxy_pass 127.0.0.1:INTERNAL-HTTPPORT
}
server { 
listen [::]:443 ipv6only=off;
proxy_protocol on;
proxy_pass $name;
}
http {
...
server{
...
listen 127.0.0.1:INTERNAL-HTTPPORT
...
}
}
log_format sni_multiplexer '$remote_addr [$time_local] ' 
'with SNI name "$ssl_preread_server_name"'
'proxying to "$name" '
'$protocol $status'
'$bytes_sent $bytes_received'
'$session_time';
access_log /var/log/nginx/tls.log sni_multiplexer;
[Service]
# unset exec stop
ExecStop=
# set to new value
ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry TERM/5 \
--pidfile /run/nginx.pid

stunnel configuration

[randomname]
client = yes
accept = 127.0.0.1:LOCAL_PORT
connect = vpn.atlantishq.de:443
sni = vpn.atlantishq.de
verifyChain = yes
CAPath = /etc/ssl/certs/
checkHost = vpn.atlantishq.de

openvpn configuration

# only listen on localhost
local 127.0.0.1
# and set the port
port VPN_PORT_YOU_USED_IN_NGINX
# use tls
tls-server
# the above necessitates
mode server
# use tcp
proto tcp
remote 127.0.0.1 STUNNEL_LOCAL_PORT

nginx configuration

'with cert status "$ssl_client_verify"'
'proxying to "$correct_NEW_map_name" ' # <-- carefull
map $ssl_client_verify $mapname{
"SUCCESS" vpn;
default http;
}
server {
listen [::]:443 ipv6only=off ssl;
ssl_verify_client optional;
proxy_protocol on;
proxy_pass $mapname;
}

stunnel configuration

[randomname]
client = yes
accept = 127.0.0.1:LOCAL_PORT
connect = atlantishq.de:443
sni = atlantishq.de
verifyChain = yes
CAPath = /etc/ssl/certs/ checkHost = atlantishq.de cert = /etc/stunnel/clientcert{.p12|.pem|...}

Workaround for HTTP2

stream {
...
map $ssl_preread_alpn_protocols $protocol_stream {
default 127.0.0.1:CERT_MAP_SERVER_PORT;
~\bh2\b 127.0.0.1:HTTP_VSERVER_PORT;
}
map $ssl_verify_client $protocol_stream {
"SUCCESS" 127.0.0.1:VPN_PORT;
default 127.0.0.1:HTTP_VSERVER_PORT_NOSSL;
}
# multiplex protocol
server {
listen 443;
ssl_preread on;
...
proxy_protocol on;
proxy_pass $protocol_stream;
}
# listen internall and multiplex certificate
server {
# remove TLS listen 127.0.0.1:CERT_MAP_SERVER_PORT ssl;
...
ssl_verify_client optional;
proxy_protocol on; proxy_pass $name;
}
}
http {
...
server {
listen 127.0.0.1:HTTP_VSERVER_PORT proxy_protocol ssl http2;
listen 127.0.0.1:HTTP_VSERVER_PORT_NOSSL;
...
}
}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Yannik Schmidt

Yannik Schmidt

Python programmer at heart, Linux Expert at work, GameDev on my good days, web developer on the bad days.