Интро
Не так давно я выступал на конференции FrontendConf 2015 (РИТ++) с темой данной статьи. И при подготовке доклада начал искать информацию, а кто вообще выступал на данную тему и что есть в Сети на данный момент.
Оказалось, что информации совсем немного, более-менее можно было бы отметить доклад mikewest.org/2013/09/frontend-security-frontendconf-2013 от Mike West из компании Google, но какой-то «непентестерский» взгляд и уж совсем мало материала. И www.slideshare.net/eoftedal/web-application-security-in-front-end где тема раскрыта более детально, но выступление 2011 года. А за 4 года технологии и атаки на месте не стояли.
Долго и сложно выбирая темы, что же все-таки рассказать разработчикам фронтендов про безопасность, при этом минимум касаясь бекэнда (местами все-таки это неделимо), получился доклад, а здесь — его текстовый пересказ.
О чем вообще разговор?
А действительно, о чем тут вообще можно разговаривать? Говоря про взломы и безопасность невольно приходят в голову тезисы — слили базу, получили доступ к выполнению команд ОС на сервере, прочитали чужую переписку. Но это все — server side код. А что ж может «нагородить» фронтэндер? Главная опасность, конечно же, в обходе атакующим SOP — Same Origin Policy, главной политики безопасности браузеров, которая регулирует работу в разных Origin. Но не только, давайте разбираться.
Межсайтовый скриптинг
Cross Site Scripting
Тема XSS изъезжена как только можно, но обычно речь идет о неправильной обработке параметров на серверной стороне (атакующий может встроить свой JS код в страницу и выполнить его, в том числе в браузере жертвы, от имени уязвимого origin, итог — обход SOP). И выделили там 2 вида XSS атак — stored («злобный» js хранится на сервере) и reflected (отдается сервером без сохранения).
Ну а что с фронтендом то? А здесь другой случай — JS злоумышленника не передается на сервер (за некоторыми исключениями, но дело не в них), а встраивается в DOM за счет легитимного JS кода. Отсюда название для третьего типа атак XSS — DOM XSS.
Типичный пример уязвимого JS кода:
document.write("Site is at: " + document.location.href);
Теперь открыв страницу по адресу
http://victim.com/action#<script>alert('xss')</script>ѕ
JS добавит в дерево значение document.location.href, где вставлен «злобный» js. Как можно уже заметить из данного примера для поиска векторов для данного вида атак нам нужно две вещи: на что-то повлиять в браузере пользователя (sources) и как-то выполнить (sinks).
Примеры «сорсов» (sources):
- document.URL
- location
- document.referrer
- window.name
- localStorage
- cookies
Примеры «синков» (sinks)
- eval
- document.write
- (element).innerHTML
- (element).src
- setTimeout / setInterval
- execScript
Более подробно ознакомиться с данной темой можно по ссылкам — code.google.com/p/domxsswiki (и взять отсюда регулярку для поиска потенциально уязвимых мест) и habrahabr.ru/company/xakep/blog/189210, статья от моего коллеги Алексея Тюрина
А вот пример уязвимого кода (реального) с twitter.com (full story)
(function(g){var a=location.href.split("#!")[1];if(a){g.location=g.HBR=a;}})(window);
Пример DOM XSS на twititer.com
Утечки информации
Часто бывает, что JS/CSS файлы содержат информацию об инфраструктуре системы.
Про javascript
Когда проекты становятся совсем большими и вступают различные среды для приложения (test/dev/prod) начинаются «костыли» и определение, в том числе на клиенте, в каком окружении нужно работать.
Пример такого реального файла, mail.ru — img.imgsmail.ru/r/webagent/release/loader.js
var dl = (''+document.location),
host = document.location.host,
branch = 'master',
path = 'release/467',
base = 'r/webagent/',
probability = [
{"branch": "wa-514", "deprecated":1}
],
lastForcedVersion = '20131126154524',
isLocalhost = dl.indexOf('localhost') != -1,
testServer = host.match(/[^.]+\.((?:f|my\.rapira)\d*)\.mail\.ru/),
devServer = host.match(/^.+\.dev\.mail\.ru$/),
isRapira = testServer && testServer[1].indexOf('my.rapira') == 0,
utf = true,//!!window.IS_UTF,
domainProps = {},
domain = '//img.imgsmail.ru',
login = getUserLogin(),
useBranch = (dl.match(/\Wwa_use_branch=([a-z0-9-]*)/i)||[0,false])[1],
useLang = (dl.match(/\Wwa_lang=([a-z]{2})/i)||[0,false])[1],
useOnce = (dl.match(/\Wbranch=([a-z0-9-]*)/i)||[0,false])[1],
appVersion = (dl.match(/\Wwa_appver=([\.0-9]*)/i)||[0,false])[1],
usedBranch = branch;
Как видно из файла, по регуляркам можно определить домены (и сбрутить валидные?) для тестов и разработки. В данном случае может информация и не очень критичная (если что — mail.ru в курсе), но встречаются порой и такие конструкции:
internalDevHOST = '172.16.22.2';
internalProdHOST = '172.16.22.5';
А иногда — и с внешними IP (хотя внутренние тоже могут быть полезны, при SSRF, например).
CSS
С CSS тоже самое. Проекты растут, разработчики начинают применять различные «сборщики», которые как раз бывают и оставляют «вкусную» информацию, пример — hackerone.com/reports/2221
file\:\/\/\/applications\/hackerone\/releases\/20140221175929\/app\/assets\/stylesheets\/application\/browser-not-supported\.scss
file\:\/\/\/applications\/hackerone\/releases\/20140221175929\/app\/assets\/stylesheets\/application\/modules\/add-category\.scss
file\:\/\/\/applications\/hackerone\/releases\/20140221175929\/app\/assets\/stylesheets\/application\/modules\/alias-preview\.scss
...
MVC фреймворки
Все популярнее становятся различные JS MVC фрейморвки: AngularJS, Knockout, EmberJS и т.д. С ними очень круто, позволяют расширить работу с DOM, создавать новые элементы (), создавать «биндинги» и т.д. Естественно, что-то новое не могло пройти незаметно для мира безопасности.
В частности нас интересуют logic-less темплейты
<ul>
<li ng-repeat="phone in phones">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
Которые позволяют существенно сократить количество «копипаста» и присваивать значения указанным переменным внутри скобок.
Или усов, если скобку повернуть на 90°. Усов! mustache.github.io
Так появился проект mustache security — code.google.com/p/mustache-security, про безопасность MVC фреймворков. Уже есть информация про:
- VueJS
- AngularJS
- CanJS
- Underscore.js
- KnockoutJS
- Ember.js
- Polymer
- Ractive.js
- jQuery
- JsRender
- Kendo UI
Так давайте вернемся, что плохого то может здесь случится? Возьмем AngularJS. Например бывают ситуации, когда атакующий как раз попадает как раз внутрь этих скобок. И из них как-то надо выполнить свой JS, но это по-умолчанию нельзя. Задача — обойти «песочницу».
Пример с AngularJS (1.1.5)
<div class="ng-app">
{{constructor.constructor('alert(1)')()}}
</div>
Вот так, пока еще не очень хитро в версии AngularJS < 1.1.5 можно было выбраться из песочницы и «достучаться» до объекта window (в примере выше — просто вызов alert(1)), всего окна браузера. Google выпустили фикс, и…
AngularJS (1.2.18), после фиксов
{{
(_=''.sub).call.call({}[$='constructor'].getOwnPropertyDescriptor(_.__proto__,$).value,0,'alert(1)')()
}}
Желаемый alert(1) также получен!
Так к чему я. Обновление фрейморков важно не только для функционала, но и для безопасности!
Cookies
Рассматривая данный пункт мы уже начинаем затрагивать немного backend. Но совсем немного.
Как обычно устанавливают cookie на серверной стороне?
Пример на PHP:
<?php
setcookie('foo','bar1');
?>
Пример на Python:
import Cookie
C = Cookie.SimpleCookie()
C["foo"] = "bar"
print C
И в первом, и во втором случае сервер ответит заголовком:
Set-Cookie: foo=bar
Т.е. просто скажет браузеру, установи значение bar ключу foo. Но нужно все обдумать и проверить себя по чеклисту:
- Указать домен для установки cookies. В примере выше IE поставит cookie и для текущего поддомена, и для всех поддоменов. Т.е. если будет найдена XSS на каком-то «левом» поддомене — данные могут скомпрометированы. Подробнее.
- Флаг HttpOnly для сессионных значений (phpsessid, например, по дефолту идет без него). Нужно для того, чтобы браузер запретил доступ к этому значению из JS (при XSS снижает риски хищения аккаунта пользователя)
- Флаг Secure в случае, если сайт работает только по HTTPS и нет никакой нужды отправлять важные значения через небезопасный канал
Переход на HTTPS
В третьем пункте было сказано про HTTPS. А давайте как раз это и обсудим, ведь направлено это как раз на работу с браузером. С т.ч. перехода это обычно вопросы администрирования сервера — выбор сертификата, настройка вебсервера и когда уже готово — перенаправление с HTTP на HTTPS
Как это бывает в жизни. Админ ставит редирект при заходе на сайт по HTTP на HTTPS и все. Но как раз этот момент, пользователь то изначально заходит по HTTP и именно в этот момент можно «дропнуть» редирект на HTTPS и проксировать трафик дальше, так и не дав пользователю HTTPS версию сайта.
Почему люди заходят на сайт также по HTTP? Потому что у всех он в закладках все также по HTTP.
Отвлечемся на кулстори :)
История находится прямо под этой картинкой
Не могу не рассказать про «горячо любимый» VK. Сейчас прошла/идет/набирает обороты волна «угонов» аккаунтов в ВК. Чаще всего это просто взломанные роутеры пользователей (в роутерах уязвимостей хватает) с измененными DNS серверами, на которых адреса ВК — другие, а именно «злобные серверы злобных злоумышленников». Они как раз и пользуются этим трюком, не дают пользователю посетить HTTPS версию сайта, «дропая» редирект, так как у пользователей сайт все еще в закладах по HTTP версии (галка на странице h_ttps://vk.com/settings?act=security все же не принудительный HTTPS).
Так как лечить?
Для этого придумали заголовок HSTS
Strict-Transport-Security: max-age=31536000;
Он сообщает браузеру, что на этот сайт по HTTP больше ни запросом. Указывается время, в течение которого данное правило работает (и опционально можно задать распространение данного правила на поддомены).
А если включить режим параноика и представить ситуацию, что уже при первом посещении сайта трафик перехватывается и пользователь не получит данный заголовок — то можно добавить свой ресурс в HSTS preload list — www.chromium.org/hsts, список ресурсов, на которые браузер всегда будет посещать по HTTPS (т.е. список ресурсов «зашивается» прямо в браузер, расшаривается между браузерами).
Безопасность HTML5
Статья довольно большая, не отчаивайтесь :0 Разговаривая про HTML5 не только в контексте новых элементов, но и новых методов API, он (HTML5) принес много полезного и безопасного, в т.ч. по кроссдоменной работе. А чего только не придумывали, и файлики «proxy.php» в корне сервера, и другие ужасные костыли.
Window.postMessage()
Представим себе, что у нас есть два объекта window (например — текущее окно и window от iframe или после window.open).
Страница на домене abc.com отправляет «секретное» сообщение в другое окно
otherWindow.postMessage(message, targetOrigin);
Страница на домене xyz.com слушает сообщения и проверяет, откуда они пришли
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event)
{
if (event.origin !== "http://example.org:8080")
return;
// ...
}
В целом такой вариант верный — указывать origin, куда отправлять (чтобы не отправить в «окно» атакующему), и принимать, проверяя откуда пришло сообщение. Но в реальности так бывает редко, а чаще нужно такое решение для всего проекта (всех поддоменов)
Уязвимый код!
if(message.orgin.indexOf(".example.com")!=-1)
{
/* ... */
}
И порой можно встретить такой уязвимый код. example.com.attacker.com тоже сматчится (домен атакующего). Так что:
- Проверяем, куда отправляем данные
- Проверяем, откуда их получили
- Перепроверяем свои проверки, чтобы не получилось как выше
HTTP access control (CORS)
И снова про кроссдоменную работу. CORS клевая, модная, безопасная штука в которой сложно накосячить. Очень сложно, но… бывает :)
Позволяет также браузеру сообщить, как ему можно работать в обход SOP. При кроссдоменной работе и первом запросе OPTIONS сервер возвращает что-то типа
Access-Control-Allow-Origin: *
Или
Access-Control-Allow-Origin: example.com
В первом случае сообщается, что можно в обход SOP работать с любого домена, во втором — только с example.com. При этом, можно указывать методы, по которым это доступно и т.д.
Но по дефолту идентификационные данные (например — cookies) браузер все равно не отправляет, что чаще всего и нужно. Нужно добавить заголовок
Access-Control-Allow-Credentials: true
Который сообщит браузеру, что теперь делать кроссдоменные запросы вместе с куками (позволит работать с сессией пользователя). Но! Самое интересное что уже напрашивающийся мисконфиг Allow-Origin: * (любой сайт) + Allow-Credentials вместе не работают. Браузер не будет отправлять cookies и это хорошо (так как в этом случае злоумышленник не сможет выполнять действия от пользователя с сессией). Т.е. данная опция работает только тогда, когда конкретно указаны разрешенные домены.
Про «бывает». Разработчик указывает разрешенные домены из GET параметра (занавес).
Web Sockets
Затрагивая Web Sockets (WS) прежде всего стоит отметить, что для них существует несколько стандартов с довольно значимыми изменениями. Поэтому здесь довольно общие вещи
- В WS нет авторизации (каждый допиливает сам)
- WS — передают данные в незашифрованном виде. Следует использовать WSS для важных данных
- Валидацию данных в них никто не отменял, как на сервере (злоумышленник также может писать свои данные в сокет), так и на клиенте
- Проверка Origin, откуда данные пришли, не отменяет п.1
- ...
Здесь я уже просто сошлюсь на owasp.org, занятое чтиво — www.owasp.org/index.php/HTML5_Security_Cheat_Sheet, хоть и неполное (в добавку про service workers — sirdarckcat.blogspot.ru/2015/05/service-workers-secure-open-redirect.html, sirdarckcat.blogspot.ru/2015/05/service-workers-new-apis-new-vulns-fun.html)
Content Security Policy
В целом то браузер он такой — доверчивый, ему откуда сказали данные загрузить — CSS/JS/картинки, он от туда и загрузил. Полезно и для атакующего, просто подключать свой злобный js файл с другого домена при XSS атаке. Чтобы снизить риски от XSS атак (и не только) придумали Content Security Policy (CSP).
Этот заголовок сообщает браузеру, откуда можно грузить ресурсы (например — js), а откуда нельзя. Соответственно злоумышленник даже имея возможность внедрить js выполнить его не сможет (например, если запрещены inline js + внешние только с доверенных доменов).
Ошибка подгрузки JS файла с домена, которого нет в «белом» списке
Но бывает так, что злоумышленник все-таки может загрузить свой js и выполнить его, например если приложения (к письму, сообщению) находятся на домене из белого списка.
Более подробно CSP разбирался много раз, в том числе на последней встрече OWASP Russia в Москве, презентация — www.slideshare.net/OWASP-Russia/yourprezi-49135416
Flash
Я это серьезно? Про flash? Вообще — да. Для кого-то это актуально, а к многих он исторически где-то еще работает. Да уже в этом году был один масштабный случай с флешем с «ново-старой» багой, про который в рунете так никто не написал :( Но давайте по порядку.
crossdomain.xml
Самое популярное связанное с флэшем — это файл crossdomain.xml. Это xml файл, который указывает флэш приложению политику работы с данным доменом (снова речь про SOP). И здесь ситуация как с CORS не пройдет. Если указано, что любой домен имеет доступ (вместе с куками) — значит любой. И это, конечно, плохо. Пример такого файла:
<cross-domain-policy>
<allow-access-from domain="*" to-ports="80"/>
</cross-domain-policy>
Флэшка с любого домена может обращаться к данному ресурсу (где расположен такой crossdomain) вместе с куками пользователя. Но это самый частый случай, бывает интереснее.
Из опыта багхантинга на wamba.com — у них было множество доменов в этом файле (много раз переезжали и меняли название ресурса). Как итог в файле оказались домены, которые уже давно истекли. Можно было их успешно зарегистрировать и атаковать пользователей wamba.com. Дали 3 тысячи (:
XSS через Flash
XSS возможен и через Flash файлы. Пример уязвимого AS кода
getURL(_root.URI,'_targetFrame');
Пример эксплуатации:
http://victim/file.swf?URI=javascript:evilcode
Флэш попытается перейти по адресу javascript: и выполнить произвольный JS код. SWF легко декомпилится, поэтому исходники (напрямую) не нужны. Почитать более подробно про этот способ атаки и найти список уявизмых функция можно на OWASP — www.owasp.org/index.php/Testing_for_Cross_site_flashing_(OTG-CLIENT-008)
CVE-2011-2461 IS BACK!
Ну а теперь из свеженького про флэш из 2015. Этой весной на Troopers был представлен рабочий PoC и тулзы для анализа, как проэксплуатировать одну старую уязвимость во флэш файлах, собранных на Adobe Flex (уязвимой версии). Это очень популярный фреймворк для флэш разработчиков. Вектор такой — находим на ресурсе такую флэшку (собранную с уязвимым Adobe Flex), генерим для нее специальный пэйлоад (ресурс) и подключаем. Исполняется флэшка с загружаемого домена с нашей полезной нагрузкой. Итог — снова обход SOP.
Для проверки таких файлов (уязвим SWF или нет) можно использовать тулзу — github.com/ikkisoft/ParrotNG, запускается очень просто
java -jar parrotng_v0.2.jar <SWF File | Directory>
Больше информации
- blog.nibblesec.org/2015/03/the-old-is-new-again-cve-2011-2461-is.html
- www.slideshare.net/ikkisoft/the-old-is-new-again-cve20112461-is-back
Авторы исследования нашли такие флэшки и на гугле, и на других известных ресурсах (например Qiwi, Yandex). Своё «срубили» :)
Утечки информации об инфраструктуре
Возможны такие же утечки, как и в случаях с JS/CSS. Серверы, API ключи и т.п.
Раскрытие информации через JSONP
Я уже писал об этом в статье про безопасность API, но стоит повториться, для полноты данной статьи.
Иногда API предоставляется не только для какого-то конечного юзера, а порой и для пересылки данных внутри проекта. Часто это встречается на больших, крупных сайтах с различными доменами. И как-то надо взаимодействовать на стороне клиента между доменами, и тут на помощь приходит JSONP. Это такой JSON с нужными на домене 1, который оборачивается в callback. При обращении на домен 1 юзер отправит свои куки, и можно проверить, авторизован ли он и выдать нужные данные. На втором домене вставляется подобный JS
<script type="application/javascript"
src="http://server1.example.com/api/getUserInfo?jsonp=parseResponse">
</script>
с уже определенной функцией parseResponse. Но суть в том, что злоумышленник может также вставить на своем домене этот скрипт, определить свой callback и, если там sensitive данные — как-то их использовать. Прекрасный пример использования подобной уязвимости продемонстрирован в статье «Сражаясь с анонимностью».
X-Frame-Options
Вроде об этом 1000 и 1 раз сказали, но снова повторим. Каждый ресурс без доп. настроек можно показать во фрейме. Фрейм грузится с куками текущего пользователя, т.е. во фрейме он открывает ресурс авторизованным (если авторизовался, конечно же). Данный фрейм встраивается атакующим на свою страницу, поверх рисуется что либо призывающее к действию (нажать на кнопку и т.п.), юзер кликает, но по факту он кликает на элементы во фрейме (например — подписывается на группы). Атака называется Clickjacking.
X-Frame-Options в ответе веб-сервера позволяет
- Полностью запретить показ страницы во фрейме
- Частично запретить (например, разрешить только для того же origin)
Статья про clickJacking в ВКонтакте
Extensions / SmartTV
Для расширения возможностей браузера есть плагины (например — Flash), а есть расширения. Вторые пишутся с использованием как раз HTML/CSS/JS, о чем и идет речь в данной статье. Приложения для SmartTV пишутся по схожему способу, используя расширенное API телевизоров (например — доступ к камере). И естественно и тут можно накосячить. Про безопасность расширений я уже рассказывал, а безопасность SmarTV приложений (и векторы атак на пользователей SmartTV) мы обсудим позже. Оставлю только видео
На десерт
Ну а если для кого-то из читателей фронтенд это — «Я ПРОСТО ПИШУ CSS И ВСТАВЛЯЮ КАРТИНКИ! РАССКАЖИ МНЕ ПРО БЕЗОПАСНОСТЬ!»… то и вот история.
При переходе (в «типичных» условиях) по ссылке
<a href=“http://external.com”>Go!</a>
Браузер в запросе передаст referer, адрес предыдущей страницы. А что со стилями, картинками и т.п.? Они загружаются передавая referer или нет? Предлагаю прямо сейчас остановиться на чтении и ответить для себя на вопрос.
Ответ
Да, передается
Чем это плохо? На ресурсе hackerone.com верстальщик вставил комикс на страницу восстановления пароля (куда юзер приходит с токеном в GET параметре) с… внешнего ресурса. Как итог — владелец этого ресурса при загрузке данного комикса видел у себя в access.log все токены для сброса паролей. При нужной автоматизации аккаунт можно быстро «угнать».
А казалось бы — «я просто вставляю картинки в html» (:
Презентация с выступления
Only registered users can participate in poll. Log in, please.
История под картинкой найдена…
71.6% С первого раза411
13.24% Со второго-пятого76
15.16% Вообще на найдена!87
574 users voted. 97 users abstained.