From cbb75f7ddd3b3ec5566116848192a5f0caeae29f Mon Sep 17 00:00:00 2001 From: Daniel Lenski Date: Thu, 29 Apr 2021 11:18:47 -0700 Subject: [PATCH] Add tests of GlobalProtect auth with gateway selection and challenge-based 2FA Signed-off-by: Daniel Lenski --- tests/fake-gp-server.py | 44 ++++++++++++++++++++++++++++++++-------- tests/gp-auth-and-config | 18 +++++++++++++++- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/tests/fake-gp-server.py b/tests/fake-gp-server.py index 375d9d75..11e905c7 100755 --- a/tests/fake-gp-server.py +++ b/tests/fake-gp-server.py @@ -60,16 +60,22 @@ def check_form_against_session(*fields, use_query=False, on_failure=None): ######################################## -# TODO: need a way to get parameters into the initial session setup (just as -# fake-{f5,fortinet,juniper}-server.py do), in order to configure gateways, -# 2FA requirement, etc. +# Get parameters into the initial session setup in order to configure gateways, 2FA requirement +@app.route('/global-protect/testconfig.esp', methods=('GET','POST',)) +@app.route('/ssl-vpn/testconfig.esp', methods=('GET','POST',)) +def testconfig(): + gateways, portal_2fa, gw_2fa = request.args.get('gateways'), request.args.get('portal_2fa'), request.args.get('gw_2fa') + session.update(gateways=gateways and gateways.split(','), + portal_2fa=portal_2fa and bool(portal_2fa), gw_2fa=gw_2fa and bool(gw_2fa)) + prelogin = '/'.join(request.path.split('/')[:-1] + ['prelogin.esp']) + return redirect(prelogin) # Respond to initial prelogin requests @app.route('/global-protect/prelogin.esp', methods=('GET','POST',)) @app.route('/ssl-vpn/prelogin.esp', methods=('GET','POST',)) def prelogin(): - session.update(step='prelogin') + session.update(step='%s-prelogin' % ('portal' if 'global-protect' in request.path else 'gateway')) return ''' Success @@ -85,13 +91,30 @@ def prelogin(): '''.format(request.path) +def challenge_2fa(where): + # select a random inputStr of 4 hex digits, and randomly return challenge in either XML or Javascript-y form + inputStr = '%04x' % randint(0x1000, 0xffff) + session.update(step='%s-2FA' % where, inputStr=inputStr) + if randint(1, 2) == 1: + tmpl = '2FA challenge from %s%s' + else: + tmpl = ('var respStatus = "Challenge";\n' + 'var respMsg = "2FA challenge from %s";\n' + 'thisForm.inputStr.value = "%s";\n') + return tmpl % (where, inputStr) + + # Respond to portal getconfig request @app.route('/global-protect/getconfig.esp', methods=('POST',)) def portal_config(): - session.update(step='portal-config', user=request.form.get('user'), passwd=request.form.get('passwd')) - if not (request.form.get('user') and request.form.get('passwd')): + portal_2fa = session.get('portal_2fa') + inputStr = request.form.get('inputStr') or None + if portal_2fa and not inputStr: + return challenge_2fa('portal') + if not (request.form.get('user') and request.form.get('passwd') and inputStr == session.get('inputStr')): return 'Invalid username or password', 512 + session.update(step='portal-config', user=request.form.get('user'), passwd=request.form.get('passwd'), inputStr=None) gateways = session.get('gateways') or ('Default gateway',) gwlist = ''.join('{}'.format(app.config['HOST'], app.config['PORT'], gw) for gw in gateways) @@ -105,10 +128,13 @@ def portal_config(): # Respond to gateway login request @app.route('/ssl-vpn/login.esp', methods=('POST',)) def gateway_login(): - session.update(step='gateway-login') - if not (request.form.get('user') and request.form.get('passwd')): + gw_2fa = session.get('gw_2fa') + inputStr = request.form.get('inputStr') or None + if gw_2fa and not inputStr: + return challenge_2fa('gateway') + if not (request.form.get('user') and request.form.get('passwd') and inputStr == session.get('inputStr')): return 'Invalid username or password', 512 - session.update(user=request.form.get('user'), passwd=request.form.get('passwd')) + session.update(step='gateway-login', user=request.form.get('user'), passwd=request.form.get('passwd'), inputStr=None) for k, v in (('jnlpReady', 'jnlpReady'), ('ok', 'Login'), ('direct', 'yes'), ('clientVer', '4100'), ('prot', 'https:')): if request.form.get(k) != v: diff --git a/tests/gp-auth-and-config b/tests/gp-auth-and-config index 48e37a92..a66681c8 100755 --- a/tests/gp-auth-and-config +++ b/tests/gp-auth-and-config @@ -41,13 +41,29 @@ echo -n "Authenticating with username/password via portal... " echo ok +echo -n "Authenticating with username/password, and selecting gateway, via portal... " +( echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=gp -q "$ADDRESS:443/global-protect/testconfig.esp?gateways=foo,bar,baz" --authgroup=bar -u test $FINGERPRINT --cookieonly >/dev/null 2>&1) || + fail $PID "Could not receive cookie from fake GlobalProtect server" + +echo ok + echo -n "Authenticating with username/password via gateway... " ( echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=gp -q $ADDRESS:443/gateway -u test $FINGERPRINT --cookieonly >/dev/null 2>&1) || fail $PID "Could not receive cookie from fake GlobalProtect server" echo ok -# TODO: add tests with 2FA +echo -n "Authenticating with username/password via portal, then +token via gateway... " +( echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=gp -q "$ADDRESS:443/global-protect/testconfig.esp?gw_2fa=1" -u test --token-mode=totp --token-secret=FAKE $FINGERPRINT --cookieonly >/dev/null 2>&1) || + fail $PID "Could not receive cookie from fake GlobalProtect server" + +echo ok + +echo -n "Authenticating with username/password/token via gateway... " +( echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=gp -q "$ADDRESS:443/ssl-vpn/testconfig.esp?gw_2fa=1" -u test --token-mode=totp --token-secret=FAKE $FINGERPRINT --cookieonly >/dev/null 2>&1) || + fail $PID "Could not receive cookie from fake GlobalProtect server" + +ok echo -n "Authenticating with username/password via portal, then proceeding to tunnel stage... " echo "test" | LD_PRELOAD=libsocket_wrapper.so $OPENCONNECT --protocol=gp -q $ADDRESS:443/portal -u test $FINGERPRINT >/dev/null 2>&1 -- 2.50.1