Опубликовал посадочный талон — потерял аккаунт: что мы измерили о закрытии QR-кода
Сколько можно вытащить из одного корешка билета
Начну с задокументированного случая, а не с гипотезы.
Журналист по безопасности Брайан Кребс написал об этом разбор в 2015 году. Читатель по имени Кори увидел, что его друг выложил в Facebook фотографию посадочного талона. Он сделал скриншот, увеличил штрихкод и прогнал его через бесплатный онлайн-декодер. Через несколько секунд у него были данные аккаунта часто летающего пассажира этого друга.
Дальше и начинается суть. Кори мог просматривать все будущие рейсы этого аккаунта, менять места уже оформленным пассажирам и отменять любой предстоящий вылет. Хуже того, раскрытых данных хватало, чтобы пройти первую половину процедуры сброса PIN-кода у авиакомпании.
Друг просто радостно выложил фотографию из поездки.
Одну вещь упускают почти все: штрихкоды и QR-коды — не украшение, а контейнеры машиночитаемых данных. Вы видите чёрно-белые квадратики, сканер видит номер брони, имя, рейс, дату и место. Именно этот разрыв и составляет весь риск.
Закрыть половину не работает, и я измерил насколько
Этот раздел мне хотелось написать больше всего, потому что неправильный способ опаснее бездействия: вы считаете себя защищённым и спокойно публикуете.
В QR-код по замыслу встроена мощная коррекция ошибок. Спецификация позволяет сканеру восстановить полное содержимое, даже когда заметная часть символа повреждена, испачкана или закрыта. В физическом мире это полезно: помятый билет или смазанная печать всё равно считываются. Применительно к рассуждению «закрою половину, и хватит» это превращается в уязвимость.
Поэтому я взял и измерил.
Какую площадь на самом деле нужно закрыть
Метод воспроизводим. С помощью библиотеки qrcode для Python я сгенерировал QR-код условного ваучера — содержимое представляет собой ссылку погашения с номером брони и проверочным кодом — на уровнях коррекции M (15 %), Q (25 %) и H (30 %). К каждому применил четыре способа закрытия в семи долях площади, после чего пробовал декодировать через QRCodeDetector из OpenCV. Засчитывалось «всё ещё читается» только при полном восстановлении исходной ссылки.
Результат оказался хуже, чем я ожидал.
| Способ закрытия | Перестаёт читаться (M, 15 %) | Перестаёт читаться (Q, 25 %) | Перестаёт читаться (H, 30 %) |
|---|---|---|---|
| Сплошная заливка по центру | 15 % площади | 20 % площади | 25 % площади |
| Сплошная заливка в правом нижнем углу | 20 % площади | 25 % площади | 25 % площади |
| Горизонтальная полоса | 10 % площади | 20 % площади | 20 % площади |
| Мозаика по центру | 15 % площади | 20 % площади | 25 % площади |
Читать так: указанный процент — та доля, с которой декодирование начинает давать сбой. Всё, что ниже, по-прежнему считывается.
Три наблюдения стоит запомнить.
Первое: билет с высокой коррекцией ошибок читается даже при закрытой пятой части площади. На уровне H при закрытых 20 % центральная заливка, угловая заливка и мозаика — все три способа вернули содержимое. Тот солидный на вид прямоугольник, которым вы замазали код, на деле не закрыл ничего.
Второе: мозаика повела себя ровно так же, как сплошная заливка — одинаковые пороги на всех трёх уровнях. Это опровергает интуицию, что размытие хотя бы лучше, чем ничего. Оно не безопаснее, оно лишь создаёт видимость проделанной работы.
Третье: горизонтальная полоса оказалась самым действенным из четырёх способов. На уровне M хватало одной полосы, закрывающей 10 % высоты. Причина в том, что полоса пересекает всю ширину символа, и повреждения распределяются по нескольким блокам кодовых слов, тогда как компактный квадрат попадает ровно в ту область, где коррекция ошибок сильнее всего.
Скажу честно: третий вывод противоречит тому, который я собирался написать. Предупреждение о том, что «зачёркивание билета не делает его недействительным», верно — речь там о действительности и читаемости данных. Но если вопрос ставится иначе — какой способ зачёркивания лучше останавливает сканер, — эти данные отвечают, что полоса выигрывает у квадрата. Такую разницу узнаёшь только измерением.
Где закрыть важнее, чем сколько закрыть
Самый полезный результат пришёл в последней серии проверок.
Три больших квадрата в углах QR-кода — это поисковые шаблоны (finder patterns), по ним сканер определяет ориентацию и границы. Я закрыл верхний левый, и все три уровня коррекции не смогли декодировать код — при том что эта область занимает всего 2,0–3,6 % площади изображения.
Иначе говоря: закрыть 2 % в правильном месте лучше, чем 20 % в неправильном.
Границы этой проверки тоже назову. Я использовал декодер OpenCV, единственное содержимое и одну версию QR-кода. Коммерческие сканеры и камеры телефонов реализованы иначе и могут восстанавливать больше. Поэтому считайте приведённые пороги нижней границей, а не гарантией: декодер реального злоумышленника, скорее всего, окажется терпимее моей тестовой среды.
На практике остаётся одно правило: закрывайте весь символ непрозрачной заливкой, с заходом за края. Не угол, не пара штрихов, не мозаика. Если инструмент не позволяет, закройте целиком хотя бы один из трёх поисковых углов.
Закрыто небрежно — значит не закрыто.
Три слоя утечки в скриншотах из поездки
Скриншоты, которые появляются в поездке, делятся на три слоя с принципиально разной природой риска.
| Слой | Типичный источник | Что утекает | Худший сценарий |
|---|---|---|---|
| Первый: сканируемый документ | Ваучеры Klook/KKday, билеты на мероприятия, посадочные талоны | Сам документ, номер брони, зацепки к бонусному аккаунту | Билет использован раньше вас, захват аккаунта |
| Второй: текстовые идентификаторы | Подтверждения Ostrovok/Booking.com, номера брони и комнаты | Имя, даты проживания, адрес объекта | Звонки в отель от вашего имени, социальная инженерия |
| Третий: данные внутри изображения | Экран, сфотографированный другим устройством | GPS в EXIF, время съёмки, устройство | Вывод о текущем местоположении или доме |
Первый слой самый срочный. Ценность электронного ваучера — в самом коде; если его отсканируют раньше, вы узнаете об этом уже на месте. Есть задокументированные случаи, когда злоумышленники брали опубликованные билеты на мероприятия, изменяли их и обманывали желающих купить — пострадавшим оказывается не только автор публикации.
Второй слой выглядит безобидно и служит топливом для социальной инженерии. Имени, дат проживания и названия отеля достаточно, чтобы позвонить на стойку от вашего лица и изменить бронь — или наоборот, позвонить вам от имени отеля и попросить «повторно подтвердить карту». В высокий сезон такого мошенничества заметно больше.
Третий слой упускают чаще всего. В скриншотах с телефона GPS обычно нет, но если вы сфотографировали экран вторым устройством, в этом снимке окажутся координаты места съёмки. Это случается чаще, чем кажется, когда нужно переслать кому-то экран компьютера.
Что делать, когда попутчикам действительно нужны детали
Это самый практичный вопрос. Информацией о маршруте приходится делиться: попутчики должны знать время встречи, содержание билетов и адрес жилья.
Порядок приоритетов такой.
Первый вариант — штатная функция обмена на самой площадке. Klook, KKday, Ostrovok и большинство других поддерживают передачу или совместный доступ к брони для попутчика. По официальному пути действительность документа остаётся под контролем площадки, и никакого гуляющего по чатам скриншота не появляется.
Второй вариант — отправить текст, а не картинку. «26.09, 9 утра, главный вход, билеты у меня» содержит всё необходимое при нулевом риске.
Третий вариант — изображение, но с контролем доступа. Если картинка действительно нужна (сложная таблица маршрута, схема), используйте способ с контролем доступа, а не открытую публикацию.
На lpic.cc при загрузке можно задать пароль на просмотр, и короткая ссылка выдерживает пересылку: без пароля её не откроют. Пароли хранятся в виде bcrypt-хеша, а не сравниваются в открытом виде. Для маршрута, который «нужно показать только этим пятерым», это ощутимо управляемее, чем бросить его в общий чат и отпустить в свободное плавание.
Ограничения тоже проговорю заранее: изображение до 8 МБ, файлы хранятся 30 дней с последнего просмотра, загруженное автоматически оптимизируется, поэтому оригинал не сохраняется. И главное — пароль останавливает тех, кому видеть не следует, но не остановит имеющего доступ, который сделает скриншот и перешлёт его дальше. У любого механизма обмена есть этот потолок. Не считайте его всесильным.
Последний рубеж обороны прежний: по-настоящему чувствительный документ вообще не должен превращаться в файл изображения.
Часто задаваемые вопросы
В1: Я выложил посадочный талон давно. Есть ли смысл удалять сейчас?
Удаление снижает дальнейшую видимость, но не доказывает, что никто не сохранил снимок. Важнее другие два действия: немедленно сменить пароль и контрольные вопросы в аккаунте авиакомпании и проверить будущие брони на несанкционированные изменения. В случае Кребса первым, что мог сделать атакующий, была смена мест и отмена рейсов.
В2: Мой ваучер Klook кто-то отсканировал. Смогу ли я всё же пройти?
Зависит от типа билета. Одноразовый ваучер, погашенный раньше, на месте покажет статус «использован». В такой ситуации нужно немедленно обращаться в поддержку площадки, хотя результат не гарантирован, а в высокий сезон ответ приходит медленно. Именно поэтому здесь профилактика надёжнее исправления.
В3: Достаточно ли замазать QR-код мозаикой?
Недостаточно, и я это измерил. На всех трёх уровнях коррекции пороги отказа у мозаики и сплошной заливки совпали: у билета высокого уровня требовалось закрыть 25 % площади, прежде чем он переставал считываться. Используйте непрозрачную заливку по всей области кода, а лучше закройте его физически ещё при съёмке.
В4: В подтверждении только номер брони и имя. Это тоже риск?
Да. Имени и номера брони на некоторых площадках хватает, чтобы проверить статус бронирования, а с датами проживания набор для социальной инженерии становится полным. В реальных случаях повторяется одна и та же схема: атакующему не нужен ваш пароль, ему хватает достаточного количества обрывков, чтобы убедить сотрудника поддержки.
В5: А сами фотографии из поездки?
Риск другой природы. Там главная проблема — координаты GPS в EXIF. Большинство соцсетей удаляет EXIF при загрузке, а вот облачные диски и отправка «оригинальным файлом» обычно сохраняют его полностью. Как поступает каждый сервис, показано в нашей проверке EXIF по полям на 8 хостингах.