Redemption Steps

When youre thinking about threats to Magic URLs and hidden forms and possible countermeasures, always consider the following threats:

  • An attacker views the data

  • An attacker replays the data

  • An attacker predicts the data

  • An attacker changes the data

Lets look at each threat and possible redemptions.

Attacker Views the Data

This is only a threat if the data is confidential, such as a password, or an identifier allowing the user into the system. Any Personally Identifiable Information (PII) is also of concern. A simple remedy is to use Secure Sockets Layer (SSL), Transport Layer Security (TLS), Internet Protocol Security (IPSec), or some other encryption technology to protect the sensitive data. For example, you could encrypt the data at the server, and then send it to the client in a hidden form or a cookie, and the client automatically sends the data back to the server. Because the key is held at the server and the encrypted blob is opaque , this is a relatively good mechanism from a pure crypto perspective.

Attacker Replays the Data

You may decide to encrypt or hash some sensitive identity data using your own code at the server, which may seem safe. But imagine if the encrypted or hashed data could be replayed by the attacker. For example, the following C# code hashes a username and password and uses the result as a key in a HTTP field to identify the user:

 SHA1Managed s = new SHA1Managed(); byte [] h = s.ComputeHash(UTF8Encoding.UTF8.GetBytes(uid + ":" + pwd)); h = s.ComputeHash(h); string b64 = Convert.ToBase64String(h); // base64 result 

Or, similar code in JavaScript (from HTML or ASP) calls CAPICOM on Windows:

 // Hex hash result var oHash = new ActiveXObject("CAPICOM.HashedData"); oHash.Algorithm = 0; oHash.Hash("mikey" + ":" + "ABCDE"); oHash.Hash(oHash.Value); var b64 = oHash.Value; // Hex result 

Or, similar code in Perl also hashes the users name and password:

 use Digest::SHA1 qw(sha1 sha1_base64); my $s = $uid . ":" . $pwd; my $b64 = sha1_base64(sha1($s)); # base64 result 

Note that all these examples hash the hash of the concatenated string to mitigate a vulnerability called length extension attacks. An explanation of the vulnerability is outside the scope of this book, but for all practical uses, dont just hash the concatenated data, do one of the following:

 Result = H(data1, H(data2)) 

or

 Result = H(H(data1 CONCAT data2)) 

This is a version that is cryptographically sound:

 static string IteratedHashAppendSalt(string uid, string pwd, UInt32 iter) {  // restrict iteration count for input safety  const UInt32 MIN_ITERATIONS = 1024;  const UInt32 MAX_ITERATIONS = 32768;    if (iter < MIN_ITERATIONS) iter = MIN_ITERATIONS;  if (iter > MAX_ITERATIONS) iter = MAX_ITERATIONS;    // get 24-byte salt  const UInt32 SALT_BYTE_COUNT = 24;  byte[] salt = new byte[SALT_BYTE_COUNT];  new RNGCryptoServiceProvider().GetBytes(salt);    // encode the uid and pwd  byte[] uidBytes = UTF8Encoding.UTF8.GetBytes(uid);  byte[] pwdBytes = UTF8Encoding.UTF8.GetBytes(pwd);  UInt32 uidLen = (UInt32)uidBytes.Length;  UInt32 pwdLen = (UInt32)pwdBytes.Length;  // copy the uid, pwd and salt to a byte buffer  byte[] input = new byte[SALT_BYTE_COUNT + uidLen + pwdLen];  Array.Copy(uidBytes, 0, input, 0, uidLen);  Array.Copy(pwdBytes, 0, input, uidLen, pwdLen);  Array.Copy(salt, 0, input, uidLen + pwdLen, SALT_BYTE_COUNT);    // hash the uid, pwd & salt  // H(uid  pwd  salt)  HashAlgorithm sha = HashAlgorithm.Create("SHA256");  byte[] hash = sha.ComputeHash(input);    // hash the hash with original hash, salt and iteration count, N-times  // R0 = H(uid  pwd  salt)  // Rn = H(Rn-1  R0  salt  i) ... N  const UInt32 UINT32_BYTE_COUNT = 32/8;  byte[] buff = new byte[hash.Length +   hash.Length +   SALT_BYTE_COUNT +   UINT32_BYTE_COUNT];  Array.Copy(salt, 0, buff, hash.Length + hash.Length, SALT_BYTE_COUNT);  Array.Copy(hash, 0, buff, hash.Length, hash.Length);  for (UInt32 i = 0; i < iter; i++) {  Array.Copy(hash, 0, buff, 0, hash.Length);  Array.Copy(BitConverter.GetBytes(i), 0, buff,   hash.Length + hash.Length + SALT_BYTE_COUNT,  UINT32_BYTE_COUNT);  hash = sha.ComputeHash(buff);  }  // build string base64(hash) : base64(salt)  string result = Convert.ToBase64String(hash) +  ":" +  Convert.ToBase64String(salt);  return result; } 

But even this version of the code is vulnerable to attack! So whats the web vulnerability? Imagine a username and password hashes down to xE/f1/XKonG+/ XFyq+Pg4FXjo7g= and you tack that onto the URL as a verifier once the username and password have been verified . All an attacker need do is view the hash and replay it. The attacker doesnt need to view the password! All that fancy-schmancy crypto bought you nothing! You can fix this with channel encryption technology like SSL, TLS, and IPSec.

Attacker Predicts the Data

In this scenario, a user connects with a username and password over SSL/TLS, and then your server code verifies the account information and generates an auto-incrementing value to represent that user. Every interaction by that user uses the value to identify them without requiring the server to go through the authentication steps. This can be attacked easily over SSL/TLS. Heres how: A valid but malicious user connects to the server and provides his valid credentials. He gets an identifier value, 7625, back from the server. He then closes the browser and tries again with the same valid username and password. This time he gets the value 7627 back. It looks like this is an incrementing value, and someone else possibly logged on between the first users two logons . Now all the attacker need do to hijack the other users session is connect (over SSL/TLS!) setting the connection identifier to 7626. Encryption technologies dont help protect against predictability like this. You could set the connection identifier using cryptographically random numbers , using code like this JavaScript and CAPICOM:

 var oRNG = new ActiveXObject("CAPICOM.Utilities"); var rng = oRNG.GetRandom(32,0); 
Note 

CAPICOM calls into the CryptGenRandom function on Windows.

Or PHP on Linux or Unix ( assuming the operating system supports /dev/random or /dev/urandom):

 // using @ before fopen to prevent fopen from dumping too much info to the user $hrng = @fopen("/dev/random","r"); if ($hrng) {  $rng = base64_encode(fread($hrng,32));  fclose($hrng); } 

Or in Java:

 try {  SecureRandom rng = SecureRandom.getInstance("SHA1PRNG");  byte b[] = new byte[32];  rng.nextBytes(b); } catch(NoSuchAlgorithmException e) {  // Handle exception } 
Note 

The default implementation of Javas SecureRandom has a very small entropy pool. It may be fine to use for session management and identity in a web application, but is probably not good enough for long-lived keys.

All this being said, there is still one potential problem with using unpredictable random numbers: if the attacker can view the data, the attacker can simply view the random value and then replay it! At this point, you may want to consider using channel encryption, such as SSL/TLS. Again, it depends on the threats that concern you.

Attacker Changes the Data

Finally, lets assume youre not really worried about an attacker viewing the data, but are worried about an attacker changing valid data. This is the hidden form field with the price embedded problem. You really ought not to do this, but, if for some strange reason you absolutely must, you can place a message authentication code (MAC) as a form field entry; and if the MAC returned from the browser fails to match the MAC you sent, or the MAC is missing, then you know the data has been changed. Think of a MAC as a hash that includes a secret key as well as data you would normally hash. The most commonly used MAC is the keyed-hash message authentication code (HMAC). So for a form, you would concatenate all the hidden text in the form (or any fields you want to protect), and hash this data with a key held at the server. In C#, the code could look like this:

 HMACSHA1 hmac = new HMACSHA1(key); byte[] data = UTF8Encoding.UTF8.GetBytes(formdata); string result = Convert.ToBase64String(hmac.ComputeHash(data)); 

Or in Perl:

 use strict; use Digest::HMAC_SHA1; my $hmac = Digest::HMAC_SHA1->new($key); $hmac->add($formdata); my $result = $hmac->b64digest; 

PHP does not have an HMAC function, but PHP Extension and Application Repository (PEAR) does. (See the Other Resources section for a link to the code.)

The result of the MAC could then be added by the server to the hidden form, viz:

 <INPUT TYPE = HIDDEN NAME = "HMAC" VALUE = "X8lbKBNG9cVVeF9+9rtB7ewRMbs"> 

When your server code receives the hidden HMAC form field, the server code can verify the form entries have not been tampered with by the repeating the concatenation and hash steps.

Dont use a hash for this work. Use a MAC because a hash can be recomputed by the attacker; a MAC cannot unless the attacker has the secret key stored at the server.



19 Deadly Sins of Software Security. Programming Flaws and How to Fix Them
Writing Secure Code
ISBN: 71626751
EAN: 2147483647
Year: 2003
Pages: 239

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net