Совет по безопасности: Повышение привилегий через шаблоны домена
Одна из вещей, на которые обращаю внимание во время аудита безопасности, — это жёстко закодированные домены, используемые для идентификации администраторов, поскольку ими можно манипулировать для атак с повышением привилегий. Иногда невероятно легко!
Атака с повышением привилегий — это когда злоумышленник может повысить привилегии доступа, например, обычный пользователь получает права администратора. Атаки могут быть самыми разнообразными, и иногда для их осуществления используется объединение более мелких уязвимостей.
Давайте рассмотрим пример, с которым недавно столкнулся:
class User extends Authenticatable
{
// ...
public function isAdmin(): bool
{
return Str::contains($this->email, '@securinglaravel.com');
}
}Хотя на первый взгляд всё выглядит отлично, есть два серьёзных недостатка, которые делают это невероятно лёгкой добычей.
1. Отсутствие верификации электронной почты
В классе User отсутствует контракт Illuminate\Contracts\Auth\MustVerifyEmail, и верификация электронной почты не была реализована вручную в других местах. Это позволяет злоумышленнику изменить свой адрес электронной почты на что-то вроде hacker@securinglaravel.com и мгновенно получить полномочия администратора, не требуя доступа к этому почтовому ящику.
Включение верификации и использование проверочного middleware для защиты маршрутов не позволит злоумышленнику получить доступ к полномочиям администратора, поскольку он не сможет получить подтверждающее электронное письмо.
Ознакомьтесь с документацией по верификации электронной почты для получения подробной информации о том, как это работает.
Однако в этом случае есть способ обойти верификацию электронной почты…
2. Содержимое строки
Надеюсь, вы заметили использование Str::contains() вместо Str::endsWith()? Это позволяет креативно подходить к именам доменов. Например:
hacker@securinglaravel.com.evilhacker.devЭто легитимный адрес электронной почты, который злоумышленник может контролировать и получать на него подтверждающие электронную почту письма, что позволит ему подтвердить свою учётную запись и получить права администратора.
Str::contains()на самом деле взята из Copilot на GitHub. Я использовал его для генерации демонстрационного кода приведённого выше. Ожидая, что он будет использовать что-то вродеStr::endsWith(), а вместо этого он использовалStr::contains()!Это может показаться очевидной уязвимостью, когда смотрите на код с точки зрения безопасности, но когда вы находитесь в процессе кодирования, то можете взглянуть на этот код и увидеть просто рабочий код, а не уязвимый.
Мои рекомендации
Я рекомендую использовать либо отдельное поле базы данных (или отдельную таблицу admins), либо конфигурационный список полных адресов электронной почты для указания учётных записей администраторов. Таким образом, ваш список администраторов будет конкретным — вы сможете увидеть каждого пользователя, у которого должны быть полномочия администратора, и если он находится в БД или в .env, вы сможете легко изменить его по мере изменения состава команды.
Жёсткое кодирование доменов (или необработанных адресов электронной почты) непосредственно в коде может обернуться неприятностями в будущем. Я знаю это, потому что успешно злоупотреблял жёстким кодированием доменов в ряде аудитов безопасности. 😈