Many web apps rely on some form of client-side state management. It's either stored in a cookie, or mangled into the URL. Regardless, have you ever considered what would happen if a user were to make small changes to (or wholesale replace) that state? It's really easy to do if you have the right tools (all you need is a browser to play with mangled URLs).
What you need is tamper detection. And it's really pretty easy to implement (you get it for free if you're storing your state in a forms auth cookie). A simple approach is to use a keyed hash, preferably a tried-and-true algorithm like HMAC coupled with a decent cryptographic hash like SHA1. The idea works like this: you hash the data you're going to give to the user, but you hash it along with a secret that only your web server knows. Then you send the data (not the secret) to the user, with the hash value embedded in it. When you get that data back, before using it, you strip off the hash, and do the same computation (using the same key) and ensure that the hashes match. If they do, you can feel confident that the data hasn't been tampered with.
Here's the code for a simple class that does just that, along with a method for generating a random key that you can stuff in your web.config file. This class was written as a sample; one obvious improvement would be key versioning - you don't want to assume you'll be using the same key a year from now!
using System;
using System.Text;
using System.Security.Cryptography;
using System.Configuration;
public class TamperDetector {
public static string AddTamperDetection(string s) {
byte[] data = Encoding.UTF8.GetBytes(s);
byte[] hash = getKeyedHash().ComputeHash(data);
return Convert.ToBase64String(hash) + '|' + s;
}
public static string CheckAndRemoveTamperDetection(string s) {
int i = s.IndexOf('|');
if (-1 == i) throw new Exception("Malformed string");
string prefix = s.Substring(0, i);
string suffix = s.Substring(i+1);
byte[] hash = Convert.FromBase64String(prefix);
byte[] data = Encoding.UTF8.GetBytes(suffix);
byte[] computedHash = getKeyedHash().ComputeHash(data);
if (!isEqual(hash, computedHash))
throw new Exception("String has been modified!");
return suffix;
}
static HMACSHA1 getKeyedHash() {
string skey = ConfigurationSettings.AppSettings["integrityKey"];
byte[] key = Convert.FromBase64String(skey);
return new HMACSHA1(key);
}
static bool isEqual(byte[] a, byte[] b) {
if (a.Length != b.Length) return false;
for (int i = 0; i < a.Length; ++i)
if (a[i] != b[i]) return false;
return true;
}
public static string GenerateRandomKey() {
byte[] rnd = new byte[16]; // 128 bits
new RNGCryptoServiceProvider().GetBytes(rnd);
return Convert.ToBase64String(rnd);
}
}
And here's some code that tests it:
using System;
using System.Data;
using System.Data.SqlClient;
class Program {
static void Main(string[] args) {
try {
if (args.Length != 1) {
Console.WriteLine("Usage: tamperDetect stringToProtect");
return;
}
string p = TamperDetector.AddTamperDetection(args[0]);
Console.WriteLine(p);
string c = TamperDetector.CheckAndRemoveTamperDetection(p);
Console.WriteLine(c);
}
catch (Exception x) {
Console.WriteLine(x);
}
}
}
You'll need a config file with an appSettings section that has a valid key. Kinda like this (but please use the key generation function to generate your own in production!
<configuration>
<appSettings>
<add key='integrityKey' value='IGEbiRJcL/EHl+kCSd5TDw=='/>
</appSettings>
</configuration>
Here's what it looks like when run:
91 C% tamperDetect "Hello world"
xXyU/Q0a2K5nbMfhzozk4Yczt4Y=|Hello world
Hello world
Posted
Dec 21 2005, 07:24 PM
by
keith-brown