Skip to content

Instantly share code, notes, and snippets.

@tiagobnobrega
Created October 24, 2025 14:51
Show Gist options
  • Select an option

  • Save tiagobnobrega/1a32e47af7c998fa907bf04cabc6a014 to your computer and use it in GitHub Desktop.

Select an option

Save tiagobnobrega/1a32e47af7c998fa907bf04cabc6a014 to your computer and use it in GitHub Desktop.
Connect to Cisco AnyConnect VPN on Linux
#!/usr/bin/env bash
#
# Use this script to connect to Cisco AnyConnect VPN on arch linux
# or any usupported linux platform. It spins up a mitm proxy and launches
# a chromium instance to perform the authentication.
# Once the webvpn cookie is identified on the proxy, it uses openconnect
# to establish the vpn connection using the cookie.
#
# In order to use this script you need to properly setup mitm proxy.
# That includes adding the CA certificate
#
set -euo pipefail
# anyconnect.sh
# ---------- defaults ----------
URL_PARAM="" # no default URL; user must provide --url or --cookie
AUTO_RUN=1 # default to auto-run (invert behavior); use --no-auto-run to disable
TIMEOUT_SECS=300
MITM_PORT=8080
BROWSER_BIN="" # optional explicit browser binary
STATUS_INTERVAL=5 # seconds between status prints while waiting
MITM_LOG="" # choose later with timestamp if empty
COOKIE_OVERRIDE="" # if provided, skip mitmdump/browser and run openconnect directly
BROWSER_PID="" # will hold the browser PID if launched by script
BROWSER_PROFILE="" # if empty, a temporary profile dir will be created with mktemp
# ---------- arg parsing ----------
while (( "$#" )); do
case "$1" in
--url) URL_PARAM="$2"; shift 2;;
--no-auto-run|--no-autoconnect) AUTO_RUN=0; shift ;;
--auto-run) AUTO_RUN=1; shift ;;
--timeout-secs) TIMEOUT_SECS="$2"; shift 2;;
--mitm-port) MITM_PORT="$2"; shift 2;;
--browser-bin) BROWSER_BIN="$2"; shift 2;;
--browser-profile) BROWSER_PROFILE="$2"; shift 2;;
--mitm-log) MITM_LOG="$2"; shift 2;;
--cookie) COOKIE_OVERRIDE="$2"; shift 2;;
-h|--help)
cat <<USAGE
Usage: $0 [options]
You must provide either:
--url <host/path> VPN host/path (protocol optional)
OR
--cookie <cookie-or-value> Provide a webvpn cookie
Options:
--no-auto-run, --no-autoconnect Don't automatically run openconnect after capturing cookie
--timeout-secs N How long to wait for cookie (default: ${TIMEOUT_SECS}s)
--mitm-port P mitmproxy listen port (default: ${MITM_PORT})
--browser-bin /path/to/bin Browser binary to use (defaults to chromium/chrome)
--browser-profile /path/to/dir Persistent browser profile directory (if omitted, a temporary profile will be created)
--mitm-log /path/to/log Where to save mitmdump stdout/stderr
-h, --help Show this help
USAGE
exit 0
;;
*) echo "Unknown arg: $1"; exit 2;;
esac
done
# ---------- verify input ----------
if [[ -z "$URL_PARAM" && -z "$COOKIE_OVERRIDE" ]]; then
echo "ERROR: You must provide either --url or --cookie."
echo
# print usage
"$0" --help
exit 2
fi
# ---------- sanitize URL_PARAM ----------
if [[ -n "$URL_PARAM" ]]; then
while [[ "$URL_PARAM" == *"://"* ]]; do
URL_PARAM="${URL_PARAM#*://}"
done
while [[ "$URL_PARAM" == //* ]]; do
URL_PARAM="${URL_PARAM#//}"
done
URL_PARAM="${URL_PARAM%/}"
VPN_URL="https://${URL_PARAM}"
fi
# ---------- derived values ----------
if [[ -z "${MITM_LOG:-}" ]]; then
MITM_LOG="/tmp/mitmdump.$(date +%s).log"
fi
err() { echo "ERROR: $*" >&2; }
info() { echo "INFO: $*"; }
# ---------- helper: run openconnect ----------
run_openconnect() {
local cookie_pair="$1"
if [[ -z "$cookie_pair" ]]; then
err "run_openconnect called with empty cookie_pair"
return 2
fi
if ! command -v openconnect >/dev/null 2>&1; then
err "openconnect not found on PATH."
return 1
fi
info "Refreshing sudo credentials..."
sudo -v || { err "sudo failed"; return 4; }
info "Executing: sudo openconnect --protocol=anyconnect ${URL_PARAM} -C \"${cookie_pair}\""
sudo openconnect --protocol=anyconnect "${URL_PARAM}" -C "${cookie_pair}" &
OC_PID=$!
info "openconnect started (PID ${OC_PID}). Closing browser (if launched)."
if [[ -n "${BROWSER_PID:-}" ]]; then
info "Killing browser PID ${BROWSER_PID}..."
kill "${BROWSER_PID}" >/dev/null 2>&1 || true
sleep 0.5
if kill -0 "${BROWSER_PID}" >/dev/null 2>&1; then
info "Browser did not exit; sending SIGKILL to ${BROWSER_PID}..."
kill -9 "${BROWSER_PID}" >/dev/null 2>&1 || true
fi
fi
wait "${OC_PID}"
rc=$?
info "openconnect exited with code ${rc}."
return $rc
}
# ---------- cookie-only mode ----------
if [[ -n "${COOKIE_OVERRIDE:-}" ]]; then
if [[ "${COOKIE_OVERRIDE}" == webvpn=* ]]; then
COOKIE_PAIR="${COOKIE_OVERRIDE}"
else
COOKIE_PAIR="webvpn=${COOKIE_OVERRIDE}"
fi
cookie_val="${COOKIE_PAIR#webvpn=}"
if [[ -z "$cookie_val" ]]; then
err "Provided cookie is empty."
exit 2
fi
info "Cookie-only mode: will run openconnect with provided cookie."
if (( AUTO_RUN )); then
run_openconnect "$COOKIE_PAIR"
exit $?
else
echo "Would run: sudo openconnect --protocol=anyconnect ${URL_PARAM} -C \"${COOKIE_PAIR}\""
exit 0
fi
fi
# ---------- normal (capture) mode preflight ----------
if ! command -v mitmdump >/dev/null 2>&1; then
err "mitmdump not found on PATH."
exit 1
fi
if (( AUTO_RUN )); then
if ! command -v openconnect >/dev/null 2>&1; then
err "openconnect not found (required for auto-run)."
exit 1
fi
fi
# ---------- browser detection helpers ----------
find_chrome_bin() {
if [[ -n "$BROWSER_BIN" && -x "$BROWSER_BIN" ]]; then
printf '%s' "$BROWSER_BIN"
return 0
fi
for cmd in chromium chromium-browser google-chrome-stable google-chrome; do
if command -v "$cmd" >/dev/null 2>&1; then
command -v "$cmd"
return 0
fi
done
return 1
}
# ---------- prepare mitm script & cookie file ----------
MITM_SCRIPT="$(mktemp --suffix=.py)"
COOKIE_FILE="$(mktemp)"
chmod 600 "$COOKIE_FILE"
: > "$COOKIE_FILE"
cat > "$MITM_SCRIPT" <<'PY'
from mitmproxy import http, ctx
import os
OUT = os.environ.get("OUTFILE", "/tmp/webvpn_cookie.txt")
def _extract_set_cookie_value(header: str) -> str:
pair = header.split(";", 1)[0]
if "=" in pair:
name, value = pair.split("=", 1)
return value
return ""
def response(flow: http.HTTPFlow) -> None:
try:
scs = flow.response.headers.get_all("Set-Cookie")
except Exception:
scs = []
for h in scs:
if not h:
continue
lower = h.lower()
if lower.startswith("webvpn="):
pair = h.split(";", 1)[0].strip()
value = _extract_set_cookie_value(h)
if value and value.strip():
try:
with open(OUT, "w") as f:
f.write(pair)
ctx.log.info("Captured webvpn cookie: %s" % pair)
except Exception as e:
ctx.log.error("Failed to write cookie: %s" % str(e))
else:
ctx.log.info("Ignored empty webvpn cookie (no value).")
return
PY
cleanup() {
rc=$?
info "Cleaning up..."
[[ -n "${MITM_PID:-}" ]] && kill "${MITM_PID}" >/dev/null 2>&1 || true
[[ -n "${BROWSER_PID:-}" ]] && kill "${BROWSER_PID}" >/dev/null 2>&1 || true
[[ -f "$MITM_SCRIPT" ]] && rm -f "$MITM_SCRIPT"
info "Mitmdump log: $MITM_LOG"
info "Cookie file: $COOKIE_FILE"
exit $rc
}
trap cleanup INT TERM EXIT
# ---------- start mitmdump ----------
info "Starting mitmdump on 127.0.0.1:${MITM_PORT}..."
export OUTFILE="$COOKIE_FILE"
nohup mitmdump -p "$MITM_PORT" -s "$MITM_SCRIPT" >"$MITM_LOG" 2>&1 &
MITM_PID=$!
sleep 0.5
if ! kill -0 "$MITM_PID" >/dev/null 2>&1; then
err "mitmdump failed to start. See $MITM_LOG"
exit 1
fi
info "mitmdump started (PID $MITM_PID)."
# ---------- open browser ----------
open_with_proxy() {
local url="$1"
CHROME_BIN="$(find_chrome_bin || true || :)"
# If no BROWSER_PROFILE set, create a temporary one (will be cleaned up on exit via trap)
if [[ -z "${BROWSER_PROFILE:-}" ]]; then
TMP_BROWSER_PROFILE="$(mktemp -d -t mitm-chrome-XXXX)"
BROWSER_PROFILE="$TMP_BROWSER_PROFILE"
info "No --browser-profile provided; using temporary profile: $BROWSER_PROFILE"
else
mkdir -p "$BROWSER_PROFILE"
fi
if [[ -n "$CHROME_BIN" && -x "$CHROME_BIN" ]]; then
info "Launching Chromium/Chrome with profile '$BROWSER_PROFILE' and proxy -> 127.0.0.1:${MITM_PORT}"
"$CHROME_BIN" --user-data-dir="$BROWSER_PROFILE" --proxy-server="127.0.0.1:${MITM_PORT}" --no-first-run --app="$url" >/dev/null 2>&1 &
BROWSER_PID=$!
return 0
fi
if command -v firefox >/dev/null 2>&1; then
TMP_FF_PROFILE="$(mktemp -d -t mitm-ff-XXXX)"
cat > "$TMP_FF_PROFILE/user.js" <<JS
user_pref("network.proxy.type", 1);
user_pref("network.proxy.http", "127.0.0.1");
user_pref("network.proxy.http_port", ${MITM_PORT});
user_pref("network.proxy.ssl", "127.0.0.1");
user_pref("network.proxy.ssl_port", ${MITM_PORT});
JS
info "Launching Firefox with temporary profile and proxy -> 127.0.0.1:${MITM_PORT}"
firefox -profile "$TMP_FF_PROFILE" "$url" >/dev/null 2>&1 &
BROWSER_PID=$!
return 0
fi
if command -v xdg-open >/dev/null 2>&1; then
info "No Chromium/Chrome/Firefox detected to auto-configure proxy. Falling back to xdg-open."
xdg-open "$url" >/dev/null 2>&1 &
BROWSER_PID=$!
return 0
fi
err "No method to open a browser found. Please open $url in a browser configured to use 127.0.0.1:${MITM_PORT}"
return 1
}
info "Opening browser to: $VPN_URL"
open_with_proxy "$VPN_URL" || { err "Failed to open browser"; exit 1; }
# ---------- wait for cookie ----------
info "Waiting up to ${TIMEOUT_SECS}s for a new non-empty webvpn cookie..."
start_ts=$(date +%s)
last_status_ts=$start_ts
while true; do
if [[ -s "$COOKIE_FILE" ]]; then
COOKIE_LINE="$(tr -d '\r\n' <"$COOKIE_FILE")"
if [[ "$COOKIE_LINE" == webvpn=* ]]; then
cookie_val="${COOKIE_LINE#webvpn=}"
if [[ -n "$cookie_val" ]]; then
info "Captured cookie: $COOKIE_LINE"
break
else
info "Found webvpn= with empty value; waiting..."
fi
else
info "Cookie file contents did not start with 'webvpn='; '$COOKIE_LINE'"
fi
fi
if ! kill -0 "$MITM_PID" >/dev/null 2>&1; then
err "mitmdump exited unexpectedly. See $MITM_LOG"
exit 3
fi
now_ts=$(date +%s)
if (( now_ts - last_status_ts >= STATUS_INTERVAL )); then
info "Still waiting for cookie... elapsed $((now_ts - start_ts))s"
last_status_ts=$now_ts
fi
if (( now_ts - start_ts >= TIMEOUT_SECS )); then
err "Timed out waiting for cookie after ${TIMEOUT_SECS}s."
exit 2
fi
sleep 0.5
done
# ---------- run openconnect ----------
if (( AUTO_RUN )); then
COOKIE_PAIR="$COOKIE_LINE"
info "Auto-run mode: running openconnect with captured cookie."
run_openconnect "$COOKIE_PAIR"
exit $?
else
echo
echo "==== webvpn cookie (use with openconnect -C): ===="
echo "$COOKIE_LINE"
echo "==============================================="
info "Auto-run disabled; re-run without --no-auto-run to run openconnect automatically."
fi
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment