#!/bin/bash # session_down.sh # # OpenVPN server-side --client-disconnect script # Runs when a client ends an OpenVPN session # 1) Validates provided token # 2) Sends token to auth API to decrease the session counter # 2.5) Sends a random number of dummy auth API requests along with the real one # exit 0 (success) if everything was successful (there's no exit 0 in this script because it defaults to that) # exit 1 (failure) if the token isn't the correct chars/length. # API_URL=$(< /etc/api) # only allow characters that are in a token or a token's hash (a-zA-Z0-9-) username=$(echo "$username" | tr -dc 'a-zA-Z0-9-' ) # only allow valid lengths (126-128 for hashes, 23 for plaintext tokens) case "${#username}" in 128|127|126) hash=$username ;; # is a hash already (some routers cut off the last one or two bytes, so accounting for that) 23) hash=$(echo -n "$username" | openssl sha512 | awk '{print $NF}') ;; # is a plaintext token, so hash it for them *) exit 1 ;; # not the length of a token or a token hash, so reject esac # ---- Obfuscated API request batch with dummy requests ---- # Even though the API server uses HTTPS and is restricted to TLSv1.3 and post-quantum key exchanges, someone capable of sniffing # the encrypted traffic between the VPN server and the API server might be able to infer potentially sensitive data about a VPN # client based on when those API requests/responses occur (I.e., when a client initially connects or disconnects, how long their # VPN session was, etc.) # To account for this, a random number of dummy API requests are sent along with the real request. # All responses have the same length to prevent size-related side channel leaks. # The dummy requests also rotate between the parameters used by this script and the auth and session down ones, so anyone listening # in won't know if this is a session going up, going down, or a key renegotiation. # There's also a separate daemon that sends a random number of dummy API requests at a random frequency. # Temporary file to hold the genuine API replies down_file=$(mktemp) total_requests=$((RANDOM % 4 + 2)) # 1-4 dummies + 1 real (sess down) # We put the first request in a random position in $real_index so it's not always the same. real_index=$((RANDOM % total_requests + 1)) result_token="" result_up="" api_failed=1 i=1 while [ $i -le $total_requests ]; do if [ $i -eq $real_index ]; then # Real request ( # Sleep for a random interval between 0-4 seconds # This is so an API request doesn't always go out immediately after a client connects, since that might be useful in some # correlation/timing attacks (still assuming the attacker can monitor the traffic between the VPN and API server). sleep $((RANDOM % 5)) r=$(curl --http2 --silent --show-error --max-time 8 --connect-timeout 8 "https://$API_URL?token=$hash&action=down") printf '%s\n' "$r" >"$down_file" ) & else # Dummy request ( # Sleep for a random interval between 0-4 seconds here too so it behaves the same as a real request sleep $((RANDOM % 5)) # Randomly pick a dummy hash for each request dummy_hash=$(head -c 64 /dev/urandom | sha512sum | awk '{print $1}') # Randomly pick a dummy parameter (auth/up/down) # This prevents anyone from inferring the API request type based on the size differences in the encrypted packets case $((RANDOM % 3)) in 0) dummy_url="https://$API_URL?token=$dummy_hash" ;; 1) dummy_url="https://$API_URL?token=$dummy_hash&action=up" ;; 2) dummy_url="https://$API_URL?token=$dummy_hash&action=down" ;; esac curl --http2 --silent --max-time 8 --connect-timeout 8 "$dummy_url" >/dev/null 2>&1 ) & fi i=$((i + 1)) done # Wait until every background job (real + dummy) is done wait # Pick up the two real answers, if they were written if [ -s "$down_file" ]; then result=$(<"$down_file") api_failed=0 fi rm -f "$down_file" # Remove the user's randomly generated internal IPv4 from the tmp pool instance_pool_dir=/tmp/pool if [[ -n $ifconfig_pool_remote_ip ]]; then rm -vf $instance_pool_dir/$ifconfig_pool_remote_ip 2>&1 # And delete their port forwards sudo /usr/local/sbin/portfwd del $ifconfig_pool_remote_ip # And any http://10.31.33.7/changemyip entries sudo /usr/local/sbin/changeip $ifconfig_pool_remote_ip # And all conntrack entries (to protect against Port Shadowing) sudo /usr/local/sbin/connmarkrm $ifconfig_pool_remote_ip & fi # Do the same for IPv6 if [[ -n $ifconfig_pool_remote_ip6 ]]; then ifconfig_pool_remote_ip6="${ifconfig_pool_remote_ip6//:0:/::}" rm -vf $instance_pool_dir/$ifconfig_pool_remote_ip6 2>&1 sudo /usr/local/sbin/portfwd del $ifconfig_pool_remote_ip6 sudo /usr/local/sbin/changeip $ifconfig_pool_remote_ip6 sudo /usr/local/sbin/connmarkrm $ifconfig_pool_remote_ip6 & fi