Published on

Automated Cisco Anyconnect Connect + Duo HOTP Bypass

Authors
  • avatar
    Name
    Teddy Xinyuan Chen
    Twitter
Table of Contents

Why?

So I recently got my hands on NCSU's HPC Cluster, to ssh into it I need to use the school's VPN.

Since I'm not an expert of Cisco VPN prodcut yet, I decided to follow the manual to connect to the VPN via the GUI:

2FA (6-digit code from Duo) required, which is a PITA

So I need to open the Duo app on my phone to get the code, memorise it, and type it in. The frequent VPN session time out made me decide to dig around and find a way to bypass all this hassle.

How?

  1. Research connecting to Cisco Anyconnect via command line (/opt/cisco/anyconnect/bin/vpn).
    The tool is usable, even though I'm not a fan of its design of getting commands from stdin.
    A few failed attempts made me realized that the group needs to be a number, and I don't need the y at the end like the person suggested.

  2. Now all I need is to find a way to generate the multiline command for the vpn tool.
    For username and password, I can get them from system keychain. Now what's left is the OTP from Duo.

  3. For Duo I found this repo (god bless his soul), who reverse engineered what happens when you scan the Duo's add device QR code and gave a working prototype.
    I happily grabbed the code. I tried the extracted OTP secret with an OTP (TOTP, more specifically) app, and it gave wrong codes. Why?

  4. Turns out that Duo is using HOTP (HMAC-based), not TOTP (time-based).
    Easily solvable with the fantastic pyotp library.
    HOTP requires the secret and a counter to generate the code, so in my code I fetch the current count from keychain, use it to generate the code, then increment it by one and update it in the keychain. It worked.

Tools used

  • /opt/cisco/anyconnect/bin/vpn - Cisco AnyConnect Secure Mobility Client
  • https://github.com/revalo/duo-bypass
  • pyotp - Python library for generating HOTP and can do a lot more
  • keyring - Python library for accessing the system keychain

Things I learned

  • HMAC-based OTP that requires a counter.
  • Major password managers like Bitwarden doesn't support HOTP yet, redditor said the requirement to updating the count makes it not very usable when offline.
  • Messing with this is more fun then what I was doing previously today, which was reading a lot of docs and only wrote 2 lines of code. It was JavaScript, btw. :)
  • What's also fun is connecting the pieces together, although I wasn't always able to do that.

Some of the code

Update 3/22/2024: anyconnect has been renamed to secureclient, you should do a find and replace in your scripts for it to continue to work.

# ~/config/scripts/ncsu_vpn_connect.py

def get_hotp(secret, count, digits: int = 6) -> str:
    import pyotp

    hotp = pyotp.HOTP(secret, digits=digits)
    return hotp.at(count)


def get_connect_commands(count: int) -> str:
    import keyring

    username = keyring.get_password("blog-example", "UNITY_ID")
    password = keyring.get_password("blog-example", "UNITY_PASSWORD")
    otp_secret = keyring.get_password("blog-example", "duo-hotp-secret")

    # 1 for student group
    commands = f"""1
{username}
{password}
{get_hotp(otp_secret, count)}
y
exit
"""
    return commands


# ....
# ....
# ....

def main():
    """Make a jazz noise here"""

    args = get_args()
    if args.set_count:
        set_count(args.set_count)
        return
    if args.show_current_count_only:
        print("Current count:", get_count())
        return
    if args.count:
        count = args.count
    else:
        count = get_count()
    commands = get_connect_commands(count)
    print(commands, end="")
    if not args.no_advancing_counter:
        set_count(count + 1)
ncsu_vpn_connect() {
    # if this command doesn't fail, then it's connected
    # /opt/cisco/anyconnect/bin/vpn -s state | command grep 'state: Connected'
    # if it's not connected, then connect
    if [[ -n $(/opt/cisco/anyconnect/bin/vpn -s state | command grep 'state: Connected') ]]; then
        echo "Already connected to NCSU VPN"
        return 0
    fi
    ~/config/scripts/ncsu_vpn_connect.py | /opt/cisco/anyconnect/bin/vpn -s connect vpn.ncsu.edu
}

Connection log

$ ncsu_vpn_connect

Cisco AnyConnect Secure Mobility Client (version 4.10.06079) .

Copyright (c) 2004 - 2022 Cisco Systems, Inc.  All Rights Reserved.


  >> state: Disconnected
  >> state: Disconnected
  >> notice: Ready to connect.
  >> registered with local VPN subsystem.
  >> contacting host (vpn.ncsu.edu) for login information...
  >> notice: Contacting vpn.ncsu.edu.

  >> Connection Banner
  >> Duo 2-Step Verification is required for all VPN groups except the 4-Vendor and 8-Workshop-Temp groups. Enter your UnityID, password, and in the Second Password field type push, sms, or a passcode to authenticate.

  >> Please enter your username and password.
    0) 1-Faculty-and-Staff
    1) 2-Student
    2) 3-Student-Health-Center
    3) 4-Vendor
    4) 5-OIT-Staff
    5) 6-Faculty-and-Staff-FT
    6) 7-Student-FT
    7) 8-Workshop-Temp
Group: [2-Student] 
Username: [teddysc.me] teddysc.me
Password: 
Second Password: 
  >> state: Connecting
  >> notice: Establishing VPN session...
  >> notice: The AnyConnect Downloader is performing update checks...
  >> notice: Checking for profile updates...
  >> notice: Checking for product updates...
  >> notice: Checking for customization updates...
  >> notice: Performing any required updates...
  >> notice: The AnyConnect Downloader updates have been completed.
  >> state: Connecting
  >> notice: Establishing VPN session...
  >> notice: Establishing VPN - Initiating connection...
  >> notice: Establishing VPN - Examining system...
  >> notice: Establishing VPN - Activating VPN adapter...
  >> notice: Establishing VPN - Configuring system...
  >> notice: Establishing VPN...
  >> state: Connected


$ ncsu_vpn_connect

Already connected to NCSU VPN

btw I also made this

https://pypi.org/project/pyotp-cli

pipx install pyotp-cli

pyotp-hotp 'mysecret' 999

# usage: pyotp-hotp [-h] [-d DIGITS] SECRET COUNT
# 
# Get OTP Using HOTP
# 
# positional arguments:
#   SECRET                Secret
#   COUNT                 Counter value
# 
# options:
#   -h, --help            show this help message and exit
#   -d DIGITS, --digits DIGITS
#                         Number of digits (default: 6)

References