User Saved Safe URL without security risks
Often there is a need to have a url like:
https://myhost/AccountStatement.aspx?accountId=0392342&Date=20111109
Unfortunately this opens a security risk. The https protects the page contents but not the URL. The URL is sent as open text. The above URL tempts hackers that may also be users of the system to try enumerating various accountId in the hope that someone has forgotten to check the access permission against the user for each account (A naive assumption is that you don’t know the account number unless you are the owner…).
One solution is to put put the data in a post back and thus take it off the URL line – unfortunately it is not friendly because it prevents a user from saving the URL for quick reference. You can have both friendly and secure by encrypting the arguments.
If you use a Membership Provider you have a key that you can use for encrypting, using the user name or host name results in a second key so you can now well encrypt the URL Parameters. I usually just concatenated the URL parameters with tabs between each ordered part to make decomposition using a split easy, i.e. 0392342\t20111109 is what I would encrypt and assign to ?key=
The first part is a SALT for which I use the MembershipUser ProviderID, typically a GUID.
- private static byte[] UserSpecificKey
- {
- get
- {
- var user = Membership.GetUser();
- if (user == null)
- {
- return Encoding.ASCII.GetBytes("GUEST");
- }
- return Encoding.ASCII.GetBytes(user.ProviderUserKey.ToString());
- }
- }
Then I use a standard built in two way encrypt/decrypt and use some other bit of information, for example, the host name (which means that the URL is host specific).
- public static string EncryptStringAES(string plainText)
- {
- if (string.IsNullOrEmpty(plainText))
- throw new ArgumentNullException("plainText");
- var hostName = HttpContext.Current.Request.Url.DnsSafeHost;
- string outStr = null;
- RijndaelManaged aesAlg = null;
- try
- {
- var key = new Rfc2898DeriveBytes(hostName, UserSpecificKey);
- aesAlg = new RijndaelManaged();
- aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
- aesAlg.IV = key.GetBytes(aesAlg.BlockSize / 8);
- var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
- using (var msEncrypt = new MemoryStream())
- {
- using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
- {
- using (var swEncrypt = new StreamWriter(csEncrypt))
- {
- swEncrypt.Write(plainText);
- }
- }
- outStr = Convert.ToBase64String(msEncrypt.ToArray());
- }
- }
- finally
- {
- if (aesAlg != null)
- aesAlg.Clear();
- }
- return outStr;
- }
and
- public static string DecryptStringAES(string cipherText)
- {
- if (string.IsNullOrEmpty(cipherText))
- throw new ArgumentNullException("cipherText");
- var hostName = HttpContext.Current.Request.Url.DnsSafeHost;
- RijndaelManaged aesAlg = null;
- string plaintext = null;
- try
- {
- var key = new Rfc2898DeriveBytes(hostName, UserSpecificKey);
- aesAlg = new RijndaelManaged();
- aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
- aesAlg.IV = key.GetBytes(aesAlg.BlockSize / 8);
- var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
- byte[] bytes = Convert.FromBase64String(cipherText);
- using (var msDecrypt = new MemoryStream(bytes))
- {
- using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
- {
- using (var srDecrypt = new StreamReader(csDecrypt))
- plaintext = srDecrypt.ReadToEnd();
- }
- }
- }
- finally
- {
- if (aesAlg != null)
- aesAlg.Clear();
- }
- return plaintext;
- }
The user can now save the URLs, they will be directed to the login page (if not logged in) and then redirected to the page they saved. There is no ability for anyone to go fishing. If someone on the same computer finds the saved link, they will be asked for a login (they may be a valid user of the system) but the page will NOT grant them access to the data since it will fail to decode the parameters.
Someone searching the browser history will not find any information that is exploitable. A room mate with your wallet can’t call up and say “Hi, my name is Jack Smith, Account No 0392342 and I want to do a wire transfer, my SSN is…. “ The account number is not available in the browser history.
Comments
Post a Comment