PunchWoli Agba, Dele threaten legal action over link to Toluwanisings’ deathDaily MaverickLula’s Brazil election win has given the world breathing spaceThe Jerusalem PostTrump-Netanyahu team suited to removing Iran’s regime, Netanyahu adviser tells ‘Post’Bollywood HungamaYash to handle Karnataka distribution of Ranbir Kapoor starrer Ramayana under Monster Mind CreationsNumeramaAvant l’exploit inédit du Starship, SpaceX fait rugir les moteurs de la fusée géanteInquirerIsabela State U-Echague bans vaping inside campusNMEUche Yara’s body music is defiantly human: “I have no choice but to wear my heart on my sleeve”ZDF heuteAktuelle Pressemitteilungen des ZDFColliderMarvel's New 'Blade' Reboot Is Officially in Troubleynet ספורטבמכבי חיפה יודעים: "ניצחון על הפועל באר-שבע יהווה הצהרת כוונות"Futura SciencesCes 5 méthodes simples pour vérifier la fraîcheur d'un œuf !HipertextualHuawei lanza en España el Watch D3, su reloj que mide la presión arterial con certificación médica
The Daily Newsstand · Free, Always
Monday, October 26, 2026

SSH-ловушка, часть 2: что мы исправили по комментариям и на чём она ещё палилась

Translate

Первую статью про нашу SSH-ловушку прочитали почти двадцать тысяч человек и оставили 25 комментариев. Часть из них оказалась полезнее, чем мы ожидали: один читатель прислал список проверок, на которых ловушка с видеокартами должна проколоться, другой назвал совет про fail2ban вредным, третий пошутил, что старую школу скоро перепишут на Rust. Шутка сбылась через сутки, но об этом в конце.

Ниже что мы сделали за полтора дня после публикации: где ловушка ещё выдавала себя, какую ошибку мы сами допустили и что в неё попало.

Проверка по списку из комментариев

Читатель tech_post_dev предположил, что разведчик не поставил майнер потому, что ловушка отвечает одной и той же таблицей nvidia-smi на любой вызов. Скрипты под майнинг спрашивают иначе, например nvidia-smi --query-gpu=name,memory.total --format=csv,noheader, и разбирают ответ построчно. Ещё он предложил заглянуть в /dev/nvidia*, lsmod | grep nvidia и /proc/driver/nvidia/version: на живом сервере с драйвером всё это непустое и согласовано между собой.

Сам разведчик, как выяснилось, ничего из этого не вызывал. Его интересовали только lspci, число ядер и модель процессора. Но мы прогнали список на ловушке и честно провалили три пункта из пяти.

Проверка

Что отвечала ловушка

Что теперь

nvidia-smi --query-gpu=... --format=csv

всю таблицу целиком, ключи игнорировались

CSV с теми же цифрами, что в таблице, с единицами и без

nvidia-smi -L

список карт

без изменений

ls /dev/nvidia*

No such file or directory

nvidia0-3, nvidiactl, nvidia-uvm и остальные, как символьные устройства

lsmod | grep nvidia

cannot execute binary file: Exec format error

модули nvidia, nvidia_uvm, nvidia_drm, nvidia_modeset

/proc/driver/nvidia/version

версия 550.90.07

без изменений, совпадает с шапкой nvidia-smi

Ответ Exec format error на lsmod особенно обидный. В первой статье мы рассказывали, что победили эту улику, разложив готовые ответы по путям Debian 12. Как видно, победили не везде.

Звёздочка, которая не раскрывалась

Проверка /dev/nvidia* вскрыла проблему крупнее видеокарт. Мы добавили устройства в файловую систему ловушки, а ls /dev/nvidia* всё равно отвечал, что ничего нет. При этом ls /dev/nvidia0 находил файл.

Оказалось, что в Cowrie 3.1.3 подстановки путей нет вообще. Об этом честно написано в комментарии к разбору команд: раскрытие *, ? и [...] оставлено на усмотрение отдельных команд. А команды его не делают. Получается, что любая строка со звёздочкой приходила в команду буквально. Ботнет из первой статьи в каждой сессии выполняет rm -rf /var/tmp/dota*, и у нас эта команда искала файл, который так и называется, со звёздочкой в имени.

Мы дописали подстановку в то место, где ещё видно, какие части слова стояли в кавычках, потому что в bash "*" в кавычках не раскрывается. Если совпадений нет, слово остаётся как было, тоже как в bash.

$ echo "/dev/nvidia*"
/dev/nvidia*
$ for f in /dev/nvidia[0-9]; do echo "card $f"; done
card /dev/nvidia0
card /dev/nvidia1
card /dev/nvidia2
card /dev/nvidia3

Заодно пришлось переделать ls. Когда мы впервые увидели вывод ls -l /dev/nvidia*, там было сразу несколько улик. Дата стояла в виде 2026-10-24 16:52, хотя GNU ls по умолчанию пишет Oct 24 16:52, а у файлов старше полугода вместо времени ставит год. У каталогов не было строки total. Устройства показывались как пустые обычные файлы, а не как crw-rw-rw- 1 root root 195, 0. Ещё одна мелочь, которую видит любой скрипт: без терминала GNU ls выводит по одному имени в строке, а Cowrie выстраивал колонки с пробелами в конце строк.

Боты, которые проверяют оболочку

После публикации пришли боты, которых в первые полсуток не было. Сразу после входа они отправляют пачку команд такого вида:

printf '\n%s|%s\n' 'check_..._small_math' "$((746*496+89))"
printf '\n%s|%s\n' 'check_..._small_pid' "$$"
set -- 'arg1' 'arg2'; printf '%s\n' "$#|$1|$2"

Каждая проверяет одну возможность настоящего sh: арифметику, номер процесса, позиционные параметры, функции, чтение /proc/self/stat встроенной командой read, подоболочку, которая не должна менять переменную снаружи, конвейер в цикл while read. Cowrie не умела почти ничего из этого. Арифметику $((...)) она возвращала буквально, $$ пустым, а read такой команды у неё не было вовсе. Ответ, где вместо 370105 написано $((746*496+89)), бот распознаёт мгновенно.

Чинили это вдвоём, и по дороге всплыла история, которой мы гордимся меньше всего. Утилита createfs, которой Cowrie снимает слепок файловой системы, потеряла все символьные ссылки, 3 598 штук. Она проверяет, что ссылка не выходит за пределы снимаемого дерева, а относительные пути этой проверки не проходили. Поэтому не было ни /usr/bin/which, ни /etc/os-release, ни /usr/bin/python3, а /bin/./uname отвечал «No such file or directory». В первой статье мы писали про «эталонный Debian 12». Эталоном он был до момента съёмки слепка.

Самый дотошный из этих ботов к утру прошёл все проверки и запустил разведку целиком: процессор, видеокарты, last и создание маленького скрипта, который должен напечатать xxxxxx. Всё сошлось, кроме одного. Скрипт искал карты и через lspci, и через busybox lspci, а наша эмуляция busybox, оставшаяся с баннером от Debian 7, отдавала полный вывод lspci. Каждая H100 приходила к нему дважды. В эталонном Debian 12 busybox не установлен, поэтому эмуляцию мы убрали, и теперь команда отвечает command not found, как на настоящем сервере. Похоже, именно это задвоение и смутило разведчика из первой статьи. Мы тогда сами написали в комментариях, что карты задвоены, но не проверили, откуда это берётся.

Обрыв на пятой минуте

Тут уже наша собственная ошибка. В комментариях Folist предложил автоматически отправлять жалобы на атакующих, и мы это сделали: адреса уходят в AbuseIPDB, скачанные ботами файлы в MalwareBazaar, базу образцов от abuse.ch. Через несколько часов на второй, тестовой ловушке заметили, что два отправленных архива из набора Outlaw битые. Они на четверть и на треть короче настоящего, и gzip их не распаковывает.

Причину мы нашли только сегодня. В журнале сессии с обрезанными загрузками заканчивались ровно через пять минут после начала: 02:33:11 вход, 02:38:12 обрыв, 05:02:33 вход, 05:07:35 обрыв. В Cowrie есть interactive_timeout, по умолчанию 300 секунд, и это не таймаут простоя, а жёсткий предел от входа: при активности он не сбрасывается. Outlaw заливает свой архив по SCP медленно, за четыре-пять минут, и половина заливок за ночь, 11 из 22, не успевала. Cowrie сохраняла обрывок, а наш отправщик нёс его в MalwareBazaar как образец.

Отправщик мы научили проверять целостность: битый gzip, ELF, у которого таблица секций лежит за концом файла, и файл ровно на пределе размера больше не отправляются. Предел сессии подняли до получаса. Простой без ввода по-прежнему режется через три минуты.

Если ставите Cowrie. Проверьте interactive_timeout в секции [honeypot]. По умолчанию он обрывает любую сессию через пять минут после входа, даже если внутри идёт загрузка. Если собираете образцы, поднимите его, иначе часть файлов будет обрывками, и вы не заметите этого, пока не попробуете их распаковать.

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

Про fail2ban и PerSourcePenalties

Sap_ru назвал совет поставить fail2ban вредным и предложил вместо него встроенную в OpenSSH защиту PerSourcePenalties. Он прав в главном: эта опция появилась в OpenSSH 9.8 и включена по умолчанию. sshd сам начисляет штраф адресу, который раз за разом не проходит аутентификацию или подключается и не доходит до входа, и пока штраф действует, соединения с этого адреса отклоняются. Повторным нарушителям штраф растёт.

Нюанс в версиях. В Debian 12 штатный OpenSSH 9.2, PerSourcePenalties в нём нет. В Debian 13 уже 10.0, и там защита работает из коробки (данные Debian). Проверить свою версию можно так:

sshd -V
sshd -T | grep -i persource

Если вторая команда что-то вывела, PerSourcePenalties у вас есть, и fail2ban для SSH становится скорее делом вкуса. Если нет, fail2ban по-прежнему полезен. А главная защита от всего, что мы видим в ловушке, не изменилась: вход по паролю выключен.

Пару ответов на остальное. Высокоинтерактивная ловушка на настоящей машине, о которой писали в комментариях, лучше подходит для разбора конкретного вредоноса, но там надо резать исходящий трафик, и бот это сразу видит. У Cowrie есть режим прокси, о котором напомнил KivApple: она пропускает сессию на настоящую машину и записывает всё, что проходит. Мы пока остались на эмуляции, потому что нам интересен сценарий бота, а не то, что останется на диске после него. И да, ограничивать вход по SSH своим адресом опасно ровно так, как написал MountainGoat: провайдер выдаёт другой адрес, и порт 22 молчит, а сайты на том же сервере работают. На этот случай у домашнего сервера есть монитор с клавиатурой, а у VPS консоль VNC в панели хостера.

Шутка про Rust

Под фразой про IRC-бэкдор в наборе Outlaw читатель poige написал: «Ничё, скоро на Rust перепишут». Ночью в ловушку зашёл бот, прочитал /bin/echo, чтобы узнать архитектуру по заголовку файла, и попробовал загрузить себе программу тремя способами подряд: через curl, через wget и через /dev/tcp, если ни того ни другого нет. Третьей попыткой он начал заливать бинарник прямо в оболочку командой head -c с точным размером. Cowrie этот поток не приняла, и у нас сохранилось три байта.

Файл мы забрали сами по той же ссылке и разобрали, не запуская. Это оказался P2Pinfect, червь на Rust, который описывала Cado Security, сейчас она часть Darktrace. Он распространяется через Redis и SSH и берёт цели прямо с захваченной машины: из .bash_history, .ssh/config и known_hosts. Упакован UPX, но три метки UPX! в файле заменены на 0000, поэтому стандартный upx -d отвечает, что файл не упакован. После возврата меток он распаковался штатно. Упакованная версия в MalwareBazaar уже была, распакованной не было, её мы туда и отправили.

Так что переписали, poige. Причём давно.

Цифры за полтора дня

Запись с 16:20 UTC 24 октября до 06:11 UTC 26 октября, без наших проверочных входов.

Что

Сколько

Подключений

6 312

Разных адресов

308

Успешных входов

350 с 149 адресов

Сессий с командами

179 с 136 адресов

Из них сценарий Outlaw

122 сессии со 120 адресов

Команд

3 193

Разных скачанных файлов

146

Смен пароля внутри ловушки

84

Outlaw по-прежнему делает почти всю работу, и его сеть оказалась шире, чем казалось в первую ночь: 120 разных адресов за полтора дня. Пароли те же: 123456, 123, 1234 и две его фирменные пары с 345gs5662d34. Клиенты те же: 4 917 подключений от программ на Go, 812 от libssh 0.9.6, восемь от PuTTY.

Короткий чек-лист

  • sshd -T | grep -i persource: есть ли у вас PerSourcePenalties

  • Вход по паролю выключен, по ключу проверен из второго окна

  • Если держите Cowrie, поднят interactive_timeout

  • Образцы перед отправкой куда-либо проверены на целостность

  • В authorized_keys нет чужих ключей, в cron нет чужих заданий

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.