One day I was perusing a client’s site during a business logic assessment, watching in my proxy history for anything interesting, when I noticed something peculiar after logging in. I saw two POST requests, both containing my credentials, but posting to two different URLs. The first request was an authentication script, such as /validateLogin, and it looked like it belonged. The second, however, made no sense at all: It was posting to the /terms-and-conditions page. With my “hacker sense” tingling and my curiosity piquing, I dug deeper.
During the most recent test, the value of “action” was simply an empty string, and therefore the value posted back to the same page. However, when compared to the initial request that sparked my interest, the “action” attribute was set to “/terms-and-conditions”, which, lo and behold, was the page I had visited immediately prior to accessing the login page. Sure enough, a quick test confirmed that the form’s “action” was being populated from the “Referer” header.
I assumed the JS-generated POST was part of an upgrade to the application’s authentication system, and the original method for logging in wasn’t properly retired. Regardless, the fact remains that at some point in time, the developers decided to trust the “Referer” header and use it as part of their authentication mechanism.
And while I’m interested to know how often the “Referer” is used this way in the wild, it brought to light a bigger issue: You can’t trust any part of a request.
From a development perspective, major frameworks give developers access to various aspects of a request object through identifiers and properties, such as Request.Headers (C#). From a hacker’s perspective, using Burp Suite for example, every bit (literally) of a request is easily manipulated and open to attack.
Your everyday Internet user, Joe Schmo, is only going to have control over the URL and (maybe) some GET/POST parameters while interacting with a web app using his browser. He’s unlikely to intentionally tamper with cookies, headers, or any other part of the request.
However, attacker Billy Bob will exercise direct control over every byte of the request if doing so results in some form of lulz or profit. The request method, path, query string, HTTP version number, headers, and body are all fair game in the world of hacking. As a developer, the moment you trust any piece of that request, you open yourself up to attack.
In my above example, with the “Referer” header being used as the action for the login request, a working exploit was trivial. As a proof of concept, I simply created a page that auto-redirected to the vulnerable login page. That’s it. Well, mostly.
I sent the link to my target victim, my boss, and made sure my redirection page captured any login data that got posted back to it. I wish this part of the story had a more interesting ending, but my boss knows better than to click on links I email him, so it didn’t really play out. In the real world, however, if this application’s login page ever made its way onto a Google search results page, for example, any users following that link and logging in would wind up posting their credentials back to Google.
While it’s not difficult to exercise a certain amount of control over the “Referer” header when trying to actively exploit another user, not all parts of a request are easily spoofed. However, just because something can’t be done today doesn’t mean it won’t be doable tomorrow. New web technologies − and their corresponding vulnerabilities − change the threat landscape regularly. It’s more like a raging ocean than a solid-sounding “landscape”.
As web technologies become more advanced, and attacks get more sophisticated, the issue of trust becomes all the more important. The safest position is to never trust any data, whether it’s perceived to be “user input” or not. Put safeguards in place for everything, because you never know where an attack will come from next.