While writing the DER Batch Reset Tool, I was forced to investigate how the DETNSW handles its SSO. The original plan was just to obtain a session cookie from the browser and use the cookie in the reset tool, but it was rather tedious copying a cookie every few minutes while coding up the script. The batch reset tool uses the Python requests module, which has session support to store cookies between requests.

The DETNSW SSO server is probably somewhere around a decade old, as I remember using the same system as a student when I first started high school. The one thing that has changed is that one less set of credentials is required when using one of the DETNSW's services such as the student/staff portal.

All the computers connected to the DETNSW's network are part of a state-wide intranet in the 10.x.x.x Class A network. Direct connections to the internet are filtered, thus all internet traffic must go through the department HTTP proxy proxy.det.nsw.edu.au:8080 with the exception of some licensing and update servers. The proxy is an HTTP proxy with Basic authentication (through HTTP headers of the first connection). They have no support for HTTPS proxy.

Capturing Proxy Authentication Credentials

Initially, to visit the student/staff portal, one would need to enter their username and password for the proxy authentication and then again on the SSO login page. However, some years later, the SSO login page got replaced with a page which seems to automatically authenticate users based on the proxy authentication. One can only assume this was done because staff complained about having to enter their credentials twice if they just opened a browser and want to visit a DETNSW site.

When requesting a DETNSW site, you get redirected to this non-secure URL, where some magic happens and you are automatically authenticated. Is this yet another page for details to leak?

SSO Auto Login

And here is the page itself. Looks like an innocent enough form and it POSTs to a secure site! Do we have some actual security?

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Portal Login</title>
        <meta http-equiv="content-type" content="text/html; charset=iso-8859-1"/>
        <meta name="author" content="Andy {REDACTED}"/>
    </head>
    <script type="text/javascript" src="../jfunctions.js"/></script>
    <body onload="javascript: submit_form();">
        <br/><br/><br/>
        <p>
            <h2 align="center">Please wait while you are being logged into the DET Enterprise Portal...</h2>
        </p>
        <form name='loginform' method='post' action='https://sso.det.nsw.edu.au/sso/UI/Login?goto=...' >
            <input type="hidden" name="ssoDetails" value="dXNlcm5hbWU=:cGFzc3dvcmQ=">                     
            <input type="hidden" name="site2pstoretoken" value="">
            <input type="hidden" name="ssousername" value="">
            <input type="hidden" name="password" value=""> 
            <input type="hidden" name="IDToken1" value=""> <!-- BA for openam -->
            <input type="hidden" name="IDToken2" value=""> <!-- BA for openam -->           
        </form>
    </body>
</html>

Oddly enough, if you POST all the details shown here using the Python requests module, it doesn't authenticate the user and redirects you to the sign in page to enter your credentials. So what's the difference between the browser and Python requests?

JavaScript. They must be using JS to do more than just submit the form automatically. The hidden value ssoDetails is already part of the form but looks suspiciously like base64. What could they possibly be doing?

var base64Chars = new Array(
        'A','B','C','D','E','F','G','H',
        'I','J','K','L','M','N','O','P',
        'Q','R','S','T','U','V','W','X',
        'Y','Z','a','b','c','d','e','f',
        'g','h','i','j','k','l','m','n',
        'o','p','q','r','s','t','u','v',
        'w','x','y','z','0','1','2','3',
        '4','5','6','7','8','9','+','/'
        );

...

function submit_form() {
    var details = document.forms['loginform'].elements['ssoDetails'].value;
    var details_array=details.split(":");
    document.forms['loginform'].elements['ssousername'].value = db(details_array[0]);
    document.forms['loginform'].elements['password'].value = db(details_array[1]);
    document.forms['loginform'].elements['IDToken1'].value = db(details_array[0]); // for openam
    document.forms['loginform'].elements['IDToken2'].value = db(details_array[1]); // for openam
    document.forms['loginform'].submit();
}

Surprise surprise... base64. (The entire JS file is available here). It looks like they have implemented base64, probably for IE users as IE only had base64 JS functions from version 10 onwards.

And at first glance, it's evident they are populating the form with details from the ssoDetails form value. This means the DETNSW servers have an un-hashed copy of everyone's password stored somewhere on a server and is willing to send out a base64 encoded version of it through an unencrypted connection.

It's understandable that they need to store a plaintext password, as the department has countless numbers of systems to integrate and some of them are ancient. What's not understandable is why the SSO server must take a username and password. Surely they could have programmed the SSO server to talk to the proxy authentication server and use a token of some sort.

So now, we don't even need Wireshark to steal a password, we just need to get the target user to authenticate the proxy connection.

Taking a further look at the response headers for that page which returns us the username and password, we can see it doesn't even disable caching.

SSO Authentication Headers

Ideally, the server should set the following headers:

Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0

We can just trawl through the browser's cache and extract the details. So now we don't even need the user to authenticate on the spot!

SSO Authentication Cache

Fortunately, if you haven't been active on a DETNSW site for a few minutes, they will invalidate your session and prompt you to log in manually. Okay, so what if the cache entry has been overwritten due to newer content and the user has been logged in previously (more than a few minutes ago) in this browser session?

Delete invalidated cookies

The solution is 3 key strokes and some clicks away. And since the SSO server doesn't talk to the proxy authentication server, you are treated as if you had just opened the browser and will be handed the account password. So this security timeout feature is essentially useless. Due to limitations of the HTTP proxy authentication, there is no proper way for the proxy server to know when a user entered their credentials into the browser (since the browser keeps a copy of the credentials in memory and uses it automatically every time a new connection is made).

In summary

  • Proxy is HTTP where the password is sent in plaintext (base64 encoded)
  • User credentials are stored in plaintext (un-hashed)
  • User credentials are sent to the user to process the login
  • User credentials are not protected with TLS
  • User credentials are allowed to be cached in the browser's cache
  • SSO auto-login server can't determine how long since a user authenticated with the proxy

I'll give Andy the benefit of the doubt and assume he didn't really have a choice when he implemented this auto-login. I'm sure someone has signed off on his work knowing exactly how bad the security is.

The good news is that DETNSW is planning to move to NTLM-authentication. This is fine for devices part of the DETNSW domain and using Internet Explorer, any other browser/device will still be required to enter a username and password. The bad news is that NTLM is really broken, as it is vulnerable to MITM and pass-the-hash attacks. Even with these vulnerabilities, it is still way more secure than plaintext passwords. Hopefully they add support for HTTPS proxy for non-domain computers.

Next Post

Related Posts