SUID-файл, которого не должно быть: как проверить систему
Злоумышленник, получивший на сервере доступ обычного пользователя, редко останавливается на этом — ему нужен способ вернуться с правами root даже после того, как вы закроете дыру, через которую он вошёл. Один из самых старых и до обидного простых приёмов — скопировать /bin/bash куда-нибудь в сторону и выставить на копию бит SUID: теперь любой, кто знает про файл, получает root-шелл одной командой, без пароля и без записи в auth.log. Ниже — как систематически искать такие файлы, отличать их от легитимных SUID-бинарников дистрибутива и встроить проверку в регулярную рутину.
Содержание
Что даёт SUID и почему это удобная лазейка
Бит SUID (set user ID) на исполняемом файле заставляет ядро при запуске (execve()) выставить эффективный UID процесса равным владельцу файла, а не тому, кто его запустил. Если файл принадлежит root, то любой пользователь, у которого есть право на выполнение, при запуске такого файла на время получает права root — именно так работают passwd, sudo, su, mount, ping в некоторых сборках. Подробнее о механизме на уровне ядра — в статье про сам бит setuid.
Для атакующего это идеальный бэкдор по нескольким причинам:
- он не требует пароля root и не оставляет следа в
sudo-логах — командаsudoвообще не участвует; - он переживает смену паролей всех пользователей и даже смену пароля root;
- обнаруживается только целенаправленным поиском — стандартный
ps,top, антивирус на него не смотрят; - достаточно одной команды на закрепление:
cp /bin/bash /usr/lib/.hidden/.sh; chmod 4755 /usr/lib/.hidden/.sh— и путь к рутовому шеллу готов на будущее.
Похожий, но менее очевидный вариант — бит SGID (set group ID), который аналогично подменяет эффективную группу процесса. SGID-бэкдор менее эффектен (нужна ещё и подходящая группа), но встречается, когда атакующий метит не в root, а в группу с доступом к конфигам, ключам или логам.
Как найти все SUID и SGID файлы в системе
Базовый инструмент — find с флагом -perm, который умеет проверять конкретные биты прав через маску:
# все файлы с битом SUID во всей файловой системе
sudo find / -xdev -type f -perm -4000 2>/dev/null
# все файлы с битом SGID
sudo find / -xdev -type f -perm -2000 2>/dev/null
# оба бита сразу, с деталями владельца и прав
sudo find / -xdev -type f \( -perm -4000 -o -perm -2000 \) -ls 2>/dev/null
Разберём флаги, потому что от них зависит, что именно вы увидите:
-perm -4000— «хотя бы этот бит установлен» (маска, а не точное совпадение); без минусаfindтребовал бы точного совпадения всех битов прав, что почти никогда не нужно;-xdev— не уходить за пределы файловой системы, на которой начат поиск. Критично: без негоfind /полезет в примонтированные NFS-шары,/proc, смонтированные образы и бэкапы, и поиск займёт часы, а результат будет засорён нерелевантными путями. Если на сервере несколько дисков и точек монтирования, которые тоже нужно проверить, гоняйтеfindотдельно на каждой с явным путём;2>/dev/null— глушитPermission deniedот каталогов вроде/proc/[pid]/...или чужих домашних директорий, в которые не может заглянуть даже root на некоторых FUSE-монтировках;-type f— не путать с symlink на SUID-файл; сам симлинк бита не несёт, но стоит проверить, куда он ведёт.
Разовый запуск отработает секунды-минуты в зависимости от размера диска. На типичной VPS с одним диском полный обход / укладывается в 10-30 секунд.
Если нужен вывод, удобный для сравнения между запусками — отсортированный и с хешами:
sudo find / -xdev -type f \( -perm -4000 -o -perm -2000 \) \
-printf '%m %u:%g %p\n' 2>/dev/null | sort -k3 > /root/suid-audit-$(date +%F).txt
%m — права в восьмеричном виде, %u:%g — владелец и группа, %p — путь. Такой файл легко положить рядом с предыдущим прогоном и посмотреть diff.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверКак сравнить с ожидаемым списком для дистрибутива
Голый список путей мало о чём говорит, пока не с чем сравнить. Есть три практичных способа получить «эталон».
1. Спросить у пакетного менеджера, какие файлы вообще должны быть SUID. На Debian/Ubuntu это делается через список файлов пакета и их заявленные права:
# какому пакету принадлежит файл
dpkg -S /usr/bin/passwd
# проверить, что реальные права файла совпадают с тем, что задекларировал пакет
dpkg --verify passwd 2>&1 | grep -v '^..5......'
На RHEL/AlmaLinux аналог — rpm -V для всей системы разом:
# проверка всех установленных пакетов на расхождение прав, владельца, содержимого
rpm -Va | grep '^.M'
Буква M в выводе rpm -Va означает именно расхождение в правах доступа (mode) — то, что нас интересует в первую очередь при поиске подмены SUID.
2. Сверить с собственным baseline, снятым на чистой системе. Сразу после установки ОС и до вывода сервера в продакшн снимите список SUID/SGID файлов командой из предыдущего раздела и сохраните его отдельно — это ваш эталон для конкретной версии дистрибутива и набора установленных пакетов:
sudo find / -xdev -type f \( -perm -4000 -o -perm -2000 \) -printf '%p\n' \
2>/dev/null | sort > /root/suid-baseline.txt
Каждую последующую проверку сравнивайте с этим файлом:
sudo find / -xdev -type f \( -perm -4000 -o -perm -2000 \) -printf '%p\n' \
2>/dev/null | sort > /tmp/suid-current.txt
diff /root/suid-baseline.txt /tmp/suid-current.txt
Строки со знаком > — новые SUID-файлы, которых не было в baseline. Именно на них смотреть в первую очередь. Обновляйте baseline осознанно — после каждого apt upgrade/dnf upgrade, который мог добавить легитимные SUID-бинарники (например, обновление sudo иногда меняет права на промежуточные файлы пакета).
3. Готовые списки для сверки. Инструменты аудита вроде Lynis держат встроенные проверки на SUID/SGID и умеют сверять найденное с известными наборами для популярных дистрибутивов — это не заменяет собственный baseline, но полезно как второе мнение. Мы разбирали его настройку в статье про установку Lynis на VPS, а типичные грабли при работе с находками — в разборе частых ошибок Lynis.
На что обращать внимание при просмотре списка
Даже без diff с baseline опытный взгляд на список SUID-файлов быстро отсекает подозрительное. Красные флаги:
- Путь за пределами стандартных бинарных каталогов. Легитимные SUID-файлы почти всегда лежат в
/usr/bin,/usr/sbin,/bin,/sbin, иногда/usr/lib/<пакет>/. Файл с SUID в/tmp,/var/tmp,/dev/shm, домашней директории пользователя,/opt/непонятно-чтоили в скрытом каталоге вроде/usr/lib/.X11-unix-cache— почти всегда повод для тревоги; - Оболочки и интерпретаторы с SUID.
bash,sh,zsh,python3,perl,php-cgiс выставленным SUID — это готовый root-шелл, и таких файлов в чистой системе быть не должно вообще. Единственное легитимное исключение — некоторые системы намеренно используют SUID-обёртки для конкретных узких задач, но это никогда не голая копия шелла; - Свежая дата изменения, не совпадающая с датой установки пакета.
stat /path/to/fileпокажетModify/Change; если файл из системного пакета, но изменён позже даты его установки (dpkg -l/rpm -q --queryformat), это может значить, что бинарник подменили после установки; - Владелец не root у файла с эффектом root. Обычно SUID имеет смысл именно для файлов, принадлежащих root — если владелец другой пользователь, а бит всё равно даёт заметное повышение прав в контексте задачи (например, доступ к группе), стоит разобраться, зачем;
- Дублирующиеся имена известных утилит в нестандартных местах. Файл
/usr/local/bin/passwdили/usr/bin/.passwdрядом с настоящим/usr/bin/passwd— классический трюк с маскировкой под системную утилиту; - SUID на файлах, которые сам процесс установки никогда не помечает так. Например,
find,vim,less,cp,tar,awk— в норме без SUID. Если видите такие в выводе — почти гарантированно бэкдор или ошибка конфигурации (например, скрипт установки по недосмотру далchmod 4755вместо0755).
Полезно сразу проверить содержимое подозрительного бинарника без его выполнения:
file /path/to/suspicious-binary
sha256sum /path/to/suspicious-binary
# сравнить хеш с оригиналом из пакета, если он известен
Если file показывает обычный ELF-бинарник с именем bash, а хеш не совпадает с хешем настоящего /bin/bash — это либо подмена, либо просто копия с других прав. В любом случае трогать chmod/удалять стоит только после того, как вы поняли контекст: если сервер, возможно, скомпрометирован, лучше сохранить копию файла и метаданные (владелец, время, stat) для разбора инцидента, прежде чем всё вычищать.
Автоматизация: cron-скрипт и файловый мониторинг
Разовая проверка руками полезна один раз, но реальную защиту даёт регулярность. Минимальный вариант — cron-задача, которая гоняет find и присылает diff с baseline:
#!/bin/bash
# /usr/local/bin/suid-check.sh
BASELINE=/root/suid-baseline.txt
CURRENT=$(mktemp)
MAIL_TO="admin@example.com"
find / -xdev -type f \( -perm -4000 -o -perm -2000 \) -printf '%p\n' \
2>/dev/null | sort > "$CURRENT"
DIFF=$(diff "$BASELINE" "$CURRENT")
if [ -n "$DIFF" ]; then
echo "$DIFF" | mail -s "SUID/SGID изменения на $(hostname)" "$MAIL_TO"
fi
rm -f "$CURRENT"
# /etc/cron.d/suid-check — ежедневно в 03:15
15 3 * * * root /usr/local/bin/suid-check.sh
Более надёжный вариант — не полагаться на периодический опрос, а ловить событие изменения в реальном времени через auditd. Правило на файловую систему целиком, конечно, избыточно, но можно повесить наблюдение на конкретные каталоги или отслеживать сам системный вызов chmod/fchmodat:
# добавить в /etc/audit/rules.d/suid-watch.rules
-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid!=-1 -k suid_change
После augenrules --load любой вызов chmod от имени залогиненного пользователя попадёт в аудит-лог с ключом suid_change, и его можно найти через ausearch -k suid_change. Настройку auditd с нуля и типичные проблемы (переполнение буфера, потеря событий при нагрузке) мы разбирали в статье про настройку audit-логов через auditd — хотя в контексте этой статьи важно, что сам факт chmod +s попадёт в лог, даже если сам файл потом переименуют или спрячут.
Ещё один вариант — file integrity monitoring (AIDE, Tripwire, auditd в связке с правилами на конкретные бинарники): такие инструменты не просто ловят факт установки SUID, а следят за неизменностью самих системных файлов, что закрывает и соседний сценарий — подмену содержимого легитимного SUID-бинарника без изменения прав.
Регулярность проверки и где она встраивается в процесс
Разовая находка ничего не гарантирует — если проверку не повторять, следующий бэкдор проживёт до случайного обнаружения. Практичный график:
| Когда | Что делать |
|---|---|
| При вводе сервера в продакшн | Снять baseline, сохранить отдельно от сервера (в git-репозитории конфигов, в бэкапе) |
| Ежедневно, автоматически | Cron-скрипт с diff против baseline, алерт на любое расхождение |
| После каждого крупного обновления пакетов | Вручную сверить diff, обновить baseline осознанно (не автоматически!) |
| При подозрении на компрометацию | Полный ручной прогон с анализом каждого нового файла, до всех остальных шагов реагирования |
| Раз в квартал | Полный аудит безопасности сервера целиком, SUID — один из пунктов чек-листа |
Важный нюанс: baseline нельзя обновлять автоматически тем же скриптом, который делает проверку — иначе сам механизм проверки станет бесполезным (атакующий поставит бэкдор, а следующий же прогон молча впишет его в новый эталон как «норму»). Обновление baseline — это отдельное осознанное действие человека после того, как он убедился, что новые файлы легитимны.
Проверку SUID/SGID стоит включить в общий процесс, а не держать изолированно. Она логично встаёт рядом с остальными пунктами базового харденинга сервера — см. чек-лист безопасности нового сервера — и с процедурой подготовки к внешнему аудиту, где такой лог часто прямо запрашивают как доказательство контроля: подробнее в статье как подготовить сервер к аудиту безопасности.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли просто снять SUID со всех файлов «на всякий случай»?
Нет — сломаете легитимные механизмы: passwd перестанет менять пароли, ping может перестать создавать raw-сокеты, mount/umount для обычных пользователей откажут. Снимайте бит только у конкретных подтверждённо лишних файлов, а не массово.
Почему find / иногда зависает или работает очень долго?
Обычно из-за отсутствия -xdev — поиск уходит в сетевые файловые системы, /proc, смонтированные образы бэкапов. Добавьте -xdev и, если есть отдельные точки монтирования, которые тоже нужно проверить, гоняйте find на них отдельно с явным путём.
Есть ли в Docker-контейнерах смысл в этой проверке?
Да, причём часто даже более критичный — образ с лишним SUID-бинарником тиражируется на все контейнеры сразу. Проверку стоит запускать на этапе сборки образа (в CI) через тот же find внутри временного контейнера, до публикации образа в реестр.
SGID на каталоге — это то же самое, что SGID на файле?
Нет, разный эффект. SGID на исполняемом файле работает как SUID, но для группы. SGID на каталоге — это правило наследования: новые файлы, созданные внутри, получают группу каталога, а не основную группу создавшего их пользователя. Второе безопасно и часто используется намеренно (общие каталоги для команды), путать их при анализе не стоит.
Что делать, если найден подозрительный SUID-файл на проде прямо сейчас?
Не удаляйте и не запускайте его. Сначала stat, sha256sum, file, копия файла и его метаданных в отдельное безопасное место, проверка, кто и когда его создал (auditd, ausearch, история bash, логи авторизации). Только после того как собраны улики — снимайте бит chmod -s или удаляйте, и параллельно ищите, как злоумышленник вообще получил возможность его создать: сам SUID-файл — это следствие, а не причина компрометации.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →