Порт ниже 1024 без root
Службе — часть через юнит, программе вне systemd — через файл:
sysctl net.ipv4.ip_unprivileged_port_start=80 тоже открывает порт, но
всем пользователям машины сразу.
Части прав root: процесс или файл получает только нужную — например, CAP_NET_BIND_SERVICE для порта ниже 1024.
grep Cap /proc/self/status
наборы текущего процесса
capsh --decode=0000000000000400
маска → имена
getpcaps 1234
capabilities процесса по PID
getcap -r / 2>/dev/null
файлы с capabilities
sudo setcap cap_net_bind_service=+ep /usr/local/bin/app
выдать файлу
sudo setcap -r /usr/local/bin/app
снять
sudo capsh --drop=cap_dac_override -- -c 'cat f'
root без части
Ядро проверяет права двумя способами. Обычному процессу — по правам
файла и его владельцу. Процессу root раньше разрешалось
всё. Теперь сила root разделена на части —
capabilities (по-русски их зовут возможностями), и
ядро спрашивает конкретную часть:
слушать порт ниже 1024 можно с CAP_NET_BIND_SERVICE, читать чужой файл —
с CAP_DAC_OVERRIDE.
У root по умолчанию все части, поэтому он может всё. Но процесс root без нужной части получает отказ, а процесс обычного пользователя с одной частью делает то, что она разрешает, и ничего сверх. На этом стоит принцип минимальных привилегий: службе — её часть, а не весь root.
На машине их 41. Список печатает capsh --decode=000001ffffffffff; частые:
| Capability | Что разрешает | Кому нужна |
|---|---|---|
CAP_NET_BIND_SERVICE |
слушать порт ниже 1024 | веб-сервер на 80 и 443 |
CAP_NET_RAW |
сырые сокеты: ICMP, свои пакеты | ping, mtr |
CAP_NET_ADMIN |
интерфейсы, маршруты, правила файрвола | systemd-networkd, ip, nft |
CAP_DAC_OVERRIDE |
обходить права rwx файлов | root, читающий чужой файл |
CAP_DAC_READ_SEARCH |
читать любой файл и проходить любой каталог | резервное копирование |
CAP_CHOWN |
менять владельца файла | установка пакетов |
CAP_SETUID, CAP_SETGID |
сменить UID и GID процесса | su, sudo, sshd |
CAP_KILL |
сигналы чужим процессам | kill от root |
CAP_SYS_TIME |
менять системное время | systemd-timesyncd |
CAP_SYS_PTRACE |
трассировать чужие процессы | strace -p |
CAP_SYS_ADMIN |
монтирование и десятки других операций | почти весь root |
У процесса пять наборов. Ядро хранит их битовыми масками и показывает в
/proc/<PID>/status:
| Строка | Набор | Что значит |
|---|---|---|
CapEff |
effective | действует сейчас — его проверяет ядро |
CapPrm |
permitted | что процесс может включить в effective |
CapInh |
inheritable | что может перейти в новую программу через exec |
CapBnd |
bounding | потолок: больше этого не получат ни процесс, ни потомки |
CapAmb |
ambient | переходит в новую программу и без capabilities файла |
У student действующих частей нет, у процесса под sudo — все 41.
Маску в имена переводит capsh --decode=<маска>, процесс по PID
показывает getpcaps:
Буквы после = — наборы: e effective, i inheritable, p permitted.
Службы времени и имён работают от своих пользователей с одной частью
каждая. =ep без имён — все части сразу: главный процесс nginx
работает от root.
Исполняемый файл тоже может нести capabilities — как setuid, только вместо всего root процесс получает названные части. Таких файлов на машине три:
Поэтому ping работает у student: ICMP без привилегий на машине
выключен (net.ipv4.ping_group_range = 1 0), и CAP_NET_RAW программа
получает от своего файла.
Выдаёт части файлу setcap, и только root. Проверка — скрипт
bind81.py, который занимает порт 81:
Часть получает копия Python: на символическую ссылку /usr/bin/python3
setcap не ставит.
Процесс из такого файла — обычный student с одной частью: CapEff у него
0000000000000400, это cap_net_bind_service. Граница непривилегированных
портов — net.ipv4.ip_unprivileged_port_start = 1024.
Capabilities живут в расширенном атрибуте файла. Простой cp их не
переносит, cp -a переносит. Новый файл на месте старого — после
обновления пакета или пересборки — приходит без них: ping получает свою
часть из скрипта установки пакета. На файловой системе с опцией nosuid
capabilities файлов не действуют.
capsh --drop запускает команду от root, убрав из bounding-набора
названные части:
Root остался root (UID 0), но без CAP_DAC_OVERRIDE и
CAP_DAC_READ_SEARCH права файла для него действуют, как для всех. Так
же root без CAP_NET_BIND_SERVICE получает Permission denied на порт 84.
Службе части выдаёт и отбирает systemd. AmbientCapabilities= даёт
службе с User= нужную часть, CapabilityBoundingSet= задаёт потолок:
Во втором случае служба работает от root, но её потолок —
CapBnd: 0000000000000400, и чужой файл ей не прочитать. Как собрать эти
ключи в юнит — systemd-hardening. Docker запускает
контейнер с урезанным набором частей, --cap-drop и --cap-add меняют
его (см. docker).
Службе — часть через юнит, программе вне systemd — через файл:
sysctl net.ipv4.ip_unprivileged_port_start=80 тоже открывает порт, но
всем пользователям машины сразу.
На чистой машине — три файла из блока выше. Лишний файл в списке — особенно интерпретатор, архиватор или редактор — повод разобраться, кто и зачем его выдал.
PermissionError: [Errno 13] Permission denied у Python, bind() to
0.0.0.0:80 failed (13: Permission denied) у nginx: процесс работает не от
root и без CAP_NET_BIND_SERVICE. Службе добавь AmbientCapabilities= и
проверь CapEff в /proc/<PID>/status.
AmbientCapabilities= уже стоит, а отказ тот же — часть срезана потолком.
Пустой CapabilityBoundingSet= в другом drop-in'е обнуляет и ambient-набор:
впиши ту же часть и в CapabilityBoundingSet=. Если порт занят, ошибка
другая — Address already in use; на учебной машине порт 80 держит nginx.
setcap запущен без sudo: выдавать части файлам может только root.
setcap не понял имя части или файл. Имена — как в capsh --decode
(cap_net_bind_service), файл — обычный: на символическую ссылку вроде
/usr/bin/python3 setcap не ставит, нужен сам python3.12. Нет файла —
Failed to set capabilities on file '…': No such file or directory.
getcap ничего не печатает, программа снова получает Permission denied.
Файл заменили: cp без -a, обновление пакета, пересборка. Выдай часть
заново и закрепи её там, где файл появляется, — в скрипте установки или
юните.
Процесс работает от UID 0, а ядро отказывает. Его потолок урезан: служба с
CapabilityBoundingSet=, контейнер, capsh --drop. Сравни CapBnd и
CapEff в /proc/<PID>/status с нужной частью.
py-bind из примера выше даёт порт ниже 1024 любому скрипту любого
пользователя. cap_dac_read_search на Python, tar или vim открывает
им /etc/shadow, а cap_setuid — путь к root. Выдавай части файлу
конкретной программы и проверяй список getcap -r /.
Эта часть разрешает монтировать файловые системы, создавать пространства имён и ещё десятки операций. Служба с ней почти равна root — ищи часть поуже.
User=, CapabilityBoundingSet= и
AmbientCapabilities= в юните./proc/<PID>/status и другие файлы процесса.cap_net_raw.Есть неточность? Сообщить об ошибке в статье