|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionQuery strings are a very convenient way of passing information to a new window or to a following page. The problem with query strings is that the information is visible. In my experience, the wiseass users see the query strings in the browser and try to duplicate the functionality: like "Oh, when I click the button, I get 'DeleteInventory.aspx?item=123&user=george&id=99' so, if I want to delete the item #222, I just need to use 'DeleteInventory.aspx?item=222&user=george&id=99' right?" Wrong, the id=99 is the database ID for the item to be removed. The rest of the information is for populating the label controls on the page. To avoid this problem, either use Solving the visible query string problemScrambling the query string allows you to pass information securely. The information displayed after the page URL is simply a jumble of characters. The query string information is scrambled prior to invoking the called page and is descrambled by the newly opened page. The scrambling and descrambling mechanism hides the information from the users while allowing you to continue using the convenient query string mechanism. The keys used to scramble and descramble the query strings are session-specific, allowing all the windows in a session to share information. Why keep keys in session?Many applications use a named window to perform a specific function. A user might have multiple browser windows referencing the same session (usually by using CTRL-N (Internet Explorer) to open new browser windows). All the windows in the session should scramble query strings the same way (using the same keys and initialization vectors) to allow the destination window to correctly decode the query string. Scrambling and descramblingThe approach described here uses RC2 encryption to scramble (encrypt) and descramble (decrypt) the query string. Other algorithms such as ROT13 (simple replacement) DES and AES could be used as well. RC2 was chosen because it is relatively fast (efficiently implemented) and is well documented. Note that the purpose here is just to scramble (and later descramble) the query string. If you need to protect your information from malicious hackers, you should:
Tamper-proofing your query stringsA secondary issue here is making sure the user cannot randomly modify the scrambled query string in a way that results in a valid, different, query string. Using a block cipher like RC2 solves this problem as the chance of a modified scrambled string passing decryption is very low. An Alternative approach is to embed a checksum (such as a CRC) in the string, when decoding the scrambled string verify the checksum to make sure the string was not modified. Deriving from System.Web.PageA simple approach to extend the web page functionality is to extend the Handling keys and initialization vectorsThe // Key management for scrambling support
public byte[] ScrambleKey
{
set
{
byte[] key = value;
if (null == key)
{
// Use existing key if non provided
key = ScrambleKey;
}
Session["ScrambleKey"] = key;
}
get
{
byte[] key = (byte[])Session["ScrambleKey"];
if (null == key)
{
RC2CryptoServiceProvider rc2 = new RC2CryptoServiceProvider();
rc2.GenerateKey();
key = rc2.Key;
Session["ScrambleKey"] = key;
}
return key;
}
}
// Initialization vector management for scrambling support
public byte[] ScrambleIV
{
set
{
byte[] key = value;
if (null == key)
{
key = ScrambleIV;
}
Session["ScrambleIV"] = key;
}
get
{
byte[] key = (byte[])Session["ScrambleIV"];
if (null == key)
{
RC2CryptoServiceProvider rc2 = new RC2CryptoServiceProvider();
rc2.GenerateIV();
key = rc2.IV;
Session["ScrambleIV"] = key;
}
return key;
}
}
As you can see above, the key and the initialization vector are generated on first access and stored in the session. I would not recommend this as a method for secure communication, but it is an acceptable solution for simple scrambling. Scrambling and descrambling the query stringThe /// <summary>
/// Scramble a message using a session-specific key
/// </summary>
public string Scramble(string message)
{
UTF8Encoding textConverter = new UTF8Encoding();
RC2CryptoServiceProvider rc2CSP = new RC2CryptoServiceProvider();
//Convert the data to a byte array.
byte[] toEncrypt = textConverter.GetBytes(message);
//Get an encryptor.
ICryptoTransform encryptor =
rc2CSP.CreateEncryptor(ScrambleKey, ScrambleIV);
//Encrypt the data.
MemoryStream msEncrypt = new MemoryStream();
CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor,
CryptoStreamMode.Write);
//Write all data to the crypto stream and flush it.
// Encode length as first 4 bytes
byte[] length = new byte[4];
length[0] = (byte)(message.Length & 0xFF);
length[1] = (byte)((message.Length >> 8) & 0xFF);
length[2] = (byte)((message.Length >> 16) & 0xFF);
length[3] = (byte)((message.Length >> 24) & 0xFF);
csEncrypt.Write(length, 0, 4);
csEncrypt.Write(toEncrypt, 0, toEncrypt.Length);
csEncrypt.FlushFinalBlock();
//Get encrypted array of bytes.
byte[] encrypted = msEncrypt.ToArray();
// Convert to Base64 string
string b64 = Convert.ToBase64String(encrypted);
// Protect against URLEncode/Decode problem
string b64mod = b64.Replace('+', '@');
// Return a URL encoded string
return HttpUtility.UrlEncode(b64mod);
The /// <summary>
/// Descramble a message in a session-specific key
/// </summary>
public string Descramble(string scrambledMessage)
{
UTF8Encoding textConverter = new UTF8Encoding();
RC2CryptoServiceProvider rc2CSP = new RC2CryptoServiceProvider();
// URL decode , replace and convert from Base64
string b64mod = HttpUtility.UrlDecode(scrambledMessage);
// Replace '@' back to '+' (avoid URLDecode problem)
string b64 = b64mod.Replace('@', '+');
// Base64 decode
byte[] encrypted = Convert.FromBase64String(b64);
//Get a decryptor that uses the same key and IV as the encryptor.
ICryptoTransform decryptor =
rc2CSP.CreateDecryptor(ScrambleKey, ScrambleIV);
//Now decrypt the previously encrypted message using the decryptor
// obtained in the above step.
MemoryStream msDecrypt = new MemoryStream(encrypted);
CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor,
CryptoStreamMode.Read);
byte[] fromEncrypt = new byte[encrypted.Length-4];
//Read the data out of the crypto stream.
byte[] length = new byte[4];
csDecrypt.Read(length, 0, 4);
csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);
int len = (int)length[0] | (length[1] << 8)
| (length[2] << 16) | (length[3] << 24);
//Convert the byte array back into a string.
return textConverter.GetString(fromEncrypt).Substring(0, len);
}
The helper method, /// <summary>
/// Descramble the query string and return a
/// NameValueCollection containing key and value
/// pairs corresponding to the original query string.
/// </summary>
public NameValueCollection DescrambleQueryString(string scrambledMessage)
{
// Decode the query string
string queryString = Descramble(scrambledMessage);
NameValueCollection result = new NameValueCollection();
char[] splitChar = new char[] {'&'};
char[] equalChar = new char[] {'='};
// Split query string to components
foreach (string s in queryString.Split(splitChar))
{
// split each component to key and value
string[] keyVal = s.Split(equalChar, 2);
string key = keyVal[0];
string val = String.Empty;
if (keyVal.Length > 1) val = keyVal[1];
// Add to the hashtable
result.Add(key, val);
}
// return the resulting hashtable
return result;
}
Example: scrambling and descrambling a query stringTo prepare and scramble a query string: string qs = "masterFlag=Y&chartDepth=0&masterAcctID=" + masterAcctID
+ "&clientName=" +pagerEntry.ClientName
+ "&vendorName=" + pagerEntry.VendorName
+"&accountID=0&masterAccount=" + pagerEntry.MasterAccountNumber;
string scrambled_qs = PLSession.Scramble(qs);
objSubDataRow["hypDeleteAccount"] =
"javascript:openwincust('/ AccountDelete.aspx?query=" + scrambled_qs + "');";
To descramble a query string: using System.Collections.Specialized;
// Read de-scrambled query string parameters
NameValueCollection queryString =
DescrambleQueryString(Request.QueryString["query"]);
lblClientName.Text = queryString["clientName"];
lblAccountID.Text = queryString["accountID"];
lblMasterAccount.Text = queryString["masterAccount"];
lblVendorName.Text = queryString["vendorName"];
m_chartDepth = Convert.ToInt32(queryString["chartDepth"]);
m_masterFlag = queryString["masterFlag"];
m_masterAccountID = Convert.ToInt32(queryString["masterAcctID"]);
The URLDecode() problemThe Author notesIf you can, always hide the information passed between pages. Use Further workThe scrambling algorithm can be broken into a separate class and an interface can be defined allowing multiple algorithms to be used. I believe such an action would be overkill. All that the code is trying to do is to prevent a nosy user from modifying the query strings. History
| ||||||||||||||||||||