Подробные статьи о технологиях, определяющих будущее.

Эволюция sudo: от Unix su до современных привилегий

Путь sudo от команды su в Unix до doas, polkit и run0: эволюция, подводные камни безопасности и лучшие практики.

Ряд ключей от ржавого железа до современных, выставленных на бархатной стене

Вот факт, который удивляет большинство людей: когда вы вводите пароль в sudo, никакой обратной связи вы не видите. Ни звёздочек, ни точек, ничего. Курсор просто стоит на месте. Это архитектурное решение приняли в 1980-х, и оно сбивает с толку пользователей уже больше четырёх десятилетий. Люди регулярно думают, что терминал завис, вводят пароль по нескольку раз или решают, что что-то сломалось. Ubuntu наконец решила показывать звёздочки по умолчанию и тем самым положила конец 46-летней традиции беззвучного ввода пароля.

Это небольшое изменение показывает кое-что большее: то, как Linux устроен с повышением привилегий, — это лоскутное одеяло решений, принятых за десятилетия, каких-то гениальных, каких-то крайне сомнительных, и все они несут груз обратной совместимости. Понимание того, как мы сюда пришли, помогает принимать более разумные решения по безопасности уже сегодня.

До sudo: мир su

Первым инструментом повышения привилегий в Unix была команда su — «substitute user». Она делала ровно одно: переключала всю оболочку на другого пользователя, обычно root. Вы вводили su, вводили пароль root, выполняли административные задачи и выходили обратно в свою обычную учётную запись.

$ su
Password: [type root's password]
# whoami
root
# apt install nginx
# exit
$ whoami
normaluser

Проблемы su стали очевидны, когда Unix-системы выросли за пределы горстки доверенных операторов. Каждому, кому нужен административный доступ, приходилось знать пароль root. Когда кто-то уходил из команды, приходилось менять пароль root и рассылать новый всем остальным. Журнала аудита не было: как только кто-то становился root, все его действия записывались как «root», и никакой записи о том, кто именно что запустил, не оставалось. А su давала полноценную root-оболочку, так что одна опечатка могла уничтожить систему.

Классическая история ужасов: админ хотел ввести rm -rf /tmp/old_files, но случайно нажал Enter после rm -rf /. С полноценной root-оболочкой ничто не мешает этой команде выполниться. su не делала разницы между «этому человеку нужно перезапустить nginx» и «этому человеку нужен неограниченный доступ ко всей системе».

Как sudo решила проблему пароля root

sudo («superuser do») создали в 1980 году в SUNY Buffalo Боб Коггешалл и Клифф Спенсер. Ключевая идея была простой, но переломной: вместо того чтобы делиться паролем root, давать отдельным пользователям запускать конкретные команды от имени root, используя свой пароль. Администратор системы контролирует, кто и что может запускать, через конфигурационный файл.

Это сразу решило несколько проблем:

  • Нет общего пароля root. Каждый пользователь проходит аутентификацию под своими учётными данными. Когда человек уходит, вы просто убираете его доступ к sudo. Ротация паролей не нужна.
  • Гранулярные права. Можно разрешить разработчику перезапускать конкретный сервис, не выдавая ему полный доступ root.
  • Журнал аудита. Каждая команда sudo фиксируется с именем пользователя, который её запустил, самой командой и временем запуска.
  • Временное повышение прав. Вместо открытой root-оболочки sudo выполняет одну команду с повышенными привилегиями, а затем возвращает обычные права.
# Run a single command as root
$ sudo apt install nginx
[sudo] password for priya:
# That's YOUR password, not root's
# The audit trail in /var/log/auth.log:
# Mar 20 14:23:01 web-03 sudo: priya : TTY=pts/0 ;
#   PWD=/home/priya ; USER=root ; COMMAND=/usr/bin/apt install nginx

Файл sudoers: мощно и опасно

Конфигурация sudo лежит в /etc/sudoers, и её синтаксис — пожалуй, одна из самых запутанных вещей в администрировании Linux. Одна синтаксическая ошибка может полностью лишить вас доступа к sudo, поэтому существует команда visudo: она проверяет файл перед сохранением.

# /etc/sudoers syntax:
# WHO  WHERE = (AS_WHOM) WHAT
# Let priya run anything as root on any host
priya ALL=(ALL:ALL) ALL
# Let the 'webdev' group restart nginx only
%webdev ALL=(root) /usr/bin/systemctl restart nginx,\
/usr/bin/systemctl reload nginx
# Let deploy user run deploys without a password
deploy ALL=(root) NOPASSWD: /opt/deploy/run.sh
# DANGEROUS: This looks restrictive but isn't
bob ALL=(root) /usr/bin/vim
# Bob can now run: sudo vim, then :!bash to get a root shell

Последний пример — выдача пользователю доступа к sudo для vim — это ловушка безопасности, в которую люди попадают постоянно. Vim (как и многие другие программы) умеет запускать shell-команды. Если пользователь может выполнить sudo vim, он фактически получает неограниченный root-доступ. То же относится к less, man, awk, find, python и десяткам других команд. Проект GTFOBins ведёт обширный список бинарников, которые можно использовать для выхода из ограниченных оболочек.

Причуды sudo и подводные камни безопасности

За свою историю длиной более 40 лет sudo обросла по-настоящему странным поведением. Понимание этих особенностей важно для безопасности:

  • Кеширование учётных данных. После ввода пароля sudo по умолчанию кеширует его на 15 минут. В это время любая команда sudo выполняется без аутентификации. Если вы отошли от разблокированного терминала, любой может запускать команды sudo до 15 минут. Выполните sudo -k, чтобы немедленно сбросить кеш.
  • Настройка tty_tickets. По умолчанию кеш учётных данных sudo привязан к терминалу. Но в некоторых системах аутентификация в одном терминале открывает sudo во всех остальных. Проверьте настройки Defaults.
  • Переменные окружения. sudo очищает большую часть переменных окружения, но не все. LD_PRELOAD и LD_LIBRARY_PATH удаляются, но если env_keep настроен неправильно, злоумышленник может подсунуть вредоносные пути к библиотекам. На этом основано несколько реальных эксплойтов повышения привилегий.
  • Ловушка NOPASSWD. NOPASSWD удобен для автоматизации, но опасен, если применяется слишком широко. Скомпрометированное приложение, запущенное от пользователя с правом sudo через NOPASSWD, фактически получает root, и пароля, который можно подобрать, у него нет.
# Security hardening for sudoers:
# Require password every time (disable caching)
Defaults timestamp_timeout=0
# Or reduce cache to 1 minute
Defaults timestamp_timeout=1
# Ensure credential cache is per-terminal
Defaults tty_tickets
# Log all sudo I/O (records full terminal sessions)
Defaults log_input, log_output
Defaults iolog_dir=/var/log/sudo-io
# Require root password instead of user password
# (prevents compromised user accounts from sudo-ing)
Defaults rootpw
# Show asterisks when typing password
Defaults pwfeedback

Современные альтернативы: doas, polkit и run0

Сложность sudo породила несколько альтернатив, каждая из которых придерживается своей философии.

doas: sudo без лишней сложности

doas из OpenBSD («dedicated OpenBSD application subexecutor») создал Тед Унангст в 2015 году, именно потому что sudo стала слишком сложной для аудита. Вся конфигурация обычно занимает 2–3 строки:

# /etc/doas.conf — the entire configuration
permit persist priya
permit nopass deploy as root cmd /opt/deploy/run.sh
# That's it. Compare this to a typical sudoers file.
# 'persist' is like sudo's credential caching
# 'nopass' is like NOPASSWD

Исходный код doas — около 2 500 строк на C. У sudo — более 150 000. Для систем, где не нужны продвинутые возможности sudo (например, сопоставление аргументов для отдельных команд или интеграция с LDAP), doas заметно проще настроить, проверить и защитить. Я использую её на своих личных серверах уже несколько лет и ни разу не пожалел о sudo.

polkit: тонко настроенные права рабочего стола

PolicyKit (polkit) подходит к задаче совсем иначе. Вместо оборачивания отдельных команд он описывает действия, которые могут запрашивать приложения. Когда десктопное приложение хочет изменить сетевые настройки, оно запрашивает разрешение у polkit, а тот по своим правилам решает: разрешить, отклонить или запросить подтверждение у пользователя.

Так работает ваш Linux-рабочий стол: USB-накопитель можно смонтировать без пароля, а для установки программ требуется аутентификация. Гранулярность здесь на уровне действий, а не команд, что лучше соответствует тому, как пользователи десктопов на самом деле думают о правах.

run0: радикальный пересмотр от systemd

run0 от systemd — самый новый участник, и работает он принципиально иначе, чем sudo. Вместо того чтобы запускать команду от root из текущей сессии (что требует setuid-битов и создаёт головную боль с безопасностью), run0 просит менеджер сервисов запустить новый сервис, работающий от root. Пользовательская сессия никогда не получает повышенных привилегий, а привилегированный процесс полностью отделён.

Это обходит целые классы уязвимостей sudo: здесь нет setuid-бинарника, нет кеширования учётных данных, нет поверхности для внедрения через переменные окружения. Цена вопроса в том, что нужен systemd, поэтому run0 не подойдёт для BSD-систем и минималистичных сборок Linux. Но для серверов на systemd это, пожалуй, самый безопасный из доступных вариантов.

Практические рекомендации

Годы администрирования Linux-серверов и расследований инцидентов с повышением привилегий привели меня к таким рекомендациям:

  1. Полностью отключите вход под root. Используйте sudo или doas для всего. Общего пароля root быть не должно.
  2. В sudoers используйте группы, а не отдельных пользователей. Управляйте доступом через членство в группах. Когда человек уходит, удаляйте его из группы.
  3. Никогда не выдавайте sudo для интерпретаторов и редакторов. Никаких sudo vim, sudo python, sudo less. Если нужно отредактировать файл, принадлежащий root, используйте sudoedit.
  4. Минимизируйте правила NOPASSWD. Используйте их только для автоматизированных процессов и только для конкретных команд с полным путём.
  5. Включите логирование sudo. Как минимум записывайте все команды sudo. Для чувствительных систем включайте полное логирование ввода-вывода.
  6. Рассмотрите doas для более простых систем. Если не нужны интеграция с LDAP или сложные правила сопоставления, doas проще настроить правильно.
  7. Агрессивно ограничивайте время жизни кеша учётных данных. Уменьшите timestamp_timeout до 1–5 минут или полностью отключите кеширование на продакшен-серверах.

Лучший механизм повышения привилегий — тот, который даёт минимум необходимого доступа, на минимальное необходимое время и с полным журналом аудита. Всё остальное — компромисс.

sudo никуда не денется. Она слишком глубоко встроена в скрипты, автоматизацию, документацию и мышечную память, чтобы исчезнуть. Но ландшафт альтернатив сейчас здоровее, чем когда-либо. Оставите ли вы sudo (правильно укреплённую), перейдёте на doas (ради простоты) или возьмёте run0 (для критичных к безопасности систем), важно понимать, что именно делает ваш инструмент повышения привилегий, ведь разрыв между тем, что люди думают, что делает sudo, и тем, что она на самом деле делает, — это место, где живёт большинство инцидентов безопасности.