Как не надо сравнивать в PHP

Всем доброго здоровья. Сегодня мы поговорим с вами о такой проблеме, как “Некорректное использование операторов сравнения и динамическая типизация данных в PHP”. А точнее о том, как можно это использовать для обхода авторизации в веб приложениях. Те, кто крутится в теме ИБ, про это уже неоднократно слышали, возможно, не под таким страшным названием, а под названием PHP Magic Hash. Это не какое-то открытие Америки, а дабы собрать все мысли по этому вопросу в одном месте.
Let’s go my little friends!

Для начала небольшое вступление о самой проблеме. Впервые, 5 лет назад об этом заговорил Gregor Kopf http://blog.nibblesec.org/2010/12/typo3-sa-2010-020-typo3-sa-2010-022.html.


Довольно часто, начинающие и не очень программисты используют оператор нестрогого сравнения ==. Это очень удобный инструмент, когда вам не хочется запариваться о поступаемых данных. Ведь при проверке на соответствие таким способом, PHP сначала преобразовывает типы данных к одному виду и только потом проводит операцию их сравнения.

Здесь сравниваются две строки string, поэтому результат отрицательный, но:

Результат сравнения истина, т.к. строка “а” была приведена интерпретатором к числовому типу int:

Если сравниваемые строки состоят только из чисел, то они будет приведены к числовому типу int, а затем проверены на равенство:

Не скучайте, мы уже вплотную подходим к нашей проблеме:

Ага! Тут строка “2e2” интерпретируется, как число с плавующей точкой float и означает 2*(10^2). Вот выдержка из руководства по PHP:

[themify_quote]Если строка не содержит какой-либо из символов ‘.’, ‘e’, или ‘E’, и значение числа помещается в пределы целых чисел (определенных PHP_INT_MAX), строка будет распознана как целое число (integer). Во всех остальных случаях она считается числом с плавающей точкой (float).[/themify_quote]

А теперь самое интересное — как все это можно применять.

Сейчас нам не нужно знать, что из себя представляет хэш-функция, об этом вы можете прочитать, например, в вики — https://ru.wikipedia.org/wiki/%D0%A5%D0%B5%D1%88%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5. Сейчас нас интересует, что результат работы хэш функции — это некая строка символов, которую чаще всего представляют в 16-ричном виде (HEX):

Хеш содержит символы из диапазона [a-f0-9], а мы знаем, что если в строке будет содержаться символ “e” и она будет состоять только из чисел (не считая “e” разумеется), то она будет приведена PHP к числовому типу. Отсюда и вытекает исследуемая проблема:

Так как результатом умножения любого числа на “0” всегда будет “0”, то любые два хэша будут равны между собой если:

1) они начинаются с произвольного количества “0”
2) за нолями следует символ “0”
3) после символа “e” следуют только числа.

Говоря языком регекспов — ^[0]+e[0-9]+$

Теперь небольшая проекция на реальную ситуацию из жизни.

Есть сайт, в котором авторизация пользователей реализована таким образом:

Теперь если у пользователя Васи пароль от аккаунта “240610708”. То, за Васю можно войти и с помощью пароля “aabg7XSs”, и с “QNKCDZO”, и даже с “NOOPCJF”.

Причем данная проблема не зависит от типа используемой функции. Буде это даже sha256 там тоже присутствуют хэши, которые удовлетворяют обозначеным выше условиям. Но вот загвоздка, натолкнуться на такой хэш довольно проблематично. Другое дело если мы его сгенерируем сами.

Система сброса с поста админа пароля.

Рассмотрим такое явление, как восстановление забытого пароля. Довольно часто оно реализовано таким способом:
1) Человек нажимает кнопку сброса пароля http://some-useless-forum.ru/recovery.php?action=recstart
2) Вводит свое имя пользователя/e-mail
3) Получает на почту ссылку для восстановления пароля, вида http://some-useless-forum.ru/recovery.php?action=newpass&id=666&hash=E9C2F4EA8015A084CE9CF83A3C65B51D
4) Переходит по ней
5) Система разрешает установить пользователю новый пароль
6) Sex & Fun

Теперь посмотрим, что будет, если этот процесс реализован примерно таким кодом:

При таком подходе, где использется нестрогого сравнение хэшей, мы можем сбросить пароль любого пользователя, зная только адрес его электронной почты.
Для этого мы можем написать скрипт, который будет:
1) Отправлять форму восстановления пароля с нужным на email’ом.
2) Переходить по ссылке для сброса, используя в качестве кода 0.
3) Проверять: появилась ли форма для ввода нового пароля. Если нет, то переходить к 1 пункту. И так по кругу, пока не получим форму для ввода нового пароля.

Это работает таким образом: раз за разом генерируется новый хэш для сброса пароля и когда этот хэш совпадает с условиями, которые я описал выше, то достаточно сравнить его с “0” и результат сравнения будет истиной. Конечно, чем длиннее хэш, тем результат получения нужного стремится к нулю.
Такая уязвимость была обнаружена в одной из старых версий Simple Machines Forum, об этом еще писал Raz0r в своем блоге http://raz0r.name/vulnerabilities/simple-machines-forum/.

Заключение.

Вот и все мысли по данному вопросу. В блоге WhiteHat Security есть очень хорошая статья на английском языке по данному вопросу — https://blog.whitehatsec.com/magic-hashes/
Я лишь приложу сюда таблицу с паролями, которые равны 0.

HASH TYPE HASH LENGTH “MAGIC” NUMBER / STRING MAGIC HASH FOUND BY
md2 32 505144726 0e015339760548602306096794382326 WhiteHat Security, Inc.
md4 32 48291204 0e266546927425668450445617970135 WhiteHat Security, Inc.
md5 32 240610708 0e462097431906509019562988736854 Michal Spacek
sha1 40 10932435112 0e07766915004133176347055865026311692244 Independently found by Michael A. Cleverly & Michele Spagnuolo & Rogdham
ripemd128 32 315655854 0e251331818775808475952406672980 WhiteHat Security, Inc.
ripemd160 40 20583002034 00e1839085851394356611454660337505469745 Michael A Cleverly
tiger128,3 32 265022640 0e908730200858058999593322639865 WhiteHat Security, Inc.
tiger160,3 40 13181623570 00e4706040169225543861400227305532507173 Michele Spagnuolo
tiger192,3 48
tiger128,4 32 479763000 00e05651056780370631793326323796 WhiteHat Security, Inc.
tiger160,4 40 62241955574 0e69173478833895223726165786906905141502 Michele Spagnuolo
adler32 8 FR 00e00099 WhiteHat Security, Inc.
crc32 8 2332 0e684322 WhiteHat Security, Inc.
crc32b 8 6586 0e817678 WhiteHat Security, Inc.
fnv132 8 2186 0e591528 WhiteHat Security, Inc.
fnv164 16 8338000 0e73845709713699 WhiteHat Security, Inc.
joaat 8 8409 0e074025 WhiteHat Security, Inc.
haval128,3 32 809793630 00e38549671092424173928143648452 WhiteHat Security, Inc.
haval160,3 40 18159983163 0e01697014920826425936632356870426876167 Independently found by Michael Cleverly & Michele Spagnuolo
haval192,3 48 48892056947 0e4868841162506296635201967091461310754872302741 Michael A. Cleverly
haval128,4 32 71437579 0e316321729023182394301371028665 WhiteHat Security, Inc.
haval160,4 40 12368878794 0e34042599806027333661050958199580964722 Michele Spagnuolo
haval128,5 32 115528287 0e495317064156922585933029613272 WhiteHat Security, Inc.
haval160,5 40 33902688231 00e2521569708250889666329543741175098562 Michele Spagnuolo
haval192,5 48 52888640556 0e9108479697641294204710754930487725109982883677 Michele Spagnuolo

Так же для тех кто пишет код, советую внимательно прочитать мануалы на сайте PHP. В двух словах — приучайте себя использовать тождественное равенство ===.

http://php.net/manual/ru/language.operators.comparison.php
http://php.net/manual/ru/types.comparisons.php
http://php.net/manual/ru/language.types.string.php
http://php.net/manual/ru/language.types.float.php
http://php.net/manual/ru/language.types.integer.php

Еще немного информации по теме:

http://turbochaos.blogspot.com.au/2013/08/exploiting-exotic-bugs-php-type-juggling.html