Репост из: Искусство. Код... ИИ?
🧩 Принципы и паттерны безопасной разработки: ISP
Часть 3.
А у нас на очереди, принцип разделения интерфейсов (Interface Segregation Principle, ISP — пожалуй, один из наиболее простых в SOLID), гласящий:
Проще говоря, лучше несколько узкоспециализированных интерфейсов, чем один «толстый», навязывающий потребителю кучу лишнего.
С точки зрения безопасности, ISP напрямую перекликается с принципом наименьших привилегий (Least Privilege). «Толстый» интерфейс — это расширенная поверхность атаки: клиент получает доступ к методам, которые ему для работы не нужны. Если один из таких методов оказывается привилегированной операцией, а её защита оказывается слабее ожидаемой, то любой потребитель интерфейса внезапно получает доступ к критичному функционалу.
💡Тривиальный пример:
interface IUserService {
User getProfile(Long id);
void updateProfile(User u);
void resetPassword(String email);
void deleteUser(Long id);
void assignRole(Long id, Role r);
List exportAll();
}
Потребитель, которому нужно лишь отображать профиль, вынужден зависеть от deleteUser, assignRole и exportAll. Если защита реализована на уровне имплементации, а не интерфейса, то одна ошибка авторизации — и профильный компонент может удалять пользователей.
❗Что делать?
Нарушения ISP порождают целый ряд CWE, связанных с чрезмерно широким доступом:
• CWE-749: Exposed Dangerous Method or Function
• CWE-306: Missing Authentication for Critical Function
• CWE-250: Execution with Unnecessary Privileges
В примере выше правильнее ввести два интерфейса, соответствующие уровням привилегий принятой в приложении модели доступа:
interface IUserProfile {
User getProfile(Long id);
void updateProfile(User u);
}
interface IUserAdmin {
void deleteUser(Long id);
void assignRole(Long id, Role r);
List exportAll();
}
Компоненту, работающему с профилями, IUserAdmin попросту недоступен, даже при наличии бага в авторизации.
Разделение интерфейсов можно (и нужно) также проводить, и по границам доверия: один интерфейс не должен обслуживать функциональность по обе стороны от любой из определенных моделью угроз границ.
В плане соблюдения ISP, среди мейнстримовых языков здесь выгодно выделяются два:
💻 Go — не запрещает «толстые» интерфейсы, но поощряет их дробление через удобство композиции и интерфейсы потребителей. Стандартную библиотеку этого языка вполне можно рассматривать, как эталон соблюдения ISP.
👣 Rust — та же композиционная история, но через трейты и необходимость соблюдения «orphan rule».
Забавно, что в этих языках есть и явный анти-паттерн, который не запрещен: пустой интерфейс (interface{} в Go / dyn Any в Rust). Это нарушение ISP: клиент зависит от всего и ни от чего одновременно. Они существуют для низкоуровневых нужд, и их использования стоит по-возможности избегать.
🐛 Жизненное
CVE-2023-22515 — Atlassian Confluence (Broken Access Control, CVSS 10.0).
Confluence использовал фреймворк XWork2 для маршрутизации HTTP-запросов. Через единый веб-интерфейс были доступны как пользовательские действия (просмотр/редактирование страниц), так и привилегированные операции первичной настройки (/setup/setupadministrator.action). В терминах ISP — один «толстый» интерфейс маршрутизации обслуживал и рядовых пользователей, и административный мастер установки.
Атакующий отправлял запрос, манипулирующий свойством bootstrapStatusProvider.applicationConfig.setupComplete через механизм привязки параметров XWork2, «сбрасывая» флаг завершённости установки. После этого endpoint создания администратора становился доступен без аутентификации — атакующий создавал собственный админский аккаунт и получал полный контроль.
Если бы интерфейс маршрутизации был сегрегирован (setup-эндпоинты физически изолированы от пользовательских, с отдельным middleware авторизации), манипуляция параметрами одного интерфейса не открыла бы доступ к другому.
В патче (разбор) эндпоинты, относящиеся к установочным действиям, блокируются после первичной установки, а маршрутизация разделена.
⚠ TL;DR: «Толстый» интерфейс — расширенная поверхность атаки. Если клиент видит метод, то рано или поздно кто-то найдёт способ его вызвать. Стоит разделять интерфейсы, как по привилегиям, так и по границам доверия.
#безопасность_кода #гайд
Часть 3.
А у нас на очереди, принцип разделения интерфейсов (Interface Segregation Principle, ISP — пожалуй, один из наиболее простых в SOLID), гласящий:
Клиенты не должны зависеть от интерфейсов, которые они не используют.
Проще говоря, лучше несколько узкоспециализированных интерфейсов, чем один «толстый», навязывающий потребителю кучу лишнего.
С точки зрения безопасности, ISP напрямую перекликается с принципом наименьших привилегий (Least Privilege). «Толстый» интерфейс — это расширенная поверхность атаки: клиент получает доступ к методам, которые ему для работы не нужны. Если один из таких методов оказывается привилегированной операцией, а её защита оказывается слабее ожидаемой, то любой потребитель интерфейса внезапно получает доступ к критичному функционалу.
💡Тривиальный пример:
interface IUserService {
User getProfile(Long id);
void updateProfile(User u);
void resetPassword(String email);
void deleteUser(Long id);
void assignRole(Long id, Role r);
List exportAll();
}
Потребитель, которому нужно лишь отображать профиль, вынужден зависеть от deleteUser, assignRole и exportAll. Если защита реализована на уровне имплементации, а не интерфейса, то одна ошибка авторизации — и профильный компонент может удалять пользователей.
❗Что делать?
Нарушения ISP порождают целый ряд CWE, связанных с чрезмерно широким доступом:
• CWE-749: Exposed Dangerous Method or Function
• CWE-306: Missing Authentication for Critical Function
• CWE-250: Execution with Unnecessary Privileges
В примере выше правильнее ввести два интерфейса, соответствующие уровням привилегий принятой в приложении модели доступа:
interface IUserProfile {
User getProfile(Long id);
void updateProfile(User u);
}
interface IUserAdmin {
void deleteUser(Long id);
void assignRole(Long id, Role r);
List exportAll();
}
Компоненту, работающему с профилями, IUserAdmin попросту недоступен, даже при наличии бага в авторизации.
Разделение интерфейсов можно (и нужно) также проводить, и по границам доверия: один интерфейс не должен обслуживать функциональность по обе стороны от любой из определенных моделью угроз границ.
В плане соблюдения ISP, среди мейнстримовых языков здесь выгодно выделяются два:
💻 Go — не запрещает «толстые» интерфейсы, но поощряет их дробление через удобство композиции и интерфейсы потребителей. Стандартную библиотеку этого языка вполне можно рассматривать, как эталон соблюдения ISP.
👣 Rust — та же композиционная история, но через трейты и необходимость соблюдения «orphan rule».
Забавно, что в этих языках есть и явный анти-паттерн, который не запрещен: пустой интерфейс (interface{} в Go / dyn Any в Rust). Это нарушение ISP: клиент зависит от всего и ни от чего одновременно. Они существуют для низкоуровневых нужд, и их использования стоит по-возможности избегать.
🐛 Жизненное
CVE-2023-22515 — Atlassian Confluence (Broken Access Control, CVSS 10.0).
Confluence использовал фреймворк XWork2 для маршрутизации HTTP-запросов. Через единый веб-интерфейс были доступны как пользовательские действия (просмотр/редактирование страниц), так и привилегированные операции первичной настройки (/setup/setupadministrator.action). В терминах ISP — один «толстый» интерфейс маршрутизации обслуживал и рядовых пользователей, и административный мастер установки.
Атакующий отправлял запрос, манипулирующий свойством bootstrapStatusProvider.applicationConfig.setupComplete через механизм привязки параметров XWork2, «сбрасывая» флаг завершённости установки. После этого endpoint создания администратора становился доступен без аутентификации — атакующий создавал собственный админский аккаунт и получал полный контроль.
Если бы интерфейс маршрутизации был сегрегирован (setup-эндпоинты физически изолированы от пользовательских, с отдельным middleware авторизации), манипуляция параметрами одного интерфейса не открыла бы доступ к другому.
В патче (разбор) эндпоинты, относящиеся к установочным действиям, блокируются после первичной установки, а маршрутизация разделена.
⚠ TL;DR: «Толстый» интерфейс — расширенная поверхность атаки. Если клиент видит метод, то рано или поздно кто-то найдёт способ его вызвать. Стоит разделять интерфейсы, как по привилегиям, так и по границам доверия.
#безопасность_кода #гайд