As a user, there have been times where I have played with the URL querystring of a page to see what happens when I change the values of various parameters in it. It can, at times, bring about unexpected and interesting results. For example, if you are viewing a page with the URL of http://example.com/viewcustomer?id=1 wouldn't you be a bit curious what happens if you change that to read http://example.com/viewcustomer?id=10 ? I know I would (and have...). Depending on the coding behind the page, sometimes you get error pages or maybe you get to see data and/or results that you're probably not supposed to be seeing.
As a developer, sometimes you need to make absolutely sure the URL querystring that is being parsed by a page hasn't been changed at all. This is especially true for scenarios like sending out links via email for password resets or automatic logins. There is a very simple way to do that and over the years I've been surprised how many times I've had to teach other developers - Junior to Senior - this method.
This technique only works when you know the full URL querystring on the server BEFORE you sent it to the user. How you deliver the URL to the client is immaterial for this, it can be part of the web response to the user or it can be sent to the user in an email, or in any other form that you want to deliver it in.
The 2 key things that that you need on the application server are:
- A secret string (or strings) that is known and to the application server. The string(s) should be fairly long and comprised of random characters or any sort of meaningless gibberish that you can come up with.
- A hash routine on the application server that can do a standard hash like MD5, SHA1. If you don't understand hash functions, then you won't understand how this technique works.
The quick summary of how to do this:
To create the URL:
- Take the parameter you want to verify and append the secret string to it.
- Hash the value of resulting string.
- Take the hash value and append it to the querystring as an additional parameter.
- Return the new URL string to the user in some fashion (email, webpage, whatever).
To verify the URL:
- Take the parameter value in the URL and then append the secret key to it.
- Hash the value of resulting string.
- Take the hash value that you calculated and compare it to the hash value in the URL string. If they match, then you have verified that the original parameter is unchanged. If they don't match, then something (or someone) has changed the parameter and it shouldn't be trusted. That's all there is to it.
There's also no real restrictions on what language you use to do this in. I've done this in Java, PHP and C#, but there's no reason you couldn't do it in other languages as well.
Here's a simple example that shows it in action.
Let's say that you want your site to be able to email users an autologin link so that when they click it it will automatically log them in, which is an especially nice feature when a new website is in development. For this feature, we have a special hidden page called autologin which takes a querystring of "user" and the username of the account to autologin, looking something like this: http://example.com/autologin?user=jeff .
Pretty simple stuff, however there's a glaring hole whereby any user could put any username into the querystring and, if the username happens to match a valid account, the user would be logged in under that account. That's not good.
On our server, we've created a secret string with the value of "asdf1234qwerty" (normally you'd create a much longer secret string). The first thing we do is append the parameter to the secret string, which gives us a result of "jeffasdf1234qwerty". Next we do an MD5 hash on it (I normally use SHA1, but for our example MD5 gives us a shorter result) which gives us a hash value of "31c940a5a60bcd1022cd8dab2be76b98". Then we add the hash value to our querystring with a parameter name of "key". Our verifiable URL looks like this: http://example.com/autologin?user=jeff&key=31c940a5a60bcd1022cd8dab2be76b98 . We then send the url to the client (or bookmark it, or whatever).
Later, our autologin page gets a request, and the querystring for that request is "user=jeff&key=31c940a5a60bcd1022cd8dab2be76b98". On the server, we now need to check the URL to see if it's correct. We take the user value ("jeff") and append our secret key to it, giving us "jeffasdf1234qwerty". We then hash that value which gives us "31c940a5a60bcd1022cd8dab2be76b98". We then compare that hash value against the "key" parameter. Since they match, we know the URL is good and we log the user in.
That's the way it *should* work, but let's look at what happens if someone decides to change the URL between the time we've generated it and sent it to the user and when the autologin page receives a request. Let's assume that we've created the URL for the "jeff" user as shown above, however the user wants to see if they can get into the "mike" account. They take the URL and change the "user=jeff" parameter of the querystring into "user=mike" (they didn't touch the "key" parameter) so the URL now looks like this: http://example.com/autologin?user=mike&key=31c940a5a60bcd1022cd8dab2be76b98 . When that request is received by the autologin page, it performs the same logic as described above. Take the user and append it to the secret string which gives us "mikeasdf1234qwerty". Then hash that result which gives us a value of "8aab50f7c16e76bf32f6d9a8f77c8582". Then comare that hash result to the hash in the "key" parameter in the querystring. Since "31c940a5a60bcd1022cd8dab2be76b98" != "8aab50f7c16e76bf32f6d9a8f77c8582" we know that the URL has been changed and we also know that the request should be ignored or redirected, with the end result being that the user is unable to hack their way into the "mike" account.
There are some additional variations that you can do on this to introduce additional features into a verifiable URL including expiring requests by date or using short URL's to manage large querystrings. I'll get into those in another post.