Use of a custom ASP.NET HTTP Module to digest querystring arguments for authenticity.
What is SHA-1 encoding?
do any page processing.
We do this by taking the querystring variables, in this case 'variable1=foo&variable2=bar' and we encode them using SHA. One very important detail not to be missed is that we seed our hash with a "secret string". This secret string is prepended to all messages so that a malicious user couldn't just use SHA to encode their own digests to tack on to the querystring.
Storage of this secret key is important. Once mechanism is to use a Session variable to do all of your encoding. This transient value will change per user, per session, and has a finite lifespan. If you find that you need to pass digest values across web applications you'll need to determine what key storage method is good for you (DB/config file/other).
Here is what an SHA message digest looks like (encodedwithout a secret key):
variable1=foo&variable2=bar - > (SHA encoder) -> a33255a17a479d6fa3745f1e0da2bc5dbcc2e863
With a prepended secret string 'mysecret':
We can then pass this digest on the querystring as such:
http://www.sitename.com/index.aspx?variable1=foo&variable2=bar&digest=94ce42f36c5537d76503ef994f57c024a9614c70
Now our module will take the querystring (sans digest) and recompute using our secret string. If the new message digest doesn't match the passed in digest, we know that something may be wrong and we can deal with that appropriately (presenting user with an error page).
References
- Introduction to HTTP Modules: http://msdn2.microsoft.com/en-us/library/ms178468.aspx
- Introduction to ASP.NET Page Lifecycle: http://msdn2.microsoft.com/en-us/library/ms178473.aspx
- MSDN Mag - HTTP Modules: http://msdn.microsoft.com/msdnmag/issues/02/05/asp/
- SHA-1 Encoding : http://www.w3.org/PICS/DSig/SHA1_1_0.html / http://en.wikipedia.org/wiki/SHA-1
- Digital Signature Standard : http://www.itl.nist.gov/fipspubs/fip186.htm
Code
////// QuerystringVerificationModule /// /// Used to verify all QueryString arguments given passed in 'digest' /// /// Required: /// Querystring that has the 'digest' variable set using ERA.Core.Util.MessageDigestEncoder /// /// (example: http://localhost/era/sample.aspx?caid=24&actionType=PASS&personID=0&digest=7b412bfd87459b5ebd360ee93b23546685923510 ) /// public class QueryStringVerificationModule : IHttpModule { public QueryStringVerificationModule() { } public void Init( HttpApplication app ) { app.BeginRequest += new EventHandler( this.Begin_Request ); } private void Begin_Request( Object source, EventArgs e ) { HttpApplication app = (HttpApplication)source; HttpContext context = app.Context; NameValueCollection queryString = context.Request.QueryString; if ( context.Request.RawUrl.Contains( ".aspx" ) && queryString.Keys.Count > 0 ) { string passedInArgs = ConstructPassedInArgs( queryString ); string computedDigest = MessageDigestEncoder.Instance.ComputeDigest( passedInArgs ); string passedInDigest = queryString[Digest]; if ( string.IsNullOrEmpty( passedInDigest ) ) { throw new NullOrBlankDigestException( "If you have QueryString arguments, you must pass in a digest value" ); } if ( passedInDigest != computedDigest ) { throw new InvalidDigestException( "Invalid DIGEST values computed: " + computedDigest + " passedIn: " + passedInDigest + " inputs: " + passedInArgs ); } } public static string ConstructPassedInArgs( NameValueCollection queryString ) { StringBuilder sb = new StringBuilder(); foreach ( string k in queryString.Keys ) { sb.Append( k ); sb.Append( "=" ); sb.Append( queryString[k] ); sb.Append( "&" ); } return sb.ToString().TrimEnd( '&' ); } }






