Совет по безопасности: Повышение привилегий через шаблоны домена
Одна из вещей, на которые обращаю внимание во время аудита безопасности, — это жёстко закодированные домены, используемые для идентификации администраторов, поскольку ими можно манипулировать для атак с повышением привилегий. Иногда невероятно легко!
Атака с повышением привилегий — это когда злоумышленник может повысить привилегии доступа, например, обычный пользователь получает права администратора. Атаки могут быть самыми разнообразными, и иногда для их осуществления используется объединение более мелких уязвимостей.
Давайте рассмотрим пример, с которым недавно столкнулся:
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
, вы сможете легко изменить его по мере изменения состава команды.
Жёсткое кодирование доменов (или необработанных адресов электронной почты) непосредственно в коде может обернуться неприятностями в будущем. Я знаю это, потому что успешно злоупотреблял жёстким кодированием доменов в ряде аудитов безопасности. 😈