Type Juggling - Обходит ли реально авторизацию?

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0

Я полный 0e1823081...​

### var_dump в PHP​

var_dump в PHP — это функция, которая выводит информацию о переменной, включая её тип и значение. Она особенно полезна для отладки кода, так как позволяет увидеть не только значение переменной, но и её тип, что помогает выявлять ошибки и понимать, что именно происходит в коде.
Код: Скопировать в буфер обмена
Код:
$a = 10;
$b = "Hello, World!";
$c = [1, 2, 3];
$d = null;

var_dump($a);
var_dump($b);
var_dump($c);
var_dump($d);

------------------------------------
int(10)
string(13) "Hello, World!"
array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}
NULL

### Преобразование строк в числа в PHP​

В PHP строки могут быть автоматически преобразованы в числа в определенных контекстах (например, во время сравнений).
Преобразование строк в целые числа (integer)
Когда строка содержит числовое значение и преобразуется в целое число, PHP просто читает эту строку как число. Если строка начинается с цифр, PHP попытается извлечь из нее числовое значение.
Код: Скопировать в буфер обмена
Код:
$int1 = (int)"123";      // 123
$int2 = (int)"456abc";   // 456
$int3 = (int)"0";        // 0
$int4 = (int)"-789";     // -789
$int5 = (int)"abc123";   // 0 (нельзя преобразовать, точнее можно и это 0...?)
Eсли строка начинается с числового значения, оно будет успешно преобразовано в целое число. Если строка начинается с букв, результат будет 0.
Преобразование строк в числа с плавающей запятой (float)
Строки, содержащие числа с плавающей запятой, преобразуются аналогичным образом. Если строка начинается с числового значения, включая точку, PHP преобразует ее в число с плавающей запятой.
Код: Скопировать в буфер обмена
Код:
$float1 = (float)"123.45";    // 123.45
$float2 = (float)"-678.90";   // -678.9
$float3 = (float)"0.123";     // 0.123
$float4 = (float)"abc456.78"; // 0.0
$float5 = (float)"789.01abc"; // 789.01
Автоматическое преобразование в числовом контексте
В PHP строки могут автоматически преобразовываться в числа в контексте арифметических операций или при использовании с операторами сравнения.
Код: Скопировать в буфер обмена
Код:
$sum = "10" + 20;      // 30 (строка "10" преобразуется в число 10)
$kolvo = "10e2" + 1;    // 1001 (строка преобразуется в 10e2, что эквивалентно 1000)
$product = "3.14" * 2; // 6.28 (строка "3.14" преобразуется в число 3.14)

if ("100" == 100) {
    echo "Equal";      // Выведет "Equal" (строка "100" преобразуется в число 100)
}
Cтроки автоматически преобразуются в числа при выполнении арифметических операций или при сравнении.

### Сравнение​

PHP предоставляет два основных типа операторов сравнения: нестрогое сравнение (loose comparison) и строгое сравнение (strict comparison). Понимание их различий важно для правильного использования в коде.
Нестрогое сравнение (loose comparison)
Нестрогое сравнение использует оператор == и приводит операнды к одному типу данных перед сравнением. Это может быть удобно, но иногда приводит к неожиданным результатам.
Код: Скопировать в буфер обмена
Код:
var_dump(0 == "0"); // true
var_dump(1 == "1"); // true
var_dump(123 == "123"); // true
var_dump(0 == false); // true
var_dump(0 == null); // true
var_dump(false == null); // true
var_dump("foo" == 0); // (true до PHP 8) (false => PHP 8.0.0)
var_dump("foo" == "0"); // false
Когда сравниваются строка и число, PHP пытается привести строку к числу. Если строка не начинается с числового значения, она будет приведена к 0. Таким образом:
Код: Скопировать в буфер обмена
"foo" == 0
Это сравнение превращается в:
Код: Скопировать в буфер обмена
0=0
Строгое сравнение (strict comparison)
Строгое сравнение использует оператор === и проверяет как значение, так и тип данных. Это более безопасный способ сравнения, так как он исключает неявное приведение типов.
Примеры строгого сравнения:
Код: Скопировать в буфер обмена
Код:
var_dump(0 === "0"); // false
var_dump(1 === "1"); // false
var_dump(123 === "123"); // false
var_dump(0 === false); // false
var_dump(0 === null); // false
var_dump(false === null); // false
var_dump("foo" === 0); // false
var_dump("foo" === "0"); // false
var_dump("123" === "123"); // true
В этом случае операнды сравниваются без приведения типов, что делает сравнение более предсказуемым и точным.
Hex
Шестнадцатеричные числа, могут использоваться для сравнения с другими числами. Пример с var_dump() в PHP 8.2.12
Код: Скопировать в буфер обмена
Код:
<?php
var_dump(0xA2 == 162);   // bool(true)
var_dump('0xA2' == 162); // bool(false)
var_dump(0xA2 == '162'); // bool(true)
var_dump('0xA2' == '162'); // bool(false)
?>
В первом случае 0xA2 - это шестнадцатеричное число, которое равно 162 в десятичной системе. При использовании оператора ==, PHP выполняет loose сравнение, при котором оба числа приводятся к одному типу (в данном случае, к числам), и их значения сравниваются. Результат true, так как 162 равно 162.
Во втором случаи '0xA2' - это строка. При loose сравнении шестнадцатеричное число, не преобразуется автоматически в числовое значение при сравнении с использованием оператора ==. Вместо этого строка остаётся строкой, и PHP не интерпретирует её как число.
В третьем примере 0xA2 снова интерпретируется как шестнадцатеричное число, которое равно 162. Строка '162' будет преобразована в число 162. Сравнение даст результат true.
В четвертом примере оба значения являются строками. При loose сравнении строки не преобразуются в числа, если обе стороны сравнения являются строками. Строки '0xA2' и '162' не равны друг другу, поэтому результат будет false.

### А где же уязвимость?​

Технически, уязвимость везде где написано == вместо === и обе стороны читаются как цифры. Скажем что есть у нас приватный ключ, который равен "QLTHNDT" и в коде у нас написано что если инпут который даёт ползьователь == нашему ключу, то он получает доступ. Естественно ключ хранится в мд5 формате в бд:
Код: Скопировать в буфер обмена
0e405967825401955372549139051580
И скажем мы сравниваем ключ который ввёл пользователь (NOOPCJF), с тем который у нас в бд
Код: Скопировать в буфер обмена
Код:
<?php
$key = 'NOOPCJF';
if (md5($key) == md5(QLTHNDT)){
    echo 'Success';
};
?>
И это превращается в это
Код: Скопировать в буфер обмена
Код:
if ('0e818888003657176127862245791911' == '0e405967825401955372549139051580'){
    echo 'Success';
};
Как вы видите они не равны, НО когда используется оператор == для сравнения двух строк, PHP пытается интерпретировать строки как числа, если они выглядят как числа. Обе строки '0e818888003657176127862245791911' и '0e405967825401955372549139051580' могут быть интерпретированы как числа:
'0e818888003657176127862245791911' интерпретируется как 0×8(с степенью)18888003657176127862245791911, что равно 0.
'0e405967825401955372549139051580' интерпретируется как 0×4(с степенью)05967825401955372549139051580, что равно 0.

Поскольку обе строки интерпретируются как число 0, сравнение 0 == 0 дает true.
Я очень даже хорошо понимаю то что возможностей тут не много, но это всего один пример на основе говнокода который я написал. Давайте чекнем то что находили на эту тему.

### CVE-2023-50110​

TestLink до 1.9.20 уязвим на Type Juggling для обхода аутентификации, поскольку === не используется.
Код: Скопировать в буфер обмена
Код:
  public function comparePassword(&$dbH,$pwd)
  {
    if (self::isPasswordMgtExternal($this->authentication)) { 
      return self::S_PWDMGTEXTERNAL;
    }
    // If we are here this means that we are using
    // internal password management.
    // 
    // Manage migration from MD5
    // MD5 hash check
    // This is valid ONLY for internal password management
    $encriptedPWD = $this->getPassword();
    if (strlen($encriptedPWD) == 32) {
      /* Update the old MD5 hash to the new bcrypt */
      if ($encriptedPWD == md5($pwd)) {
        $this->password = $this->encryptPassword($pwd,$this->authentication);
        $this->writePasswordToDB($dbH);
        return tl::OK;
      }
    }
    if (password_verify($pwd,$encriptedPWD)) {
      return tl::OK;
    }
    return self::E_PWDDONTMATCH;   
  }
Этот код проверяет пароль на соответствие. Если система использует внешнее управление паролями, она возвращает специальное сообщение. Если управление внутреннее, то код проверяет, сохранен ли пароль в формате MD5. Если да, и пароль совпадает с текущим хэшем MD5, то он обновляет хэш пароля на более безопасный (bcrypt) и сохраняет его в базу данных.
Давайте напишем симуляцию того что наверху:
Код: Скопировать в буфер обмена
Код:
<?php
$encryptedPass = '0e405967825401955372549139051580';
$pwd = '5lYWomyrSgBi';
if ($encryptedPass == md5($pwd)) {
    echo 'Бкрипти $pwd и сохраняет его в БД. ';
};
?>
Значит скажем кто-то поставил такой пароль, что хеш мд5 такой "0е912801..." и есть функция для обновления паролей, куда я могу вставить любой пароль который будет конвертнут в мд5, скажем '5lYWomyrSgBi', мд5 которого '00e82203671254360601934759406438'.

Код бкрипнет мой пароль и я получу доступ, тк как и выше было написано он возьмёт оба инпута как числа, в итоге которого 0 будет равен 0. Шансы такого? Я лично считаю что где то ближе к нулю.

Один и тот же случай (почти) с CVE-2022-47034. CVE-2020-23356, CVE-2020-23361, CVE-2020-8547, CVE-2020-8088...

### А где же интересные случаи ?​

Этот случай взят из программы оффсек, но я по своему написал.
"А где же интересные случаи?" спросите вы меня. Я просмотрел много CVE-шек и как по мне самый интересный случай с ATutor.
Код: Скопировать в буфер обмена
Код:
25: if (isset($_GET['e'], $_GET['id'], $_GET['m'])) {
26:     $id = intval($_GET['id']);
27:     $m  = $_GET['m'];
28:     $e  = $addslashes($_GET['e']);
29: 
30:   $sql    = "SELECT creation_date FROM %smembers WHERE member_id=%d";
31:     $row = queryDB($sql, array(TABLE_PREFIX, $id), TRUE);
32:    
33:     if ($row['creation_date'] != '') {
34:         $code = substr(md5($e . $row['creation_date'] . $id), 0, 10);
35:         if ($code == $m) {
36:             $sql = "UPDATE %smembers SET email='%s', last_login=NOW(), creation_date=creation_date WHERE member_id=%d";
37:             $result = queryDB($sql, array(TABLE_PREFIX, $e, $id));
38:             $msg->addFeedback('CONFIRM_GOOD');
39:
40:             header('Location: '.$_base_href.'users/index.php');
41:             exit;
42:         } else {
43:             $msg->addError('CONFIRM_BAD');
44:         }
45:     } else {
46:         $msg->addError('CONFIRM_BAD');
47:     }
Переменная $id это айди пользователей ATutor и находится под нашим контролем. Переменная $e - новый адрес электронной почты, который мы хотели бы установить. Переменная $m это код который мы передаём. Переменная $code - это подстрока хэша MD5 из десяти символов, частично под контролем злоумышленника.

Значит если m (он же код) будет равен нулю, и если мы сможем как то сделать так чтобы хеш кода был равен тоже нулю (как делали выше 0e0192091....), то сможем поменять пароль пользователя.

Так в чём разница этого случая с предыдущими?
Здесь сравнивается первые 10 символов кода, тоесть хеш может быть и такой: 0e1827381dhsjqosamznftriwoanzgq9.

Значит для генерации кода берётся почта, дата и айди. Почту и айди манипулировать мы можем, скажем что дата это просто год (2024). Тогда я могу отправить такой запрос:
Код: Скопировать в буфер обмена
/changeMail.php?e=test@example.com&m=0&id=10788
В бекенд будет так:
Код: Скопировать в буфер обмена
Код:
$code = substr(md5(test@example.com . 2024 . 10788), 0, 10);
----------
$code = substr('0e075858689f192df8cd60810402d1ca'), 0, 10);
ТК мы м поставили как 0, а из кода берёт только первые 10 символов, идёт сравнение:
Код: Скопировать в буфер обмена
Код:
$code == $m
----------
var_dump('0e07585868' == '0'); // bool(true)
ТК сравнение true, наша почта меняется на test@example.com и мы можем сделать ресет пароля и войти.
Реально ли это? Снова нет, но случай интересный.

### Заключение​

Как по мне, уязвимости такого типа могут сработать нормально если сравнивать какую то часть какого-то хеш-а с инпут-ом который даёт пользователь. B целом даже и если что-то такое завтра найдут в каком то знаменитом продукте, то нужно будет брутить xD.

Автор grozdniyandy

Источник https://xss.is/

 
Сверху Снизу