Xerratus
Happily stressed out, since 1974
Wednesday, October 25, 2006
<< .NET 2.0 Global.asax code in separate file
Outlook 2003 Auto-Correct Anomaly >>
ASP.NET Encoding QueryString Data to Limit Tampering
We've all been there. We develop a web application that uses the querystring to pass data from a master list page to a detail page. Usually just the ID is passed (just a good programming technique) and you even check and double check the value and type of data so that only the user who is supposed to see it, sees it. But the temptation to change the id for the user is too great and nobody ever likes seeing any type of identifier in the querystring.
The solution I've developed simply encodes the querystring name/value pairs so that it is unreadable but can be retrieved and created with relative ease.
Note:
This is solution does not encrypt/decrypt the values. Rather it uses Base64 encoding to render the string unreadable. It
is
possible for somebody with knowledge to hack this but you're a good programmer... and you've solved for this, just as you did if a user simply changed the ID; right?
So back to the solution:
First, we need to create a simple class to encode and decode Base64 values.
public
sealed
class
Base64
{
public
static
string
Encode(
string
data)
{
try
{
byte
[] encData_byte =
new
byte
[data.Length];
encData_byte =
Encoding
.UTF8.GetBytes(data);
string
encodedData =
Convert
.ToBase64String(encData_byte);
return
encodedData;
}
catch
(
Exception
e)
{
throw
new
Exception
(
"Error in base64Encode "
+ e.Message);
}
}
public
static
string
Decode(
string
data)
{
try
{
UTF8Encoding
encoder =
new
UTF8Encoding
();
Decoder
utf8Decode = encoder.GetDecoder();
byte
[] todecode_byte =
Convert
.FromBase64String(data);
int
charCount = utf8Decode.GetCharCount(todecode_byte,
0
, todecode_byte.Length);
char
[] decoded_char =
new
char
[charCount];
utf8Decode.GetChars(todecode_byte,
0
, todecode_byte.Length, decoded_char,
0
);
string
result =
new
String
(decoded_char);
return
result;
}
catch
(
Exception
e)
{
throw
new
Exception
(
"Error in base64Decode "
+ e.Message);
}
}
}
Next, we create a QueryString class and give it an indexer to easily retrieve the values using an ordinal. The getter returns null if the ordinal is empty, the querystring is not found or if the ordinal does not match up with any items found. If Request.QueryString["q"] is found, we decode the string, split the pairs, loop thru each pair splitting the name/value out, and match on the ordinal. If a match is found, we simply return the value of the item back.
public
string
this
[
string
ordinal]
{
get
{
if
(ordinal ==
""
)
{
return
null
;
}
if
(
HttpContext
.Current.Request.QueryString[
"q"
] ==
null
)
{
return
null
;
}
// decodes to name/value pairs: id=1&something=what&test=true
string
decode;
try
{
decode =
Base64
.Decode(
HttpContext
.Current.Request.QueryString[
"q"
]);
}
catch
(
Exception
)
{
return
null
;
}
string
[] pairs = decode.Split(
'&'
);
foreach
(
string
pair
in
pairs)
{
string
[] item = pair.Split(
'='
);
if
(item[
0
].ToLower() == ordinal.ToLower())
{
return
item[
1
];
}
}
return
null
;
}
}
For encoding, we supply a simple encode() method.
public
string
EncodePairs(
string
data)
{
if
(data ==
""
)
{
return
""
;
}
return
Base64
.Encode(data);
}
Now, for the UI:
To retrieve the querystring items, create an instance of QueryString (I chose to make it a disposable object so I could use the using pattern), check to see if the ordinal exists. If it does, cast it to whatever datatype your system calls for.
// Retrieve the querystring values
using
(
QueryString
q =
new
QueryString
())
{
IdLabel.Text = (q[
"id"
] !=
null
) ? q[
"id"
] :
""
;
NameLabel.Text = (q[
"name"
] !=
null
) ? q[
"name"
] :
""
;
ActiveLabel.Text = (q[
"active"
] !=
null
) ? q[
"active"
] :
""
;
}
To encode pairs, build up the querystring like you normally would (leaving off the starting "?") and use the encode() method to encode it. Then, append it to whatever page you are redirecting to as q=encodedstring.
string
pairs =
String
.Format(
"id={0}&name={1}&active={2}"
, IdTextBox.Text, NameTextBox.Text, ActiveRadioButtonList.SelectedValue);
string
encoded;
// Set the querystring values
using
(
QueryString
q =
new
QueryString
())
{
encoded = q.EncodePairs(pairs);
}
Response.Redirect(
"~/Default.aspx?q="
+ encoded);
This is just a quick demonstration how one can use encoding to limit tampering when passing querystring data to another page. There are many ways to achieve this without even using a querystring but for those instances where you either have to or want to, you now have another option.
One last note:
This can be taken one step further if encryption is needed. Just extend the QueryString class to encrypt/decrypt rather than encode/decode. There are risks there as well but that's for you to decide.
Download the solution:
QueryStringEncoding.zip (4.13 KB)
Wednesday, October 25, 2006 @ 11:48 AM (-07:00) Pacific Daylight Time
Comments (8)
tags:
Programming
admin: Edit | Remove
Wednesday, October 25, 2006 11:21:45 AM (Pacific Standard Time, UTC-08:00)
Dude... so funny.
I did something exactly like this. But I did an MD5 hash on it. I even stick the current, date/time in w/ it, and can tell the decrypt function that if it's been more than x minutes or x seconds, that even if successfully decrypts to count it as invalid.
I also have a DecryptNoTime method that removes the timestamp on decryption. The cool thing about this is, when you encrypt a value, it's different every time. So you hit refresh on the page that links to a detail, and bam, every time you refresh, it's different.
Again, like yours, my method is not exactly hack proof, but it goes well beyond a 'good' programmer to beat it. Not putting yours down at all.
The other idea I've played with recently is a PageBase class, and stuffing the value into HttpContext.Current.Items, but unless tied to a specific user or session, it could be problematic.
If you want my 'Crypto' class, let me know. I'll send it over... you could probably even make it better. One last thing, it lets you set your own 'passphrase' in a config file, so you can use the same DLL across MANY applications without direct fear of encrypting values the same way.
Talk to you later ....
John
John Batdorf
Wednesday, October 25, 2006 11:45:27 AM (Pacific Standard Time, UTC-08:00)
Hey John,
I like the timestamp idea where it makes the querystring different each time. If you were working on a large project where hundreds of users could edit the same item at once, you could use the datetime stamp (the SQL modified date) to tell if the item was updated during the time you edited and tried to save it.
I've added this to one real-time application and it works well (not the exact code, this is actually a derivative of it). I did put it into a base class but I did not try and stuff it into Current.Items. I can see the pros and cons right off the bat.
I have a few crypto classes I use so you don't have to send me yours but thanks for the offer.
John McGuinness
Friday, October 27, 2006 4:37:13 AM (Pacific Standard Time, UTC-08:00)
I think it depends on what are you storing in there. If you store product ids, and the pages are public, using a timestamp would come against caching. Also, an idea to extend yours would be to rewrite the URLs like this:
yourapp.com/dir/file.aspx?q=yourBase64orMD5orWhatever to
yourapp.com/dir/file/yourBase64orMD5orWhatever.aspx
Adi
Friday, October 27, 2006 5:29:39 AM (Pacific Standard Time, UTC-08:00)
You mention IDisposable, why? The using construct is a means to an end and your class doesn't seem to have that end (i.e., a need to clean up unmanaged or otherwise precious resources). Moreover, your code's implementation of IDisposable doesn't actually do anything. Misleading.
Grant
Friday, October 27, 2006 6:36:06 AM (Pacific Standard Time, UTC-08:00)
Adi, good point on the caching. The URL rewriting is also an option depending upon your needs.
John McGuinness
Friday, October 27, 2006 6:48:33 AM (Pacific Standard Time, UTC-08:00)
Grant, you bring up a good point concerning IDisposable -it isn't doing anything.
I guess what I was trying to achieve was encapsulation. Rather than creating the object and grabbing what I need throught my code (a bit too haphazard for me), I like to put it all in one place. The using statement allows me to do this even though that is not its main purpose. In order to use the using statement, as I'm sure you know, you must implement IDisposable.
Please note, the sample code and the attached project were thrown together in about half an hour from a recent, larger project that I worked on. It's not meant to be a final solution. Rather, it's meant to show how the querystring can be used in a different way than we're all used to.
I implore you to write your own implementation of this and use the objects the way that works best for you and your solution.
John McGuinness
Thursday, April 24, 2008 3:09:02 AM (Pacific Standard Time, UTC-08:00)
The timestamp idea is great but completely fubar's caching requirements. No go.
zxcsf
Friday, June 27, 2008 4:29:59 AM (Pacific Standard Time, UTC-08:00)
Thank you very much
sivaram
Name
E-mail
Home page
Remember Me
Comment (Some html is allowed:
a@href@title, b, blockquote@cite, em, i, strike, strong, sub, super, u
) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.
Enter the code shown (prevents robots):
Live Comment Preview
Site Navigation
About Me
Calendar
Disclaimer
Site Search
Sponsored Links
Calendar
<
November 2008
>
Sun
Mon
Tue
Wed
Thu
Fri
Sat
26
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1
2
3
4
5
6
Monthly Archives
August, 2008 (1)
July, 2008 (1)
March, 2008 (2)
January, 2008 (3)
December, 2007 (3)
October, 2007 (1)
September, 2007 (1)
August, 2007 (5)
July, 2007 (5)
June, 2007 (3)
April, 2007 (5)
February, 2007 (2)
January, 2007 (2)
December, 2006 (9)
November, 2006 (15)
October, 2006 (19)
September, 2006 (3)
August, 2006 (5)
July, 2006 (4)
June, 2006 (6)
May, 2006 (12)
April, 2006 (20)
March, 2006 (11)
February, 2006 (14)
January, 2006 (14)
December, 2005 (23)
November, 2005 (23)
October, 2005 (42)
September, 2005 (4)
Categories
Alert (14)
Community Server (2)
Daily Quote (1)
Did you know (7)
Dumb Searches (1)
Dumbass (15)
General (133)
Haiku (6)
Holiday (5)
Movie quote (8)
Paladin (4)
Paris (1)
Parody (1)
Photo (24)
Political (2)
Priceless (7)
Programming (35)
Pytheus (2)
Rant (23)
Screen capture (15)
SQL (3)
Technical (39)
Video (4)
Vista (8)
Visual Studio 2005 (2)
Wifey (33)
XP (1)
Feeds
RSS
Atom
Good Reading
Code Better
Coding Horror
Computer Zen
Daily WTF
Days Bush Has Left
Dooce
Fargg
Me-Nikk
Post Secret
Rory Blythe
TechCrunch
Sign In