########################################
-# Get parameters into the initial session setup in order to configure gateways, 2FA requirement
+# Get parameters into the initial session setup in order to configure:
+# gateways: list of gateway names for portal to offer (all will point to same HOST:PORT as portal)
+# portal_2fa: if set, require challenge-based 2FA to complete /global-protect/getconfig.esp request
+# gateway_2fa: if set, require challenge-based 2FA to complete /ssl-vpn/login.esp request
+# portal_cookie: if set (to 'portal-userauthcookie' or 'portal-prelogonuserauthcookie'), then
+# the portal getconfig response will include the named "cookie" field which should
+# be used to automatically continue login on the gateway
@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(','),
+ gateways, portal_2fa, gw_2fa, portal_cookie = request.args.get('gateways'), request.args.get('portal_2fa'), request.args.get('gw_2fa'), request.args.get('portal_cookie')
+ session.update(gateways=gateways and gateways.split(','), portal_cookie=portal_cookie,
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)
@app.route('/global-protect/getconfig.esp', methods=('POST',))
def portal_config():
portal_2fa = session.get('portal_2fa')
+ portal_cookie = session.get('portal_cookie')
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)
+ session.update(step='portal-config', user=request.form.get('user'), passwd=request.form.get('passwd'),
+ # clear inputStr to ensure failure if same form fields are blindly retried on another challenge form:
+ inputStr=None)
gateways = session.get('gateways') or ('Default gateway',)
gwlist = ''.join('<entry name="{}:{}"><description>{}</description></entry>'.format(app.config['HOST'], app.config['PORT'], gw)
for gw in gateways)
+ if portal_cookie:
+ val = session[portal_cookie] = 'portal-cookie-%d' % randint(1, 10)
+ pc = '<{0}>{1}</{0}>'.format(portal_cookie, val)
+ else:
+ pc = ''
return '''<?xml version="1.0" encoding="UTF-8" ?>
<policy><gateways><external><list>{}</list></external></gateways>
<hip-collection><hip-report-interval>600</hip-report-interval></hip-collection>
- </policy>'''.format(gwlist)
+ {}</policy>'''.format(gwlist, pc)
# Respond to gateway login request
def gateway_login():
gw_2fa = session.get('gw_2fa')
inputStr = request.form.get('inputStr') or None
- if gw_2fa and not inputStr:
+ if session.get('portal_cookie') and request.form.get(session['portal_cookie']) == session.get(session['portal_cookie']):
+ # a correct portal_cookie explicitly allows us to bypass other gateway login forms
+ pass
+ elif 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')):
+ elif not (request.form.get('user') and request.form.get('passwd') and inputStr == session.get('inputStr')):
return 'Invalid username or password', 512
- session.update(step='gateway-login', user=request.form.get('user'), passwd=request.form.get('passwd'), inputStr=None)
+ session.update(step='gateway-login', user=request.form.get('user'), passwd=request.form.get('passwd'),
+ # clear inputStr to ensure failure if same form fields are blindly retried on another challenge form:
+ inputStr=None)
for k, v in (('jnlpReady', 'jnlpReady'), ('ok', 'Login'), ('direct', 'yes'), ('clientVer', '4100'), ('prot', 'https:')):
if request.form.get(k) != v:
preferred_ipv6 = None
session.update(preferred_ip=preferred_ip, portal=portal, auth=auth, domain=domain, computer=request.form.get('computer'),
ipv6_support=request.form.get('ipv6-support'), preferred_ipv6=preferred_ipv6)
+ session.setdefault('portal-prelogonuserauthcookie', '')
+ session.setdefault('portal-userauthcookie', '')
session['authcookie'] = cookify(dict(session)).decode()
return '''<?xml version="1.0" encoding="utf-8"?> <jnlp> <application-desc>
<argument>-1</argument>
<argument>4100</argument>
<argument>{preferred_ip}</argument>
- <argument/>
- <argument/>
+ <argument>{portal-userauthcookie}</argument>
+ <argument>{portal-prelogonuserauthcookie}</argument>
<argument>{ipv6}</argument>
</application-desc></jnlp>'''.format(ipv6=preferred_ipv6 or '', **session)