Выполнение команд в процессе повышенного bash путем записи на стандартный ввод его родительского скриптового процесса

У меня есть простой bash-скрипт bash.sh который запускает другой экземпляр bash с использованием pkexec .

 #!/bin/bash bash -c 'pkexec bash' 

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

Когда я открываю окно терминала и пытаюсь записать некоторую команду на стандартный ввод процесса с повышенным уровнем bash, он выдает ошибку разрешения (как и ожидалось).

 echo 'echo hello' > /proc/<child-bash-pid>/fd/0 

Проблема в том, что когда я пишу родительский процесс ( bash.sh ), он передается процессу дочернего bash, который затем выполняет команду.

 echo 'echo hello' > /proc/<parent-bash.sh-pid>/fd/0 

Я не могу понять, как это возможно? Поскольку родитель работает как обычный пользователь, почему я (обычный пользователь) разрешаю передавать команды дочернему процессу, который работает с более высокими привилегиями?

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

Это не кажется логичным. Что мне не хватает?

Примечание. Я проверил, что дочерний элемент выполняет команду, переданную родительскому элементу, удалив файл в /usr/share , доступ к которому имеет только root.

 sudo touch /usr/share/testfile echo 'rm -f /usr/share/testfile' > /proc/<parent-bash.sh-pid>/fd/0 

Файл был успешно удален.

One Solution collect form web for “Выполнение команд в процессе повышенного bash путем записи на стандартный ввод его родительского скриптового процесса”

Это нормально. Чтобы понять это, давайте посмотрим, как работают дескрипторы файлов и как они передаются между процессами.

Вы упомянули, что используете GLib.spawn_async() для GLib.spawn_async() сценария оболочки. Эта функция, по-видимому, создает канал, который будет использоваться для отправки данных в stdin ребенка (или, возможно, вы сами создаете канал и передаете его функции). Чтобы порождать дочерний процесс, эта функция отключит новый процесс fork() изменит его файловые дескрипторы таким образом, что stdin pipe станет fd 0 , а затем exec() ваш скрипт. Поскольку сценарий начинается с #!/bin/bash , ядро ​​интерпретирует это с помощью exec() в оболочке bash, которая затем запускает ваш сценарий оболочки. Этот скрипт оболочки вилки и execs еще один bash (это, кстати, избыточно, вам действительно не нужен bash -c ). Никакие файловые дескрипторы не перегруппированы, поэтому новый процесс наследует тот же самый канал, что и дескриптор файла stdin. Обратите внимание, что это не связано с его родительским процессом как таковым – на самом деле файловые дескрипторы ссылаются на один и тот же канал, тот, который был создан или назначен GLib.spawn_async() . По сути, мы просто создаем псевдонимы для трубы: fd 0 в этих процессах все ссылаются на трубу.

Процесс повторяется при pkexec , но pkexec является двоичным двоичным двоичным pkexec . Это означает, что когда этот двоичный файл является exec() ed, он запускается с правами root, но его stdin все еще подключен к исходной трубе. pkexec выполняет свои проверки разрешений ( pkexec пароля), а затем, в конечном счете, exec() s bash. Теперь у нас есть корневая оболочка, которая берет свой вход из трубы, в то время как ряд других процессов, принадлежащих вашему пользователю, также имеют ссылку на этот канал.

Важно понять, что в семантике POSIX файловые дескрипторы не имеют прав. Файлы имеют разрешения, но дескрипторы файлов представляют собой привилегию доступа к файлу (или абстрактному буферу, например, к каналу). Вы можете передать дескриптор файла на новый процесс или даже на существующий процесс (через сокеты UNIX), а разрешение доступа к файлу перемещается с файловыми дескрипторами. Вы даже можете открыть файл, а затем изменить его владельца на другого пользователя и все же получить доступ к файлу через исходный fd как предыдущий владелец, поскольку разрешения проверяются только при открытии файла. Таким образом, файловые дескрипторы позволяют осуществлять связь между границами привилегий. Имея процесс, принадлежащий вашему пользователю, и процесс, принадлежащий корневому ресурсу, имеют один и тот же файловый дескриптор, вы предоставляете обоим процессам одинаковые права над этим файловым дескриптором. И поскольку fd является каналом, а корневой процесс принимает команды из этого канала, это позволяет другому процессу, принадлежащему вашему пользователю, вводить команды как root. Сама труба не имеет понятия владельца, а всего лишь ряд процессов, в которых есть открытые дескрипторы файлов.

Кроме того, поскольку базовая модель безопасности Linux предполагает, что пользователь имеет полный контроль над всеми своими процессами, это означает, что вы можете заглянуть в /proc чтобы получить доступ к fd, как вы это делали. Вы не можете сделать это через запись /proc для процесса bash, выполняемого как root (поскольку вы не являетесь пользователем root), но вы можете сделать это для своего собственного процесса, и полученный в результате полученный дескриптор файла файла будет таким же, как если бы вы может сделать это непосредственно для дочернего процесса, выполняемого как root. Таким образом, повторение данных в трубе заставляет ядро ​​возвращать его обратно к процессам, считываемым из канала – в этом случае – только корневая оболочка ребенка, которая активно считывает команды из канала.

Если сценарий оболочки вызывается из терминала, то повторение данных в его стандартный дескриптор входного файла фактически приведет к записи данных в терминал и будет отображаться пользователю (но не выполняется оболочкой). Это связано с тем, что терминальные устройства являются двунаправленными, и, фактически, терминал будет подключен как к stdin, так и к stdout (и stderr). Тем не менее, терминалы имеют специальные методы ioctl для ввода входных данных, поэтому все еще можно вводить команды в корневую оболочку как пользователь (это просто требует больше простого echo ).

В общем, вы обнаружили неудачную правду об эскалации привилегий: в тот момент, когда вы разрешаете пользователю эскалацию в корневую оболочку любыми средствами, эффективно любое приложение, выполняемое этим пользователем, должно считаться способным злоупотреблять этой эскалацией (в то время как это существует). Пользователь становится root, для целей и целей безопасности. Даже если такая инъекция stdin не была возможна, например, если вы запускали скрипт под терминалом, вы могли бы просто использовать поддержку X-клавиатурной клавиатуры для отправки команд непосредственно на графическом уровне. Или вы можете использовать gdb для присоединения к процессу с открытым трубом и впрыскивать в него записи. Единственный способ закрыть это отверстие – иметь корневую оболочку, напрямую связанную с безопасным каналом ввода-вывода, с физическим пользователем, который не может быть подделан непривилегированными процессами. Это трудно обойтись без строгого ограничения юзабилити.

Стоит отметить последнее: обычно (анонимные) каналы имеют конец чтения и конец записи, то есть два отдельных дескриптора файла. Конец передан дочерним процессам, поскольку stdin – это конец чтения, в то время как конец записи останется в первоначальном процессе, который называется GLib.spawn_async() . Это означает, что дочерние процессы не могут фактически записывать в stdin для отправки данных обратно себе или в bash с правами root (конечно, процессы обычно не записываются в stdin, хотя ничто не говорит, что вы не можете, но в этом в случае, если stdin является прочитанным концом трубы). Однако механизм ядра /proc для доступа к файловым дескрипторам из другого процесса подрывает это: если процесс имеет открытый fd для чтения конца канала, но вы пытаетесь открыть его соответствующий /proc fd-файл для записи, тогда ядро ​​будет на самом деле дает вам конец записи того же самого канала. Кроме того, вы можете искать запись /proc соответствующую исходному процессу, который называется GLib.spawn_async() , найти конец канала, который открыт для записи, и записать в него, что не будет зависеть от этого особого поведения ядра ; это в основном любопытство, но на самом деле не меняет проблему безопасности.

Interesting Posts

Почему dd занимает слишком много времени?

Как я могу отсортировать этот массив чисел?

меньше с несколькими файлами; команда для вывода имен файлов

Виджет температурного монитора Fedora KDE в Farenheit

объединить файлы csv, сохранить запятую внутри кавычек

Как использовать wc и piping для определения количества файлов и каталогов в определенном каталоге?

Невозможно использовать самоподписанный сертификат. на локальном хосте

Как пометить прочитанные письма как непрочитанные в Postfix

Поиск шаблона с условиями

dualhead nvidia twinview + один монитор повернут

Обратное совпадение в sed, замените противоположное тому, что было найдено

Почему я не могу обновить Linux Mint 15 от Cinnamon 1.8.8 до Cinnamon 2.0 и как его исправить?

Необъяснимое разъединение rsync

Ошибки таймаута во FreeBSD zfs

Сценарии Bash и большие файлы (ошибка): ввод с встроенным чтением из перенаправления дает неожиданный результат

Linux и Unix - лучшая ОС в мире.