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

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

Для начала небольшое вступление о самой проблеме. Впервые, 5 лет назад об этом заговорил Gregor Kopfhttp://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 TYPEHASH LENGTH“MAGIC” NUMBER / STRINGMAGIC HASHFOUND BY
md2325051447260e015339760548602306096794382326WhiteHat Security, Inc.
md432482912040e266546927425668450445617970135WhiteHat Security, Inc.
md5322406107080e462097431906509019562988736854Michal Spacek
sha140109324351120e07766915004133176347055865026311692244Independently found by Michael A. Cleverly & Michele Spagnuolo & Rogdham
ripemd128323156558540e251331818775808475952406672980WhiteHat Security, Inc.
ripemd160402058300203400e1839085851394356611454660337505469745Michael A Cleverly
tiger128,3322650226400e908730200858058999593322639865WhiteHat Security, Inc.
tiger160,3401318162357000e4706040169225543861400227305532507173Michele Spagnuolo
tiger192,348
tiger128,43247976300000e05651056780370631793326323796WhiteHat Security, Inc.
tiger160,440622419555740e69173478833895223726165786906905141502Michele Spagnuolo
adler328FR00e00099WhiteHat Security, Inc.
crc32823320e684322WhiteHat Security, Inc.
crc32b865860e817678WhiteHat Security, Inc.
fnv132821860e591528WhiteHat Security, Inc.
fnv1641683380000e73845709713699WhiteHat Security, Inc.
joaat884090e074025WhiteHat Security, Inc.
haval128,33280979363000e38549671092424173928143648452WhiteHat Security, Inc.
haval160,340181599831630e01697014920826425936632356870426876167Independently found by Michael Cleverly & Michele Spagnuolo
haval192,348488920569470e4868841162506296635201967091461310754872302741Michael A. Cleverly
haval128,432714375790e316321729023182394301371028665WhiteHat Security, Inc.
haval160,440123688787940e34042599806027333661050958199580964722Michele Spagnuolo
haval128,5321155282870e495317064156922585933029613272WhiteHat Security, Inc.
haval160,5403390268823100e2521569708250889666329543741175098562Michele Spagnuolo
haval192,548528886405560e9108479697641294204710754930487725109982883677Michele 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