Как вы знаете, большая часть атак BlackHat-хакеров направлена на компрометацию серверных данных web-приложений и сервисов. При этом клиентскую часть сегодня атакуют не реже. Согласно сухому определению, любая атака — это комплекс мер со стороны хакера, направленных на сеть и передачу данных, на данные и их подмену, инфраструктуру и технические особенности реализации web-приложения. Поэтому международные компании требуют от инженеров-разработчиков более ответственного и тщательного подхода к безопасности клиентских приложений.
На примере моего проекта я расскажу о том, как сегодня атакуют клиентские приложения и как вам избежать этих угроз.
![Топ 10 угроз за 2013 — 2017 годы](https://habrastorage.org/webt/ys/zg/d7/yszgd7tqw75npl5bx9pliitikte.jpeg)
Топ 10 угроз за 2013 — 2017 годы.
Как видите, среди основных угроз первое место занимают инъекции, вызов ошибок, обход аутентификации и незащищенность конфиденциальных данных. По-прежнему актуальна угроза использования компонентов с известными уязвимостями. Также появились новые угрозы: взлом механизма контроля доступа, небезопасная десериализация и сериализация данных, недостаточно подробное логирование и мониторинг.
В 2001 году Марк Керфи и Дэннис Грувз основали OWASP (Open Web Application Security Project). Это международный OpenSource-проект для обмена опытом по борьбе с уязвимостями в клиентских приложениях, в котором принимает участие огромное количество инженеров по безопасности приложений. Сообщество OWASP наполняет портал множеством статей с информацией об уязвимостях, учебными материалами, инструментарием для тестирования и отражения атак. Описываются реальные атаки, раскрываются их аспекты и рассказывается о том, что надо сделать для предотвращения угроз.
Чтобы понять, какие угрозы опасны для проекта, надо его тщательно протестировать. Для этого в сети есть приложения, фреймворки и онлайн-сервисы, которые автоматически определяют те или иные уязвимости. Для локального тестирования рекомендую использовать приложения и фреймворки, а для тестирования проектов в эксплуатации очень полезно добавить ещё и онлайн-сервисы.
![](https://habrastorage.org/webt/zh/c7/ll/zhc7lln_b43msp-lkz7ah0iapmw.jpeg)
Но даже если инструменты тестирования не рассказали вам в отчетах про существенные уязвимости (что маловероятно), всё-таки уделите внимание хранению конфиденциальных данных в системе контроля версий, сборке приложения, механизму аутентификации, алгоритму хэширования паролей, функции кодирования конфиденциальных данных, а также системам логирования и мониторинга всего web-приложения. В этом деле лучше перестраховаться и не доверять слепо автоматике.
Git
Для начала поговорим о конфиденциальных данных в Git. В идеальном случае для хранения конфиденциальных данных выделяется отдельное хранилище секретов. Из него, при сборке для ввода в эксплуатацию, подтягиваются конфиденциальные данные и вшиваются в приложение. Сегодня популярны хранилища секретов Hashicorp Vault, Keywhiz, Docker secrets, Azure Key Vault и ряд других.
Но что делать, если у вас такого хранилища нет? Можно воспользоваться инструментами для кодирования и сокрытия файлов с секретами, которые будут расширять возможности систем контроля версий.
Первое, что приходит в голову — универсальное решение BlackBox. Его можно использовать с любой системой контроля версий, например, Mercurial, Git и т.д. Кроме того, для Git есть два расширения: git-crypt и git-secret. Я рекомендую использовать второе, потому что оно показалось мне наиболее удобным в использовании и более понятным в плане описания в официальной документации. После установки git-secret нужно его инициализировать в Git-репозитории. Не забудьте в файле .gitattributes указать используемое расширение. Далее настройте доступность секретов: определите пользователей, которым хотите предоставить доступ к конфиденциальным данным. Потом добавьте файлы с конфиденциальными данными и скройте их через git-secret-hide
. Получить скрытые файлы можно через git-secret-reveal.
brew install git-secret // Установка
git secret init // Инициализация
git secret tell your@gpg.email  // Конфигурирование доступности
git secret add <files...> // Добавление файлов
git secret hide  // Сокрытие файлов
git secret reveal  // Получение скрытых файлов
Webpack
Ещё один способ устранения угроз — правильно сконфигурировать webpack. Чтобы защититься от XSS, XEE и подобных атак, надо продумать соблюдение политик CORS (Cross-origin resource sharing) и CSP (Content Security Policy). В обоих случаях важно соблюдать заголовки, чтобы проверять достоверность тех или иных скриптов, которые используются в проекте. В браузерах есть механизмы проверки достоверности того или иного источника, к примеру, Safari будет на каждом шагу выдавать предупреждения, если CORS и CSP настроены некорректно.
Есть два пути соблюдения CORS и CSP. Первый — настроить заголовки ответа на запросы в серверной части. Второй — прописать обе политики через мета-теги и атрибуты. Последний способ рекомендуется в том случае, если у вас ленивые back-end разработчики, они вечно заняты и им не до политик безопасности. Мета-теги можно прописывать сразу же при сборке приложения. В этом нам помогут такие плагины, как html-webpack-plugin, html-webpack-exclude-assets-plugin, script-ext-html-webpack-plugin, csp-html-webpack-plugin и crypto. Кроме того, если у вас в проекте есть сторонние ресурсы (например, ссылки на внешние шрифты, применяемые в CSS; ресурсы, подгружаемые из CDN, и так далее), то рекомендую использовать ещё и webpack-subresource-integrity-plugin. Таким образом, вы будете сообщать браузеру, что подгружаемые ресурсы в скрипте достоверны, в них нет никаких внедрений, они целые и неиспорченные. И если даже кто-то внедрил в ресурс зловредные данные, а ты их подгрузил, то надо быть готовым к этому и обезопасить свой проект от подобных угроз.
Хочу обратить особое внимание на то, в какой последовательности и каким образом создаются экземпляры классов для плагинов. Очерёдность должна быть именно такой:
const SHA256 = (str) => CRYPTO.createHash('sha256').update( str, 'utf8').digest('base64');
const sha256Str = SHA256( '' + Date.now() );
[…]
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'public/index.html'
}),
new ScriptExtHtmlWebpackPlugin({
custom: [{
test: /\.js$/,
attribute: 'nonce',
value: 'nonce-' + sha256Str
}]
}),
new HtmlWebpackExcludeAssetsPlugin(),
new CspHtmlWebpackPlugin({
'base-uri': '\'self\'',
'object-src': '\'none\'',
'script-src': ['\'self\'', '\'unsafe-eval\'', '\'nonce-' + sha256Str + '\''],
'style-src': ['\'unsafe-inline\'', '\'self\'']
}, {
devAllowUnsafe: false,
enabled: true,
hashingMethod: 'sha256'
}),
new SriPlugin({
hashFuncNames: ['sha256', 'sha384'],
enabled: true
}),
[…]
Тогда при сборке в теге <heаd>
появится мета-тег http-equiv=content-security-policy
. В атрибуте content
будут прописаны директивы, которые говорят о том, каким скриптам и ресурсам можно доверять.
Директива base-uri
показывает, какой базовый URL используется для загрузки скриптов, CSS, картинок и прочего.
Объекты обычно не загружаются, поэтому в директиве object-sr
c ставьте none
.
Директива script-src
относится к JS-скриптам.
Не забывайте каждый раз прописывать атрибут типа nоnce-<hаshVаlue>
. Причем хэш нужно вычислять с помощью алгоритма SHA256 или SHA512.
Что касается директивы style-src
, то у нашего проекта есть особенность: мы используем styled-components, чтобы для каждого компонента прописывать CSS и изолировать их друг от друга. И поэтому надо обязательно указать, что у нас в style-src
используются значения unsafe-inline
и self
, иначе styled-components отвалится.
![](https://habrastorage.org/webt/9x/di/ny/9xdinyh5msrmdszkkdayka1tqya.jpeg)
Для тега script
автоматически будут заданы атрибуты nоnce-<hаshVаlue>
, integrity
и cross-origin
. Они говорят браузеру о том, что ресурсы подтягиваются из достоверных источников. Иначе, если браузер увидит, что ресурс не соответствует CSP или CORS, то он просто не загрузит этот скрипт или CSS-файл, а в консоли напишет что-то вроде: «Обрати внимание на этот скрипт, на вот эту строку, где ты инициализируешь его. Посмотри, у тебя что-то не так!».
В документации MDN, OWASP и W3C есть рекомендации по соблюдению политик CSP и CORS. Кроме того, любой инструментарий для тестирования на проникновение будет сообщать о соблюдении правил CORS и CSP в проекте. Любой фреймворк или инструмент, который будет проводить автоматическое тестирование проекта, укажет на недочёты.
Аутентификация пользователей
Мы используем OpenID Connect и протокол Kerberos. Довольно распространённый стандарт OpenID применяется для аутентификации внешних пользователей.
Kerberos больше подходит для внутренней сети, в банке он используется для автоматической аутентификации сотрудников. Допустим, есть локальная машина, на которой работает сотрудник организации. Он один раз аутентифицировался на этой машине, и потом ему уже нигде не нужно будет снова вводить логин и пароль: сотрудник заходит в любое приложение и система сразу его аутентифицирует. У Kerberos тонкие настройки для локальной машины, и это вызывает затруднения, потому что приходится настраивать для каждого компьютера и каждого браузера. Если Internet Explorer нормально подтягивает настройки по умолчанию, а Chrome подтягивает настройки IE, то Firefox приходится настраивать отдельно. Safari на MacOS X сам найдёт настройки, а для Safari на Windows нужно будет указать их вручную.
Надо проверять своё приложение во всех браузерах, везде ли оно работает как нужно. К примеру, если я работаю на Windows, то ставлю Safari локально и тестирую в нём свой проект, а если работаю на Mac, то поднимаю Windows на виртуальной машине, чтобы погонять приложение на соответствующих версиях браузеров.
Реализовать аутентификацию в современных приложениях можно с помощью пакетов Passport.js и express-session, а также SDK Auth0.
Если у вас нет возможности разработать сервис аутентификации через OpenID Connect или любой другой протокол, то используйте проксирующий слой, такой как Auth0 и ему подобные, чтобы аутентификация происходила через стороннюю компанию, которая специализируется на обеспечении безопасного доступа пользователей к интернет-ресурсам.
Когда мы поднимаем на Node.js какое-то приложение, то рекомендую использовать на сервере пакеты Passport.js, express-session и т.п. Для обеспечения безопасности на клиенте мы самостоятельно поднимаем компонент для аутентификации. В форме аутентификации не забывайте указывать атрибут autocomplete off, чтобы исключить автозаполнение полей формы.
Хэширование паролей
На сайте OWASP рекомендуют не использовать встроенные в базы данных механизмы хеширования паролей. Для этого лучше применять такие пакеты, как Argon2, PBKDF2, ccrypt и bcrypt. В своей практике я использую Argon2 — это обёртка для алгоритмов GCC, PGP/GPG и т.п., но для него требуется сначала установить пакет GCC. Схема использования Argon2:
1. Установка GCC >= 4.8 install
$ brew install gcc
2. Установка библиотеки для обеспечения С-имплементаций
$ npm install -g node-gyp
3. Установка пакета Argon2
$ npm install argon2
4. Использование пакета
import * as ARGON from 'argon2';
ARGON.generateSalt().then( (salt: string) => {
ARGON.hash('some-user-password', salt)
.then((hash : string) => {
console.log('Successfully created Argon2 hash:', hash);
// TODO: store the hash in the user database
});
});
argon2.verify(
'previously-created-argon-hash-here',
'some-user-password').then(() => {
console.log('Successful password supplied!');
// TODO: log the user in
}).catch(() => {
console.log('Invalid password supplied!');
});
Обфускация
Обфускация позволяет видоизменить код таким образом, чтобы его невозможно было разобрать на составляющие. Ведь злоумышленники — да и не только они — очень часто используют обратный инжиниринг: программист берёт какой-либо JS-файл и начинает анализировать исходники. Таким образом он может узнать используемые методы или разобраться в механизме работы того или иного скрипта, чтобы внедрить зловредный код. Или воспользоваться этими механизмами для взлома веб-приложения и проведения незаметной атаки.
Хакеры не лезут на рожон. Сначала они проводят разведку ресурса, определяют уязвимости и вектор атаки. Например, манипулируют с данными или используют уязвимости, которые содержатся в транспортных протоколах. Вектор атаки может быть направлен на уязвимости той или иной операционной системы, их очень много в любой UNIX-системе. Но уязвимости могут быть использованы только в том случае, если администратор плохо настроил политики безопасности, например, неправильно задал URL-ы, которые выходят наружу.
Так вот, для проведения разведки используется обратный инжиниринг. Полностью исключить его невозможно, но можно сильно затруднить. Для этого применяют различные обфускаторы, в моём случае — javascript-obfuscator. На его базе сделан плагин под webpack — webpack-obfuscator. Также для webpack создан obfuscator-loader. У этого пакета есть рекомендуемые настройки разного уровня параноидальности: low, medium и high, с ними можно ознакомиться на официальном сайте. Если вы используете этот обфускатор, то помните, что он очень плохо работает со встроенным в webpack механизмом минификации. Не используйте вместе минификацию и обфускацию, иначе обфускатор может полностью сломать код скрипта.
Кроме того, обфускатор увеличивает объем скрипта и его загрузку. Здесь нужно для себя решить: либо вы усиливаете безопасность, стабильность и надёжность, но теряете в удобстве и скорости; либо заботитесь о скорости, но забываете о безопасности, о следовании каким-либо руководствам.
Логирование и мониторинг угроз
Существует такая угроза, как использование пакетов с уже известными уязвимостями. В подобных ситуациях помогут анализаторы угроз, такие как npm audit, Snyk и LGTM. Npm audit — это стандартная утилита, которая встроена в npm, но придётся постоянно вызывать эту команду или придумывать костыли. Поэтому советую использовать Snyk. У этого проекта есть своя база данных с уязвимостями. Когда начинаешь проверку, Snyk обращается к этой базе и конфиденциально выгружает отчёт в ваш Snyk-проект, недоступный посторонним. Правда, бесплатно проверить свой проект можно только 300 раз, и когда делаешь проверку на каждый pre-commit, эти 300 бесплатных попыток очень быстро заканчиваются. Поэтому лучше запускать проверку на pre-push или pre-merge хуки.
Человек — самая главная уязвимость любой системы. Поэтому обязательно проверяйте проект до начала сборки приложения, ведь даже в исходном коде может содержаться что-то вредоносное. Хорошо, когда доступ к проекту имеет только один человек, но обычно мы работаем командами. А вдруг появился какой-то «доброжелатель», который решил уйти из компании «красиво» и оставить после себя след? Это тоже надо иметь в виду.
Использовать пакет Snyk я рекомендую с самого начала проекта, а проверку запускать из консоли. Тут всё просто: после установки задайте логин и пароль к учетной записи, а само тестирование можно выполнить так:
- После установки зависимости npm i snyk —D и указания в package.json «snyk»: true запустите:
./node_modules/.bin/snyk wizard --dev
- В package.json добавьте скрипты и настройки:
{
...
"scripts": {
...
"test": "npm run test:snyk && npm run test:jest",
...
"test:snyk": "snyk test --dev",
...
"prepare": "npm run prepare:snyk",
"prepare:snyk": "snyk protect"
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
"pre-commit": "npm run test:snyk && npm run lint && npm run test:jest",
"pre-push": [
"npm run test:snyk",
"npm run lint",
"npm run test:jest",
"npm run build:production"
],
...
}
},
"snyk": true
}
Выше мы рассмотрели локальную проверку на угрозы безопасности. Для проверки пакетов на известные угрозы рекомендую пользоваться еще и LGTM. Этот проект используйте в связке с GitHub или Bitbucket (пока не пробовал, не было необходимости), и код будет сразу проверяться при каждом пуше.
Мониторинг приложения
В сфере Front-end'а инструментарий уже устоялся, для логирования и мониторинга клиентской части доступны инструменты на любой вкус. Самые известные — это Sentry, TrackJS и InsightOps. Сервер Sentry можно развернуть на своих физических серверах. К примеру, в двух наших проектах использовался отдельный сервер, полностью сконфигурированный под логирование работы приложений. Мы заходили по URL и скидывали туда все логи. Если в приложении возникает ошибка, то она обёртывается в блок try catch и через методы пакета raven отправляется на сервер Sentry. Всё просто и удобно. Если вы видите непонятные URL в Sentry, которые вы не прописывали, если видите внедрения или непонятные сообщения — значит, вас пытаются взломать. В моей практике такое встречалось регулярно. Например, одному из проектов — сервису по обходу блокировщиков рекламы и антивирусов — постоянно пытались противодействовать, взломать его.
Для мониторинга также рекомендую использовать Grafana. Важно продумать систему критериев и показателей, которые будут отслеживаться системой. Мы настраивали на трафик, на отдачу рекламы, на степень отрисовки рекламы, на количество баннеров, пришедших от Яндекса и т.п. (проекты в Rambler Group). Нам надо было понимать, как работает Яндекс с нашими запросами, потому что он является сторонним сервисом, а значит его надо мониторить, ведь если он откажет, то весь проект может полностью обрушиться.
Если будете мониторить всё общение со сторонними сервисами, тогда очень быстро найдёте любую ошибку. История из моей практики: мы увидели, что от Яндекса резко перестали приходить ответы по рекламе. Оказалось, что у них технические неисправности и наглухо легла вся рекламная сеть. И это не Яндекс нас проинформировал первым, а мы позвонили им и попросили посмотреть, что творится с их сервисами.
Как лучше всего мониторить? Возьмите какой-то маленький URL, пропишите GET-параметры и отправьте GET-запрос на этот URL. В серверной части обработайте этот URL, запишите лог в базу и поднимите мониторинг на Grafana. Всё просто.
На этом всё. В будущем я постараюсь продолжить писать на тему защиты web-приложений от угроз. Всем, кто дочитал до конца — желаю безопасности вашим проектам)))
Список источников для чтения по теме:
www.owasp.org/index.php/Main_Page
tproger.ru/translations/webapp-security
С. Ястребов. Even Faster Single Page Application: security
Seacord, Robert C. The CERT C secure coding standard / Robert C. Seacord. — 2008
Chetan Karande. Securing Node Applications — 2017
Steven Palmer. Web Application Vulnerabilities Detect, Exploit, Prevent — 2011
Robert Shimonski, Sean-Philip Oriyano. Client-Side Attacks and Defense — 2012
Marcus Pinto, Dafydd Stuttard. The Web Application Hacker's Handbook: Finding and Exploiting Security Flaws, 2nd Edition — 2011
Karl Duuna. Secure Your Node.js Web Application — 2015