I recently had a situation where I needed to ensure only authorised visitors could view a particular page when they supplied the correct URL with valid query string parameters. After some research I decided that a good way to tackle this would be use SHA-256 hashing in the URL, to protect the query string parameters from being tampered with.
To avoid query string parameters being tampered, we would simply add a hash value to the end of the URL to ensure validity, for example:
https://www.mywebsite.com/mypage?x=value1&y=value2&hash=532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25
If an attacker tries to modify any part of the query string parameter values (x, y or hash) the system will know and reject the URL, as the query string values don't match the provided hash value.
Hash Calculation
The hash calculation is fairly simple, all we need to do is concatenate all the query string values along with a unique GUID value (this is known as the Salt value) and then send the resulting string to be hashed using SHA256. This will give us the hash value we use at the end of the URL.
x + y + Salt -> send to SHA256 = hash query string value added to end of your URL
Hash Salt
We simply can't just hash the query string values as someone could run the hash function and work out the hash value themselves. Therefore leaving you open to unauthorised access to your URL. To prevent this a random Salt value is added to the concatenated query string values, those value is only known to the system. Any string can be used for your Salt value, however I'd recommend using something like a GUID value.
Hash Code
Now for some code! First of all lets look at how we can generate a URL with query string hash protection.
public string GenerateUrl(string queryStringOne, string queryStringTwo) { string hashedVariables = GenerateHashToken(queryStringOne, queryStringTwo); string url = String.Format("?x={0}&y={1}&token={2}", queryStringOne, queryStringTwo, hashedVariables); return url; }
In this method we are receiving 2 query string parameters and using those to generate a hash token. Once we have that hash token value, we use String.Format to create a valid URL ending with the hash token.
private string GenerateHashToken(string queryStringOne, string queryStringTwo) { // Use a random GUID for the salt e.g. 313fdac5-af26-44bd-9dbc-70a15d4093c6 string salt = System.Configuration.ConfigurationManager.AppSettings["QueryStringHash.Salt"]; string concatVariables = queryStringOne + queryStringTwo + salt; string hashedVariables = GetSha256Hash(concatVariables); return hashedVariables; }
Next we look closer into what the actual GenerateHashToken method is doing. It gets the Salt value from config, then takes the 2 query string parameter values provided and concatenates them all together. The concatenated values are then sent to the GetSha256Hash method to be hashed.
private string GetSha256Hash(string text) { byte[] bytes = Encoding.UTF8.GetBytes(text); SHA256Managed hashstring = new SHA256Managed(); byte[] hash = hashstring.ComputeHash(bytes); string hashString = string.Empty; foreach (byte x in hash) { hashString += String.Format("{0:x2}", x); } return hashString; }
Finally the GetSha256Hash method takes a string value (text) and then proceeds to hash the string provided.
public bool IsValidUrl(string queryStringOne, string queryStringTwo, string token) { if (string.IsNullOrWhiteSpace(token)) { return false; } string hashedToken = GenerateHashToken(queryStringOne, queryStringTwo); return hashedToken == token; }
Once you have generated a hashed query string URL, the next step is to check for authorised access to see if the hash provided by the user is valid. To do this, we take in the query string parameters including the provided hash token. Check if the hash token is supplied, if not reject straight away. We then generate a hash with the provided query string parameters, then simply test to see if the provided hash matches the actual valid hash. If any of the query string parameters including the hash token have been tampered with this will fail and return a false, therefore rejecting any access.
I put all this code in a Service file called HashQueryStringService.cs and injected the service where needed. To improve this code, you could make it more generic and use Request.QueryString to loop over the values.