dlenski / gp-saml-gui

Interactively authenticate to GlobalProtect VPNs that require SAML
GNU General Public License v3.0
307 stars 69 forks source link

Getting None for USER and COOKIE #2

Closed nicklan closed 5 years ago

nicklan commented 5 years ago

Thanks for this! Unfortunately, doesn't seem to work quite right with our setup. If you let me know how to get even more verbose logs I can do that for you too. Here's the output with -v:

Got SAML POST, opening browser...
Finished loading about:blank
Finished loading https://company.okta.com/app/panw_globalprotect/[snip]/sso/saml
Finished loading https://company.okta.com/login/sessionCookieRedirect
Finished loading https://[company-vpn].com/SAML20/SP/ACS
Got SAML relevant headers, done: {'saml-auth-status': '1'}

SAML response converted to OpenConnect command line invocation:

    echo None |
        openconnect --protocol=gp --user=None --usergroup=gateway:None --passwd-on-stdin [company-vpn].com/global-protect/prelogin.esp

HOST='https://[company-vpn].com/global-protect/prelogin.esp/gateway:None'
USER=None
COOKIE=None
dlenski commented 5 years ago

Hey @nicklan, interesting, thanks!

This code (https://github.com/dlenski/gp-saml-gui/blob/master/gp-saml-gui.py#L63-L73) basically says, “If we got any SAML-relevant headers in a page, assume that we're done with the authentication process.”

For the VPNs I have access to, this works great because we get the cookie and SAML username in one fell swoop.

Evidently that's too simplistic. Perhaps another form-submission or redirection is needed before your VPN returns the cookie+username headers? Try the following patch:

diff --git a/gp-saml-gui.py b/gp-saml-gui.py
index 1f2b1b5..6251096 100755
--- a/gp-saml-gui.py
+++ b/gp-saml-gui.py
@@ -66,7 +66,7 @@ class SAMLLoginView:
                     t.append((name, value))
             h.foreach(listify)
             self.saml_result = d = dict(l)
-            if d:
+            if 'saml-username' in d and ('prelogin-cookie' in d or 'portal-userauthcookie' in d):
                 if self.verbose:
                     print("Got SAML relevant headers, done: %r" % d, file=stderr)
                 self.success = True
dlenski commented 5 years ago

If you let me know how to get even more verbose logs I can do that for you too.

  1. Use -vv to make it extra verbose (will show all the XHR requests issued within the browser).

  2. Use gp-saml-gui.py --external to open the SAML request page in an external browser (Chrome or Firefox) and use the browser's dev tools for detailed inspection of the XHR requests.

My assumption here is that all of these SAML authentication flows for GP eventually result in some *cookie and saml-* headers getting plunked into the HTTP response…

TODO: Add an --inspector option to add the WebKit inspector to the browser Window, so that an external browser isn't needed. I got some advice on how to do this here.

nicklan commented 5 years ago

Yeah, I just hacked some code in to show what was coming back. So whatever we have set up doesn't return things in the headers it seems.

Headers are:

Date: (date)
Content-Type: text/html; charset=UTF-8
Content-Length 1792
Connection keep-alive
ETag: "[some hex]"
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Expires: (date))
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block;
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; img-src * data:; style-src 'self' 'unsafe-inline';
saml-auth-status: 1
Strict-Transport-Security: max-age=31536000;

It looks like the useful data is burried in the page html, which includes:

<input name="SAMLResponse" type="hidden" value="PD94bWwg....[big long base64(ish, need to strip out &#x2b; unicode codes) string">

However, when I base64 decode that I get a blob of bytes that I can't figure out. Specifically it's not gzip, zip, or deflate afaict. This page suggests it should be deflated, but zlib-zpipe can't inflate it.

Anyway, the script I'm using that works passes: jnlpReady='jnlpReady' as part of the POST data to https://[gateway]/ssl-vpn/login.esp, which causes it to return XML which is much easier to parse.

dlenski commented 5 years ago

It looks like the useful data is burried in the page html, which includes: SAMLResponse form field

So what happens if you apply the patch I suggested and submit that form?

Did you walk through this in Chrome/Firefox debugger? I assume that's what happens.

So whatever we have set up doesn't return things in the headers it seems.

Hmmm. I think the headers are how the official Windows client detects that it's “done” with the auth flow. I imagine you just have to submit one more form or go through one more JavaScript redirect or something…

Anyway, the script I'm using that works passes: jnlpReady='jnlpReady' as part of the POST data to https://[gateway]/ssl-vpn/login.esp, which causes it to return XML which is much easier to parse.

Ah. I presume that means your VPN has an option to login through the gateway (/ssl-vpn) using GP's "native" authentication, without going through SAML.

Is that correct?

nicklan commented 5 years ago

Ohh, with the patch you suggested it seems to work, yay :)

Not sure about the setup tbh, I just use it.

dlenski commented 5 years ago

Ohh, with the patch you suggested it seems to work, yay :)

Cool. That confirms that it works how I expected. Fixed in f923c12.