Перейти к содержимому

Аудит sudo и root-команд

Скомпрометированный сервер выдаёт себя не сразу. Один из самых надёжных маркеров — кто и какие команды запускает через sudo. Если внезапно sudo запускает пользователь, которого нет в списке админов, идёт прямой вход под root или сыплются ошибки аутентификации — узнать об этом надо в ту же секунду, а не из post-mortem.

Этот сценарий про эскалацию привилегий и отличается от уведомлений о SSH-входах: там мы ловим сам факт входа, здесь — что человек делает под sudo/root уже внутри.

Поднимаем долгоживущий watcher (systemd-сервис с Restart=always), который читает журнал в реальном времени и реагирует на строки sudo::

  • sudo от неожиданного пользователя (не из allowlist alice, bob) — 🚨 высокий приоритет (9);
  • ошибка аутентификации sudo (authentication failure, NOT in sudoers) — ⚠️ высокий приоритет (8);
  • прямой вход/сессия под root — 🚨 высокий приоритет (9);
  • ожидаемый админ запустил sudo — 🔑 тихий/низкий приоритет (3) для дайджеста (можно вовсе отключить).

В сообщение кладём вызвавшего пользователя, TTY и саму команду (COMMAND=).

На Debian/Ubuntu записи sudo идут в /var/log/auth.log, на systemd/RHEL — в журнал (journalctl) и в /var/log/secure. Watcher универсален: если есть journalctl — читаем его, иначе tail -F по auth.log.

Сохраните как /usr/local/bin/notifly-sudo-audit:

#!/usr/bin/env bash
set -eu
set -a; source /etc/notifly.env; set +a
HOST=$(hostname -s)
# Кто из пользователей имеет право повышать привилегии.
# Всё, что вне этого списка, — повод для громкого алерта.
ALLOWLIST="alice bob"
# Читаем журнал sudo в реальном времени. journalctl (systemd) или auth.log (Debian).
tail_source() {
if command -v journalctl >/dev/null 2>&1; then
# SYSLOG_IDENTIFIER=sudo покрывает и systemd, и RHEL (/var/log/secure)
journalctl SYSLOG_IDENTIFIER=sudo -f -o cat --since now
elif [ -f /var/log/auth.log ]; then
tail -F -n0 /var/log/auth.log
else
tail -F -n0 /var/log/secure
fi
}
in_allowlist() {
for u in $ALLOWLIST; do
[ "$1" = "$u" ] && return 0
done
return 1
}
tail_source | while read -r line; do
# Нас интересуют только строки сессий sudo
echo "$line" | grep -q "sudo:" || continue
# Ошибка аутентификации / нет в sudoers — самое подозрительное
if echo "$line" | grep -qiE "authentication failure|NOT in sudoers|incorrect password"; then
USER=$(echo "$line" | grep -oP 'sudo:\s*\K\S+' | head -1)
TTY=$(echo "$line" | grep -oP 'TTY=\K\S+' | head -1)
/usr/local/bin/notifly-send \
"⚠️ Ошибка sudo: ${USER:-?}@$HOST" \
"Пользователь: ${USER:-?}
TTY: ${TTY:-?}
Хост: $HOST
Строка: $line" 8 || true
continue
fi
# Обрабатываем только строки с реально запущенной командой
echo "$line" | grep -q "COMMAND=" || continue
USER=$(echo "$line" | grep -oP 'sudo:\s*\K\S+' | head -1)
TTY=$(echo "$line" | grep -oP 'TTY=\K\S+' | head -1)
RUNAS=$(echo "$line" | grep -oP 'USER=\K\S+' | head -1)
COMMAND=$(echo "$line" | grep -oP 'COMMAND=\K.*' | head -1)
# Прямая эскалация до root — всегда громко
if [ "${RUNAS:-root}" = "root" ] && ! in_allowlist "${USER:-?}"; then
/usr/local/bin/notifly-send \
"🚨 Неизвестный sudo→root: ${USER:-?}@$HOST" \
"Пользователь: ${USER:-?} (нет в allowlist)
TTY: ${TTY:-?}
Команда: ${COMMAND:-?}
Хост: $HOST" 9 || true
elif ! in_allowlist "${USER:-?}"; then
# sudo от кого-то вне списка админов
/usr/local/bin/notifly-send \
"🚨 Чужой sudo: ${USER:-?}@$HOST" \
"Пользователь: ${USER:-?} (нет в allowlist)
Как: ${RUNAS:-?}
TTY: ${TTY:-?}
Команда: ${COMMAND:-?}" 9 || true
else
# Ожидаемый админ — тихий дайджест (при желании закомментируйте)
/usr/local/bin/notifly-send \
"🔑 sudo: ${USER:-?}@$HOST" \
"Команда: ${COMMAND:-?}
TTY: ${TTY:-?}" 3 || true
fi
done

Сделайте исполняемым:

Окно терминала
sudo chmod +x /usr/local/bin/notifly-sudo-audit

Чтобы watcher жил постоянно и сам поднимался после перезагрузки и падений, оформляем его сервисом с Restart=always.

/etc/systemd/system/notifly-sudo-audit.service:

[Unit]
Description=Notify about suspicious sudo/root activity
After=network-online.target
[Service]
ExecStart=/usr/local/bin/notifly-sudo-audit
Restart=always
RestartSec=10s
[Install]
WantedBy=multi-user.target

Активируем:

Окно терминала
sudo systemctl daemon-reload
sudo systemctl enable --now notifly-sudo-audit

Проверка:

Окно терминала
sudo -k
sudo id # ожидаемый админ — придёт тихое 🔑
sudo -u nobody id # чужой пользователь — прилетит 🚨

Прямой root-вход (например, через консоль или su -) виден по сессиям PAM. Добавьте вторую подписку в watcher или отдельный pam_exec в SSH-сценарии, где USER=root уже помечается приоритетом 9. Идеальная политика — PermitRootLogin no в sshd плюс алерт на любую строку session opened for user root в журнале.

Аналог для Windows-серверов: следим за Security Event Log через подписку в Task Scheduler. Нужные коды:

  • 4672 — специальным привилегиям назначен вход (т.е. вошёл администратор);
  • 4720 — создана новая учётная запись пользователя.

Оба события — классические маркеры эскалации привилегий и «тихого» создания бэкдор-аккаунта. Используем общую функцию Send-Notifly.

C:\scripts\Notifly-Privilege.ps1
param([int]$EventId = 4672)
. C:\scripts\Notifly.ps1
# Кто ожидаемо имеет админ-права. Всё вне списка — повод для алерта.
$AllowList = @("alice", "bob", "Administrator")
# Берём последнее событие из Security Log
$ev = Get-WinEvent -FilterHashtable @{LogName='Security'; Id=$EventId} -MaxEvents 1
if (-not $ev) { return }
# Разбираем EventData
$xml = [xml]$ev.ToXml()
$data = @{}
$xml.Event.EventData.Data | ForEach-Object { $data[$_.Name] = $_.'#text' }
if ($EventId -eq 4672) {
$user = $data.SubjectUserName
# Игнорируем системные учётки и компьютерные аккаунты
if ($user -in @("SYSTEM", "LOCAL SERVICE", "NETWORK SERVICE")) { return }
if ($user -like "*$") { return }
$prio = if ($user -in $AllowList) { 3 } else { 9 }
$icon = if ($user -in $AllowList) { "🔑 Админ-вход" } else { "🚨 Чужой админ-вход" }
Send-Notifly `
-Title "$icon: $user@$env:COMPUTERNAME" `
-Message "Пользователь: $user`nДомен: $($data.SubjectDomainName)`nСервер: $env:COMPUTERNAME`nВремя: $($ev.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss'))" `
-Priority $prio
}
else {
# 4720 — создан новый пользователь (всегда громко)
$newUser = $data.TargetUserName
$by = $data.SubjectUserName
Send-Notifly `
-Title "🚨 Создан пользователь: $newUser на $env:COMPUTERNAME" `
-Message "Новый аккаунт: $newUser`nКем создан: $by`nСервер: $env:COMPUTERNAME`nВремя: $($ev.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss'))" `
-Priority 9
}
Окно терминала
# 4672 — назначены специальные привилегии (админ-вход)
$xml = @"
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">
*[System[EventID=4672]]
</Select>
</Query>
</QueryList>
"@
$Trigger = New-ScheduledTaskTrigger -AtStartup
$Trigger.Subscription = $xml
$Action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File C:\scripts\Notifly-Privilege.ps1 -EventId 4672"
Register-ScheduledTask -TaskName "Notifly Privilege Watch" -Trigger $Trigger -Action $Action `
-Principal (New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest) `
-Description "Notifly: аудит админ-входов"
# 4720 — создана новая учётная запись
$xml2 = $xml -replace '4672', '4720'
$T2 = New-ScheduledTaskTrigger -AtStartup
$T2.Subscription = $xml2
$A2 = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File C:\scripts\Notifly-Privilege.ps1 -EventId 4720"
Register-ScheduledTask -TaskName "Notifly New Account Watch" -Trigger $T2 -Action $A2 `
-Principal (New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest) `
-Description "Notifly: аудит новых учёток"
  • Видно любое повышение привилегий. Чужой sudo или прямой root — это ровно тот момент, когда атака превращается в захват машины.
  • Ошибки аутентификации — ранний сигнал. NOT in sudoers часто означает, что кто-то уже сидит под чужой учёткой и пробует расширить права.
  • Allowlist гасит шум. Пока команды запускают только alice и bob, вам приходят тихие 🔑 (или ничего), а громкие 🚨 звучат только на аномалиях.
  • Отправлять полный контекст (last, who, окружение) через поле extras, чтобы разбирать инцидент прямо с телефона.
  • Приоритеты по priority: ожидаемый админ — 3, чужой sudo и ошибки — 8–9, создание пользователя root/wheel — 10.
  • Связать с уведомлением о срабатывании Fail2ban — bruteforce снаружи и подозрительный sudo внутри часто идут в паре.