48

In PHP a magic hash attack happens when loose type comparisons cause completely different values to be evaluated as equal, causing a password "match" without actually knowing the password. Here is an example:

<?php
if (hash('md5','240610708',false) == '0') {
  print "'0' matched " . hash('md5','240610708',false);
}

If you execute this you will see:

'0' matched 0e462097431906509019562988736854

Would this be possible in a JavaScript application that's using using the loose equality == operator to compare two hashes?

JavaScript is a weakly typed language, so I would naturally assume the type coercion can be taken advantage of and therefore present various security holes.

  • 6
    Yet another reason to use prebuilt, battle-tested libraries for this kind of thing. – corsiKa Sep 7 '20 at 20:32
  • 13
    Could you please include an outline of “this attack” directly into your question to prevent the question from becoming meaningless if the link changes or the page vanishes? – Konrad Rudolph Sep 8 '20 at 9:01
  • 25
    For those wondering, this "works", because the output of this specific hash is a string which looks like a number in scientific notation with a mantissa of 0, so if evaluated as a number, its value is 0 * 10^462097431906509019562988736854 which is of course 0. The issue here is that php will take two strings and convert them to numbers for comparison, so '0' == '0e462097431906509019562988736854' because 0 == 0e462097431906509019562988736854. – jcaron Sep 9 '20 at 8:56
  • 6
    Of all programming languages in the world, two specimens with insane implicit type conversion rules have managed to become (a) the most used web server language (PHP) and (b) the most used web client language (JavaScript). Given the importance of the web to our society nowadays, that fact never ceases to amaze me. – Heinzi Oct 8 '20 at 8:37
  • 1
    Why would anyone compare a password hash against 0 to start with ? – Julien Oct 8 '20 at 15:03
62

Being loosely typed with a crazy == operator, JavaScript is vulnerable to type juggling. But it is not as vulnerable as PHP.

Here are a few things that are equal in PHP, but not in JavaScript:

  • '0e111' == '0e222' Even though both are strings, PHP will treat them as numbers. JavaScript needs one of the operands to be a number before it tries to coerse anything into a number.
  • '0eaaa' == 0 PHP will interpret anything beginning with a number as a number, while JavaScript will not. Note that even PHP needs the other operand to be a number in this case.

However, this will be equal in both PHP and Javascript:

  • '0e111' == 0 One operand is a string containing only digits after the 0e (very unlikely that will happend at random), and the other must be an actual number (not just a string looking like a number).

This makes it harder to find type juggling vulnerabilities with hashes in JavaScript. That doesn't mean they don't exist, though. Use ===.

  • 6
    I think in your second example you meant '0xaaa'. – Edheldil Sep 7 '20 at 18:46
  • 4
    @Edheldil I mean e not x. JS never interprets x as a number. PHP does interpret the string 0xaaa as a number. Note that a hex encoded hash will never contain x. I may be missing something here, though. – Anders Sep 8 '20 at 13:34
  • 3
    @Edheldil All these are strings which can be the hex output of a hash (only contain 0-9a-f) and look like scientific notation numbers (xey which means x * 10^y) and where the mantissa (the part before the e) is 0 (so that 0ewhatever = 0 * 10^whatever = 0). So they all start by 0e followed by (decimal) digits only. The question then is whether a given language will automatically convert a string contain such a sequence to a number, which has then value 0. – jcaron Sep 9 '20 at 8:48
  • 2
    '0whatever' == 0 due to the string being turned into an int. – Cees Timmerman Sep 9 '20 at 10:01
  • 1
    @CeesTimmerman is that in PHP? It evaluates to false if I try it in JS. If I try Number('0whatever') I get NaN, not 0, which explains the equality being false. – Ryan1729 Sep 9 '20 at 20:45
22

Yes, sure. Magic hash attacks are also possible in JavaScript.

The JavaScript operator == means equal after type juggling.

0e123 is a valid representation of a number (in scientific notation)

If clients could control the type and value of the hash passed to the server, passing the number 0 would force JavaScript to cast magic hash to the number, resulting in number comparison:

'0e462097431906509019562988736854' == 0 -> true

The behaviour of the == operator can be seen in this equality table.

  • 10
    One of the many reasons why it is a very useful habit for JS developers to always type === (strict equality) by default, and only use == when you know you are going to need to compare values with different type representations. But it is usually a good idea to architect your software in a way which avoids comparisons of different types. The rules of JS equality are so arcane that nobody can be expected to memorize them. It's usually better to do explicit conversions so you can use ===. – Philipp Sep 7 '20 at 10:39
  • 3
    @Kaddath == is exactly as fast as === when both operands have the same type, and there's nothing wrong with using it when you know that both operands have the same type. You only got a problem when you don't know what types the operands have. – Bergi Sep 8 '20 at 13:48
  • 2
    @Bergi I find it safer for code maintainability/reuse, you may know that they have the same type, but the ones that follow you may not know/ forget it.. I may be wrong though – Kaddath Sep 8 '20 at 14:01
  • 2
    @Bergi If both are equally as fast, there is no reason to use anything other than the safest one. – Ian Kemp Sep 9 '20 at 16:12
  • 1
    @Sulthan Totally agree. The only point I was trying to make is that "because === is also faser" is not a valid argument. – Bergi Sep 11 '20 at 21:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.