Новости
17.09.2024
«Безопасность веб-приложений. Разведка, защита, нападение. 2-е изд.»
Три столпа безопасности приложений — разведка, нападение и защита. Во втором издании Эндрю Хоффман рассматривает десятки смежных тем — от новейших типов атак и средств защиты до моделирования угроз, жизненного цикла безопасной разработки ПО (SSDL/SDLC) и архитектуры нулевого доверия.
Вы получите подробную информацию об эксплойтах и средствах защиты от атак с использованием GraphQL, облачных технологий и доставки контента (CDN). В главы, посвященные атакам и их предотвращению, добавлены сведения для более продвинутых читателей.
Ознакомиться с оглавлением
Об авторе
Эндрю Хоффман (Andrew Hoffman) — старший инженер по безопасности в компании Salesforce.com, где отвечает за безопасность групп JavaScript, Node.js и OSS. Он специализируется на глубоких уязвимостях DOM и JavaScript. Работал со всеми основными поставщиками браузеров, а также с TC39 и WHATWG — организациями, ответственными за разработку будущих версий JavaScript и DOM-браузера.
Эндрю внес свой вклад в готовящуюся к выпуску функцию Realms, призванную обеспечить изоляцию пространства имен на уровне языка в качестве встроенной функции JavaScript. Еще Эндрю изучает потенциальное влияние на безопасность «модулей без сохранения состояния (безопасных/чистых)», значительно снижающих риск от выполнения пользовательского кода JavaScript на веб-порталах.
Четан Каранде, руководитель проекта, OWASP, высказался о книге:
«Безопасность веб-приложений. Разведка, защита, нападение. 2-е изд.» — это исчерпывающий источник практической информации по безопасности веб-приложений.
Атаки загрязнения прототипа
В последние годы, с появлением npm и других менеджеров пакетов JavaScript, возросло число атак, нацеленных на загрязнение прототипов. Загрязнение прототипов (prototype pollution) — это форма атаки, возможная только при использовании языков с прототипным наследованием (например, JavaScript) — формой наследования, отличающейся от традиционного объектно-ориентированного наследования, встречающегося в Java, C# и других популярных языках. Атаки загрязнения прототипа позволяют скомпрометировать объект, к которому нет прямого доступа, скомпрометировав доступный объект, наследующий тот же прототип, что и атакуемый объект.
Основы загрязнения прототипа
Рассмотрим следующие фрагменты кода, написанные на клиентском JavaScript:
const Technician = function(name, birthdate, paymentId) {
this.name = name;
this.birthdate = birthdate;
this.paymentId = paymentId; // для оплаты за работу
}
В прототипных языках программирования такие функции называют псевдоклассами или структурами, подобными классам. Используя этот псевдокласс как образец, можно создать производный экземпляр, и он унаследует состояние и функции родительского псевдокласса, например:
const Bob = new Technician("Bob", "12/01/1970", 12345);
console.log(Bob.toString()); // [object Object]
JavaScript и другие языки, основанные на прототипном наследовании, сохраняют данные внутри так называемой цепочки прототипов. В JavaScript каждый объект имеет свой прототип, содержащий ссылки на все объекты-предки, от которых он наследует функции и данные.
Мы можем убедиться, что Bob действительно принадлежит классу Technician, сравнив прототипы объектов Bob и Technician. Обратите внимание на то, что для доступа к информации о прототипе объекта Bob в этой ситуации следует использовать Bob.__proto__ и сравнивать его с Technician.prototype. Это связано с тем, что __proto__ указывает на реальный объект-прототип, тогда как свойство prototype указывает на шаблон для создания прототипов. Поскольку для создания экземпляра Bob применялась функция-конструктор Technician, при сравнении их прототипов оператор равенства вернет true:
Bob.__proto__ == Technician.prototype; // true
Помимо проверки принадлежности объекта Bob классу Technician можно также убедиться, что Bob — это объект, одним из двух способов. Во-первых, зная, что Bob наследует Technician, можно пройти по цепочке наследования вручную, поскольку Technician не наследует никакие другие пользовательские объекты:
Bob.__proto__.__proto__ == Object.prototype; // true
Далее можно с помощью оператора instanceof еще раз подтвердить, что Bob действительно является экземпляром Object, наследуя экземпляр Technician, который наследует Object, как и все функции в JavaScript:
Bob instanceof Object; // true
С помощью оператора instanceof можно также подтвердить, что Bob является экземпляром Technician. Этот оператор можно считать более короткой альтернативой подъему по цепочке прототипов вручную:
Bob instanceof Technician; // true
Опираясь на новые знания, можно заключить, что иерархия наследования в этом приложении выглядит так:
(Object) -> (Technician) -> (Bob)
И последнее замечание перед тем, как перейти к атаке загрязнением прототипов для компрометации приложений на JavaScript, касается распространения информации по цепочке прототипов. Ранее мы вызвали функцию toString() для объекта Bob, которая вернула строку [object Object], хотя в самом объекте Bob нет такой функции, но она определена в Object. На самом деле, когда вызываемая функция не обнаруживается в текущем объекте, интерпретатор проверяет каждый уровень в цепочке прототипов, пока не найдет функцию с заданным именем.
Если функция так и не будет найдена, интерпретатор сгенерирует ошибку. Однако если родительский класс содержит функцию с соответствующим именем, она будет вызвана. Другими словами, функция toString() отсутствует в объекте Bob, поэтому интерпретатор перейдет по цепочке прототипов к Technician и затем к Object, где и найдет функцию toString(). Этот процесс описан в табл. 16.1.
Таблица 16.1. Цепочка прототипов
В этом случае, не имея доступа к Bob, мы могли бы атаковать прототип класса Bob. Отыскав функцию, которая изменяет прототип Technician или Object, мы сможем изменить работу функции toString() так, чтобы это изменение сказалось на поведении объекта Bob.
Рассмотрим следующую атаку методом загрязнения прототипа, нацеленную на класс Bob, хотя и не воздействующую на него напрямую:
// добавляет функцию в класс Technician
const addTechnicianFunctionality = function(obj) {
Technician.prototype[obj.name] = obj.data
}
// данные, полученные от пользователя
{
name: "toString",
data: `function() { console.log("polluted!"); }`
}
Bob.toString(); // выведет "polluted!"
В этом примере предполагалось сделать класс Technician настраиваемым, чтобы упростить управление состоянием на стороне клиента, но не предполагалось сделать Bob настраиваемым. Из-за изменения функции toString в прототипе Technician поведение функции toString при вызове ее для Bob тоже изменилось.
Атаки загрязнением прототипов используют тот факт, что поиск функций производится по цепочке прототипов, как отмечалось ранее. Это позволяет злоумышленнику загрязнить один объект и распространить загрязнение на близлежащие связанные объекты, недоступные для прямой атаки.
Атака загрязнением прототипа
Ранее мы узнали, как работает прототипное наследование, и теперь понимаем, что если функция или свойство не будут найдены в текущем объекте, то интерпретатор пойдет вверх по цепочке, пока не обнаружит искомое.
Когда полноценное выполнение скрипта (XSS) недоступно на клиенте, есть вероятность, что прототипы будут объединяться и обновляться. Это поверхность для атаки загрязнением прототипов.
Рассмотрим npm-пакет merge v2.0 с открытым исходным кодом. Интернет-сообществу известно, что этот пакет уязвим для атак загрязнением прототипов, несмотря на его простой синтаксис. В библиотеке merge есть функция merge(), которая просто объединяет два объекта. Она часто используется для моделирования состояния на сервере или клиенте.
Давайте еще раз загрязним объект Bob, добавив новое свойство isAdmin: true. Предположим, что мы нашли место в клиентском коде, где производится объединение Object и userData вызовом функции merge(). Применение полезной нагрузки { isAdmin: true } к Object приведет к тому, что Object получит свойство { isAdmin: true }, но это не отразится на объекте Bob, потому что прототип Object еще не обновился.
Увидеть результат этой попытки можно со следующей полезной нагрузкой:
merge(Object, { isAdmin: true });
console.log(Bob.isAdmin); // undefined
Однако, прикрепив эту полезную нагрузку к прототипу, мы видим другой результат:
merge(Object, { "__proto__.isAdmin": true });
console.log(Bob.isAdmin); // true
На этот раз мы успешно загрязнили прототип Object, скомпрометировав и объект Bob.
Примечательно, что сама эта библиотека уязвима для атак загрязнения конструктора. Вместо загрязнения прототипа Object можно загрязнить функцию-конструктор, которая автоматически присоединяется языком JavaScript к Object и используется всякий раз, когда создается экземпляр Object:
merge(Object, { "constructor.prototype.isAdmin": true });
console.log(Bob.isAdmin); // true
В этом случае каждый экземпляр, наследующий Object, будет вызывать конструктор, что приведет к загрязнению с тем же результатом, что и прямое загрязнение прототипа Object.
Архетипы загрязнения прототипов
Загрязнив прототип в веб-приложении, мы можем прочитать некоторую информацию или помешать запланированному выполнению на стороне клиента.
Отказ в обслуживании
Атаки с загрязнением прототипов можно использовать для замедления или изменения нормального выполнения клиентских сценариев. Например, можно изменить вещественное число на целое, что в дальнейшем приведет к ошибкам и помешает нормально задействовать клиентское приложение.
Внедрение свойств
Если сценарий применяет определенное значение для вызова функции, его можно изменить путем загрязнения прототипа. Это может привести к непреднамеренному поведению приложения, которого не ожидает конечный пользователь.
Удаленное выполнение кода
В общем случае это худший сценарий развития атаки загрязнением прототипа. На стороне клиента эту атаку можно перевести в атаку XSS, а на стороне сервера Node.js — в атаку удаленного выполнения кода. В любом случае компрометируются состояние, данные и функциональность приложения. Обычно для развития атаки загрязнением прототипа до удаленного выполнения кода требуется приемник, который выполнит сценарий, например eval(), или функция, генерирующая узлы DOM, например DOMParser.parseFromString().
Кликджекинг
Кликджекинг (clickjacking — «кража кликов») — это непростые, но эффективные атаки против клиента в браузере. Атаки этого вида объединяют вредоносные и обычные элементы пользовательского интерфейса или незаметно вынуждают браузер отправлять входные данные на вредоносный сервер либо выполнять незапланированные вызовы функций.
Существует множество методов атаки на приложение с помощью кликджекинга, в которых задействуются JavaScript, HTML и CSS либо отдельно, либо с применением некоторой технологии. Кликджекинг можно рассматривать как разновидность кейлоггера пользовательского интерфейса. Будучи примененными против ничего не подозревающих конечных пользователей, эти атаки позволяют перехватывать ценные данные, не предназначенные для прочтения третьей стороной.
Эксплуатация камеры и микрофона
Один из наиболее известных ранних примеров кликджекинга — эксплойт взлома микрофона и камеры через Adobe Flash, который был опубликован в сети в 2008 году после обнаружения уязвимости исследователями безопасности Робертом Хансеном (Robert Hansen) и Джереми Гроссманом (Jeremiah Grossman). Этот эксплойт представлял собой набор веб-ссылок, выглядящих как игра или веб-страница, совершенно не связанных с проигрывателем Adobe Flash.
Без ведома конечного пользователя каждый щелчок в этой игре или на веб-странице соответствовал щелчку на расположенной под ней странице с настройками Adobe Flash. Страница с настройками загружалась в плавающий фрейм iframe, для которого устанавливалась полная прозрачность, поэтому она не была видна конечному пользователю.
При взаимодействии ничего не подозревающего пользователя с веб-страницей, скомпрометированной посредством кликджекинга, его щелчки мышью перенаправлялись в настройки конфиденциальности Adobe Flash. В результате эксплойт плагина Adobe Flash для браузера давал хакеру возможность управлять камерой и микрофоном. Этот случай стал одним из самых массовых примеров кликджекинга в мире информационной безопасности, позволяющим с помощью плагина Adobe Flash выйти за пределы изолированной программной среды браузера и получить привилегированный доступ к компьютерному оборудованию.
Создание эксплойтов для кликджекинга
Кликджекинг может осуществляться разными способами. Самый распространенный — создать веб-сайт, выглядящий как обычно, но содержащий невидимый iframe, который ссылается на атакуемый веб-сайт.
Рассмотрим такой пример веб-сайта:
<html>
<head>
<title>Clickjacker</title>
</head>
<body>
<div id="clickjacker">
<span id="fake_button">click me</span>
</div>
<iframe id="target_website" src="target-website.com"></iframe>
</body>
</html>
Здесь атакуется веб-сайт target-website.com, для чего создается его экземпляр во фрейме iframe, находящемся под разделом div с id=«clickjacker». Чтобы сделать target-website.com невидимым, можно использовать следующий класс CSS:
#target_website {
opacity: 0;
}
Элемент div с id=«clickjacker» содержит кнопку, которую можно расположить непосредственно над законной кнопкой в iframe средствами позиционирования в CSS:
#fake_button {
position: relative;
right: 25px;
top: 25px;
pointer-events: none;
background-color: blue;
}
Благодаря CSS-атрибуту pointer-events: none в #fake_button любые взаимодействия (щелчки) на элементе с CSS-классом #fake_button будут передаваться элементу под этой кнопкой. В данном случае под элементом div, внутри iframe, находится кнопка, которую конечный пользователь не собирается нажимать. При нажатии #fake_button событие щелчка передается в iframe и запускает функцию на другом веб-сайте.
К сожалению, из-за особенностей модели безопасности, реализованной в браузере, iframe, скорее всего, будет иметь доступ к сеансовым файлам cookie веб-сайта, открытого во фрейме. Это означает, что щелчок может инициировать привилегированные запросы к веб-серверу, например, вызов API для открытия профиля или запуска финансовой транзакции.
Кликджекинг — один из самых простых способов заставить пользователя выполнить действия от своего имени, необходимые злоумышленнику, если в веб-приложении отсутствуют соответствующие средства управления фреймами.
Табнаббинг и обратный табнаббинг
Табнаббинг (tabnabbing — «подмена вкладок») и родственный ему обратный табнаббинг (reverse tabnabbing) — это формы атаки на стороне клиента, сочетающие элементы фишинговых атак (которые обманным путем заставляют пользователя взаимодействовать с вредоносной веб-страницей) и атак с перенаправлением (которые перенаправляют обращения к текущей веб-странице на вредоносную веб-страницу).
Табнаббинг основан на применении DOM API браузера для перенаправления с текущей страницы на новую или для перезаписи содержимого текущей страницы с помощью HTML/CSS и JS, созданных хакером.
Традиционный табнаббинг
Традиционная реализация табнаббинга использует объект window, который поддерживается всеми основными браузерами и определен как часть спецификации WHATWG DOM. Когда сценарий вызывает функцию window.open(), чтобы открыть новую вкладку, то получает в ответ ссылку на объект window новой вкладки.
В традиционном табнаббинге вредоносный веб-сайт открывает новую вкладку, как показано в следующем примере:
<button onclick="goToLegitWebsite()">click to go to legit website</button>
const goToLegitWebsite = function() {
// открыть новую вкладку с проверенным веб-сайтом
const windowObj = window.open("https://website-b.com");
// через 5 минут сменить содержимое вкладки,
// открыв вредоносный сайт
setTimeout(() => {
windowObj.location.replace("https://website-c.com");
}, 1000 * 60 * 5);
};
В этом примере веб-сайт A предоставляет пользователю ссылку для открытия веб-сайта Б на другой вкладке. Но так как новая вкладка с сайтом Б открывается вызовом функции open(), ссылка на объект window новой вкладки сохраняется в памяти открывающей вкладки (веб-сайт A).
Позже, после того как конечный пользователь посетит новую вкладку и убедится, что на ней открыт законный веб-сайт, открывшая вкладка (веб-сайт A) обращается к DOM API веб-сайта Б и инициирует перенаправление вызовом windowObj.location.replace(). В результате сайт Б заменяется веб-сайтом В — внешне идентичным веб-сайту Б (законному веб-сайту), но когда пользователь пытается повторно выполнить вход, он просто копирует свои учетные данные, которые затем отправляются на хакерский сервер, а сам пользователь переадресуется на страницу с сообщением об ошибке на веб-сайте Б.
Это довольно сложный вид атаки, в ходе которой содержимое вкладки с веб-сайтом Б временно замещается веб-сайтом В — вредоносным веб-сайтом, которым управляет хакер. Поскольку, открыв вкладку, пользователь убедился, что на ней отображается законный веб-сайт, он едва ли заметит быстрое изменение содержимого, которое могло произойти, даже пока он просматривал другую вкладку. В результате обмануть его и вынудить ввести учетные данные или другую конфиденциальную информацию намного проще, чем при обычных фишинговых атаках.
Все описанное возможно благодаря тому, что DOM-функция window.open возвращает ссылку на объект window новой вкладки и позволяет открывающей вкладке вызывать функции этого объекта. Для эксплуатации уязвимости нужно разработать веб-сайт с реализацией табнаббинга на JavaScript, фишинговый веб-сайт, повторяющий пользовательский интерфейс законного веб-сайта, а затем обманом заставить конечного пользователя щелкнуть на ссылке, чтобы открыть новую (скомпрометированную) вкладку.
Обратный табнаббинг
Обратный табнаббинг работает в противоположном направлении. Вредоносным должен быть не веб-сайт, открывающий новую вкладку, а веб-сайт, открываемый в новой вкладке и атакующий начальную вкладку. Существует несколько способов реализации обратного табнаббинга, но все они основаны на возможности выполнять вызовы DOM API для вкладки, открывшей текущую вкладку.
Атака DOM API
Самый простой метод атаки обратного табнаббинга — подготовить вредоносный веб-сайт, затем убедить законный веб-сайт открыть ваш вредоносный веб-сайт вызовом функции window.open().
Когда метод window.open() вызывается для создания новой вкладки, он по умолчанию передает ссылку на свой объект window в новую вкладку. Это очень похоже на традиционный табнаббинг.
Рассмотрим следующий пример:
<!-- Элемент пользовательского интерфейса для легального веб-сайта -->
<button onclick="openTab()>click me</button>
// Сценарий для легального веб-сайта
window.open("https://malicious-website.com")
// Сценарий для вредоносного веб-сайта
window.opener.location.replace("https://get-hacked.com")
Как видите, ссылаясь на свойство opener своего объекта window, новая вкладка может управлять открывшей ее вкладкой, изменить ее содержимое, подменив законный веб-сайт вредоносным, и с его помощью выманить у конечного пользователя учетные данные и другую информацию.
Для обратного табнаббинга, как и для традиционного, нужно создать вредоносный веб-сайт, вызывающий возможности свойства window.opener. Основной недостаток этого приема — не все способы создания вкладок сохраняют в свойстве window.opener ссылку на открывшую вкладку, поэтому не все веб-сайты уязвимы для такой формы атаки.
Атака по HTML-ссылке
Если свойство window.opener не заполняется веб-сайтом, который вы хотите скомпрометировать, то провести атаку методом обратного табнаббинга можно, если есть возможность создать самому или обманом вынудить законный веб-сайт создать HTML-ссылку с атрибутом target=_blank.
Атрибут target=_blank тоже заставляет браузер открыть новую вкладку и создать для нее объект window со свойством opener:
<!-- легитимный веб-сайт, создающий ссылку, сгенерированную пользователем -->
<a href="https://malicious-website.com" target="_blank">click me</a>
// сценарий на зловредном веб-сайте
window.opener.location.replace("https://get-hacked.com")
Атака через iframe
Наконец, если ни одна из двух вышеупомянутых атак невозможна, то обратный табнаббинг можно выполнить внутри iframe. Если вызываемый iframe ссылается на ваш вредоносный веб-сайт и не имеет защиты от обратного табнаббинга (например, в виде атрибута sandbox или поддержки политики CSP), то вы сможете получить доступ к родительскому объекту window через DOM-свойство window.parent:
<!-- легитимный веб-сайт -->
<iframe src="https://malicious-website.com"></iframe>
// JavaScript на вредоносном веб-сайте
window.parent.location.replace("https://get-hacked.com");
Итак, атаки методом табнаббинга основаны на получении доступа из одной вкладки к объекту window другой вкладки с помощью небезопасных функций DOM API браузера. Получив доступ к этому объекту — прямым вызовом DOM, через HTML-ссылку или незащищенные фреймы iframe, — вредоносная вкладка может распоряжаться содержимым законной вкладки по своему усмотрению, используя DOM API.
Резюме
Итак, к атакам на клиента относятся атаки, нацеленные на клиента или характерные исключительно для клиента и не требующие отправлять запросы к веб-серверу. Атаки на клиента, такие как табнаббинг (подмена вкладок), кликджекинг (кража кликов) и загрязнение прототипов, позволяют скомпрометировать состояние приложения и перехватывать нажатия клавиш — часто незаметно для сервера, доставившего код клиентского приложения.
Понимание особенностей атак на клиента — важная часть инструментария любого специалиста по безопасности.
Антипаттерны безопасного программирования
Проверки безопасности на уровне кода имеют некоторые общие черты с планированием архитектуры приложения, которое происходит перед написанием кода. Но если на этапе планирования архитектуры все уязвимости являются гипотетическими, в процессе проверки кода их действительно можно обнаружить.
Есть несколько антипаттернов, на которые следует обращать внимание при любых проверках безопасности. Часто к ним относится наспех внедренное решение. Впрочем, по какой бы причине ни возникали такие явления, умение их видеть ускоряет процесс проверки.
Рассмотрим примеры распространенных антипаттернов. Каждый из них может нанести ущерб, если попадет в итоговую сборку.
Черные списки
В сфере безопасности лучше не использовать временные решения, а сразу искать постоянные, даже если это займет больше времени. Единственный случай, когда допустимо временное или неполное решение, — это заранее запланированный график, на основе которого будет спроектировано и реализовано полное. Черные списки — это пример временного или неполного решения.
Представим, что на стороне сервера создается механизм фильтрации, задающий список допустимых для интеграции доменов:
const blocklist = ['http://www.evil.com', 'http://www.badguys.net'];
/*
* Определяем, разрешена ли интеграция с этим доменом.
*/
const isDomainAccepted = function(domain) {
return !blocklist.includes(domain);
};
Этот код часто принимают за решение, но это не так. Даже если на начальном этапе он выглядит как решение, его следует рассматривать как неполное (если, конечно, вы не в курсе всех возможных доменов, что маловероятно) или временное (вы не сможете предугадать, какие домены придется отфильтровывать в будущем).
Другими словами, черный список защитит приложение, только если гарантированно известны все возможные текущие и будущие входные данные. А поскольку получить такую информацию нереально, черный список не обеспечит достаточной защиты. Более того, приложив немного усилий, его можно обойти (например, хакер просто купит другой домен).
В сфере безопасности всегда предпочитали белые списки. Они дают чуть лучшую защиту — достаточно изменить способ, которым разрешена интеграция:
const allowlist = ['https://happy-site.com', 'https://www.my-friends.com'];
/*
* Определяем, разрешена ли интеграция с этим доменом.
*/
const isDomainAccepted = function(domain) {
return allowlist.includes(domain);
};
Некоторые инженеры утверждают, что белые списки усложняют среду разработки продуктов, потому что требуют постоянного ручного или автоматического обслуживания. Если такой список обновляется вручную, это действительно может напрягать, но сочетание ручных и автоматических методов позволяет значительно упростить обслуживание.
В этом примере требование к интегрирующим партнерам представить для проверки свой веб-сайт, бизнес-лицензию и т. п. перед внесением в белый список затрудняет появление вредоносной зависимости. Но даже если предположить, что это все же произошло, такая зависимость не попадет туда повторно после удаления (для этого злоумышленникам понадобились бы новый домен и бизнес-лицензия).
Шаблонный код
Следует обратить внимание также на применение шаблонного кода (boilerplate code), который фреймворк генерирует по умолчанию. Дело в том, что фреймворки и библиотеки зачастую требуют дополнительных усилий для повышения безопасности, тогда как, по идее, она должна существовать по умолчанию.
Классическим примером является ошибка конфигурации в базе данных MongoDB, из-за которой установленные на веб-сервере более старые версии базы по умолчанию оказывались доступными через Интернет. При этом там не требовалась обязательная аутентификация, в результате чего с помощью специальных сценариев были захвачены десятки тысяч баз MongoDB, а за их возврат требовали биткоины. А ведь этого можно было избежать, исправив пару строк в файле конфигурации и оставив только локальный доступ к MongoDB.
Подобные проблемы связаны с большинством основных фреймворков. Например, в Ruby on Rails шаблонный код страницы 404 позволяет легко получить информацию об используемой версии фреймворка. То же самое касается и EmberJS, где по умолчанию создается целевая страница, которую необходимо удалять.
Фреймворки избавляют разработчиков от сложной и рутинной работы, но если те не понимают, что стоит за шаблонным кодом, может получиться так, что он будет применяться без надлежащих механизмов безопасности. Поэтому избегайте запуска любого шаблонного кода в производственную среду, если он предварительно не оценен и не настроен должным образом.
Доверие по умолчанию
При создании приложения с несколькими уровнями функциональности, которые запрашивают ресурсы у серверной операционной системы, крайне важно корректно реализовать модель разрешений для вашего кода.
Представим приложение, способное генерировать на стороне сервера журналы регистрации, записывать файлы на диск и обновлять базу данных SQL. Во многих реализациях на сервере будут создаваться учетные записи пользователей с разрешениями на записи в журнал, доступ к базе данных и диску. То есть учетная запись дает пользователю доступ ко всей функциональности приложения. Это означает, что уязвимость, которая разрешает выполнение кода или изменяет предполагаемое выполнение сценария, скомпрометирует все три серверных ресурса.
Безопасное приложение генерирует разрешения для ведения журнала, записи на диск и выполнения операций с базой данных независимо друг от друга. Каждый модуль в нем будет работать под собственным пользователем со специально настроенными разрешениями, допускающими только то, что требуется для работы конкретной функции. При таком подходе критический сбой в одном модуле не распространяется на другие. Уязвимость в модуле SQL не должна давать хакеру доступа к файлам или журналам регистрации на сервере.
Разделение клиента и сервера
Следует обращать внимание на слишком тесную связь клиента и сервера. Этот антипаттерн возникает, когда код клиентской и серверной частей приложения настолько переплетается, что одна не может работать без другой. В основном такое встречается в старых веб-приложениях, но и сегодня подобные вещи порой происходят. В безопасном приложении клиентская и серверная части не должны зависеть друг от друга. Они взаимодействуют по сети с использованием заранее определенного формата данных и сетевого протокола.
Приложения без разделения клиента и сервера, в которых, к примеру, в шаблонный PHP-код добавлена логика аутентификации, намного проще эксплуатировать. Модуль не читает результаты сетевого запроса (например, при аутентификации), а отправляет обратно свой HTML-код, включая в него любые данные формы. За синтаксический анализ этого HTML-кода должен отвечать сервер, который будет гарантировать, что ни внутри этого кода, ни в логической схеме аутентификации не произойдет выполнение сценария или изменение параметров.
В полностью разделенном клиент-серверном приложении сервер не несет ответственности за структуру и содержание данных HTML. Он отклоняет любой присланный HTML-код и принимает только данные, необходимые для аутентификации, причем в заранее определенном формате. В распределенном приложении каждый модуль отвечает за собственные механизмы защиты. Монолитное же приложение должно учитывать механизмы защиты на многих языках, а также тот факт, что полученные данные могут быть отформатированы разными способами.
Разделение ответственности важно с точки зрения как инженерии, так и безопасности. Правильно разделенные модули упрощают управление механизмами защиты, которые не должны перекрывать друг друга или учитывать редкие пограничные случаи, возникающие при сложных взаимодействиях между несколькими типами данных/сценариев.
Резюме
При проверке кода на предмет безопасности необходимо не просто искать общие уязвимости (об этом мы поговорим в следующих главах). Нужно учитывать антипаттерны, которые могут выглядеть как решения, но впоследствии станут источником проблем. Проверки кода на безопасность должны быть всесторонними, то есть охватывать все потенциально опасные области.
В процессе проверки кода необходимо рассмотреть конкретные требования к использованию приложения, чтобы понять, какие логические уязвимости, не вписывающиеся в общий архетип, могут в нем возникнуть. Для обзора кода стоит выбирать логическую последовательность, позволяющую оценить варианты применения приложения и сопутствующие каждому из них риски. В давно существующих приложениях, где области высокого риска хорошо известны, следует сосредоточить большую часть усилий на этих областях, а оставшиеся рассматривать в порядке уменьшения риска.
Если все сделано правильно, добавление процедуры проверки безопасности в конвейер проверки кода снизит вероятность появления уязвимостей в базе. Это должно стать частью любого современного конвейера разработки ПО и по возможности выполняться специалистами в области безопасности совместно с разработчиком продукта или функции.
Ознакомиться с «Безопасностью веб-приложений» можно на нашем сайте.
Комментарии: 0
Пока нет комментариев