Какой самый безопасный способ программной записи в файл с привилегиями root?

35

Огромное приложение должно в одно конкретное время выполнить небольшое количество операций записи в файл, для которого требуются права суперпользователя. На самом деле это не файл, а аппаратный интерфейс, который представлен Linux как файл.

Чтобы избежать предоставления привилегий root всему приложению, я написал скрипт bash, который выполняет критические задачи. Например, следующий скрипт включит порт 17 аппаратного интерфейса в качестве вывода:

echo "17" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio17/direction

Однако, поскольку suidв моей системе отключены скрипты bash, мне интересно, каков наилучший способ добиться этого.

  1. Используйте некоторые обходные пути, представленные здесь

  2. Вызовите сценарий sudoиз основного приложения и измените список sudoers соответствующим образом, чтобы избежать вызова пароля при вызове сценария. Мне немного неудобно давать привилегии sudo echo.

  3. Просто напишите программу на C fprintfи установите для нее suid root. Жестко закодируйте строки и имена файлов и убедитесь, что только root может их редактировать. Или прочитайте строки из текстового файла, убедившись, что никто не может редактировать файл.

  4. Какое-то другое решение, которое мне не пришло в голову и является более безопасным или более простым, чем представленное выше?

ВСЗ
источник
10
Почему бы вам просто не запустить вашу программу с привилегиями root, открыть файл и удалить привилегии? Вот как каждый веб-сервер делает это для сокетов. По сути, вы не работаете от имени пользователя root, и вам не нужен помощник.
Деймон

Ответы:

33

Вам не нужно давать sudoдоступ к echo. На самом деле, это бессмысленно, потому что, например sudo echo foo > bar, перенаправление выполняется как первоначальный пользователь, а не как root.

Вызовите небольшой сценарий sudo, разрешив NOPASSWD:доступ ТОЛЬКО к этому сценарию (и любым другим аналогичным сценариям) пользователям, которым необходим доступ к нему.

Это всегда лучший / самый безопасный способ использования sudo. Выделите небольшое количество команд, которым требуются привилегии root, в свои собственные отдельные сценарии и разрешите не доверенному или частично доверенному пользователю запускать этот сценарий только как root.

Небольшой sudoсценарий (сценарии) не должен принимать аргументы (или входные данные) от пользователя (т. Е. Любые другие вызываемые им программы должны иметь жестко запрограммированные параметры и аргументы) или он должен очень тщательно проверять любые аргументы / входные данные, которые он должен иметь. принять от пользователя.

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

Проверка должна происходить как можно раньше в скрипте (желательно до того, как он сделает что - либо еще от имени пользователя root).


Я действительно должен был упомянуть об этом, когда впервые написал этот ответ, но если ваш сценарий является сценарием оболочки, он ДОЛЖЕН правильно указывать все переменные в кавычках. Будьте особенно осторожны , чтобы процитировать переменные , содержащие вход , переданному пользователем в любой способ, но не предполагают некоторые переменные являются безопасными, ЗАКАВЫЧУ ИХ ВСЕ .

Это включает в себя переменные среды, потенциально контролируемые пользователем (например "$PATH", "$HOME"и "$USER"т. Д. И, безусловно, включение "$QUERY_STRING"и "HTTP_USER_AGENT"т. Д. В сценарий CGI). На самом деле, просто процитируйте их всех. Если вам нужно создать командную строку с несколькими аргументами, используйте массив для построения списка аргументов и заключите его в кавычки "${myarray[@]}".

Я уже достаточно часто говорил «процитировать их всех»? Помни это. сделай это.

саз
источник
18
Вы забыли упомянуть, что сам скрипт должен принадлежать пользователю root с разрешениями 500.
Wildcard
13
По крайней мере, удалите разрешения на запись, ради всего святого. Это была моя точка зрения. Остальное просто закаливается.
Wildcard
10
Продуманно написанная C-программа будет иметь меньшую поверхность атаки, чем сценарий оболочки.
user253751
7
@immibis, возможно. Но для написания и отладки потребуется гораздо больше времени, чем для сценария оболочки. И требует наличия компилятора C (который запрещен на некоторых производственных серверах для снижения риска безопасности, поскольку злоумышленникам будет сложнее компилировать эксплойты). Кроме того, IMO сценарий оболочки, написанный новичком для системного администратора или программиста среднего уровня, менее пригоден для использования, чем программа на C, написанная кем-то с похожими навыками - особенно если он должен принимать и проверять предоставленные пользователем данные.
Cas
3
@JonasWielicki - я думаю, что теперь мы находимся в правильном положении в сфере мнений, а не фактов. Вы можете сделать правильные аргументы для оболочки или C (или perl, или python, или awk и т. Д.), Более или менее подверженных ошибкам, которые могут быть использованы Когда, действительно, это зависит в основном от умения программиста и внимания к деталям (а также от усталости, спешки, осторожности и т. Д.). Это факт, однако, что языки более низкого уровня, как правило, требуют написания большего количества кода для достижения того, что можно сделать с гораздо меньшим количеством строк кода в языках более высокого уровня ... и каждый LoC - это еще одна возможность для ошибка должна быть сделана.
Cas
16

Проверьте владельца файлов gpio:

ls -l /sys/class/gpio/

Скорее всего, вы узнаете, что они принадлежат группе gpio:

-rwxrwx--- 1 root     gpio     4096 Mar  8 10:50 export
...

В этом случае вы можете просто добавить своего пользователя в gpioгруппу, чтобы предоставить доступ без sudo:

sudo usermod -aG gpio myusername

После этого вам нужно будет выйти и снова войти в систему, чтобы изменения вступили в силу.

JPA
источник
Не работает Действительно, владельцем группы всего /sys/class/gpio/является gpio, но даже после добавления себя в эту группу я получаю «отказ в разрешении» всякий раз, когда пытаюсь что-то там написать.
вс
1
Проблема в том, что файлы в /sys/class/gpio/действительности являются просто символическими ссылками, /sys/devices/platform/soc/<some temporary name>/gpioгде владелец и группа являются корневыми.
вс
4
@vsz Ты пробовал chgrp gpio /sys/devices/platform/soc/*/gpio? Возможно, что-то подобное может быть помещено в файл запуска.
JPA
да, но это не так просто Поскольку они всегда генерируются по-другому, мне пришлось использовать что-то вродеchgrp gpio `readlink -f /sys/class/gpio/gpio18`/*
vsz
7

Одним из решений для этого (используется, в частности, на настольном компьютере Linux, но также применимо и в других случаях), является использование D-Bus для активации небольшого сервиса, работающего от имени root, и polkit для выполнения авторизации. Это в основном то, для чего был разработан polkit ; из вступительной документации :

polkit предоставляет API авторизации, предназначенный для использования привилегированными программами («МЕХАНИЗМЫ»), предлагающими услуги непривилегированным программам («КЛИЕНТЫ»). См. Страницу руководства polkit для ознакомления с архитектурой системы и общей картиной.

Вместо того, чтобы выполнять вашу вспомогательную программу, большая непривилегированная программа отправит запрос на шину. Ваш помощник может либо работать как демон, запущенный при загрузке системы, либо, что лучше, быть активирован по мере необходимости systemd . Затем этот помощник будет использовать polkit для проверки того, что запрос поступает из авторизованного места. (Или, в этом случае, если это кажется излишним, вы можете использовать какой-то другой жестко закодированный механизм аутентификации / авторизации.)

Я нашел хорошую базовую статью о связи через D-Bus , и хотя я ее не тестировал, похоже, это основной пример добавления polkit к миксу .

При таком подходе ничто не должно быть отмечено setuid.

mattdm
источник
5

Один из способов сделать это - создать программу setuid-root, написанную на C, которая делает только то, что нужно, и ничего более. В вашем случае, это не должно смотреть на любой пользовательский ввод вообще.

#include <unistd.h>
#include <string.h>
#include <stdio.h>  // for perror(3)
// #include ...  more stuff for open(2)

static void write_str_to_file(const char*fn, const char*str) {
    int fd = open(fn, O_WRONLY)
    if (-1 == fd) {
        perror("opening device file");  // make this a CPP macro instead of function so you can use string concat to get the filename into the error msg
        exit(1);
    }
    int err = write(fd, str, strlen(str));
    // ... error check
    err = close(fd);
    // ... error check
}

int main(int argc, char**argv) {
    write_string_to_file("/sys/class/gpio/export", "17");
    write_string_to_file("/sys/class/gpio/gpio17/direction", "out");
    return 0;
}

Там нет никакого способа, чтобы подорвать это через переменные окружения или что-то еще, потому что все, что он делает, это сделать пару системных вызовов.

Недостаток: вы должны проверять возвращаемое значение каждого системного вызова.

Вверху: проверка ошибок действительно проста: если есть какая-либо ошибка, просто perrorи выручите: выйдите с ненулевым статусом. Если есть ошибка, расследуйте с strace. Вам не нужна эта программа, чтобы выдавать действительно хорошие сообщения об ошибках.

Питер Кордес
источник
2
Я мог бы испытать желание открыть оба файла перед тем, как что-нибудь написать, чтобы защитить от отсутствия второго. И я мог бы запустить эту программу через sudoнее, так что она не требует настройки. Кстати, это <fcntl.h>для open().
Джонатан Леффлер
3

Благослови тройник вместо эха для sudo - один из распространенных способов приблизиться к ситуации, когда вам нужно ограничить корневые перми. Перенаправление на / dev / null - предотвращение утечки любого вывода - тройник делает то, что вы хотите.

echo "17" | sudo tee /sys/class/gpio/export > /dev/null
echo "out" | sudo tee /sys/class/gpio/gpio17/direction > /dev/null
Майлз Гилхэм
источник
5
Если вы разрешаете teeзапускать с ним sudo, вы разрешаете ЛЮБОЙ файл перезаписывать или /etc/passwdдобавлять (в том числе, например, - просто добавить новую учетную запись uid = 0). Вы также можете разрешить выполнение всех команд с помощью sudo(BTW /etc/sudoersвходит в набор «ЛЮБЫХ файлов», поэтому их можно перезаписать sudo tee)
cas
2
Видимо, вы можете ограничить количество файлов, в которые teeможно записывать, как описано в этом ответе на отдельный вопрос. Просто убедитесь, что вы также читаете комментарии, так как у некоторых людей были проблемы с исходным синтаксисом, и они предлагают исправления этой проблемы.
Алекс
1
@alex, да, вы можете сделать это с помощью любой команды в sudo - ограничить допустимые аргументы. Конфигурация может быть очень длинной и сложной, если вы хотите разрешить teeили что-либо еще работать с большим количеством файлов sudo.
Cas
0

Вы можете сделать сценарий, который выполняет желаемую задачу. Затем создайте новую учетную запись пользователя, которая может войти в систему только путем предоставления ключа, используемого OpenSSH.

Примечание. Любой сможет запустить этот сценарий, если у него есть файл ключа, поэтому убедитесь, что файл ключа OpenSSH не доступен для чтения любому, кому вы хотите помешать выполнить задачу.

В конфигурации OpenSSH (в файле author_keys) перед указанием ключа укажите команду (с пробелом), как описано в этом «примере» текста:

command="myscript" keydata optionalComment

Этот параметр конфигурации может ограничить этот ключ OpenSSH только выполнением определенной команды. Теперь у вас есть sudo, предоставляющее разрешения, но конфигурация OpenSSH - это часть решения, которое действительно используется для ограничения / ограничения возможностей этого пользователя, чтобы пользователь не выполнял другие команды. Это также не так сложно, как файл конфигурации sudo, поэтому, если вы используете OpenBSD или новые OpenASD "doas" ("do as") начинают становиться более популярными (с будущими версиями любой операционной системы, которую вы используете) вам не придется сталкиваться с большой сложностью в конфигурации sudo.

TOOGAM
источник