Формирование команд санитарной оболочки или системных вызовов в Ruby
Я создаю демона, который поможет мне управлять моим сервером. Webmin отлично работает, так же как и открытие оболочки на сервере, но я предпочел бы иметь возможность контролировать операции с сервером UI I, а также предоставлять некоторые функции конечным пользователям.
Демон выберет действия из очереди и выполнит их. Однако, поскольку я буду принимать входные данные от пользователей, я хочу убедиться, что им не разрешено вводить что-то опасное в привилегированную команду оболочки.
Вот фрагмент, который иллюстрирует мою проблему:
def perform
system "usermod -p #{@options['shadow']} #{@options['username']}"
end
Суть, которая объясняет больше: https://gist.github.com/773292
Я не уверен, если для этого случая достаточно типичного ухода и дезинфекции входных данных, и, будучи дизайнером, у меня нет тонны опыта, связанного с безопасностью. Я знаю, что это должно быть очевидно для меня, но его не!
Как я могу гарантировать, что веб-приложение, которое будет создавать и сериализовать действия, не может передать опасный текст в привилегированный процесс, который получает действия?
Спасибо за помощь
отн
Ответы
Ответ 1
Не похоже, что вам нужна оболочка для того, что вы делаете. См. Документацию для system
здесь: http://ruby-doc.org/core/classes/Kernel.html#M001441
Вы должны использовать вторую форму system
. Ваш пример выше:
system 'usermod', '-p', @options['shadow'], @options['username']
Более приятный способ (IMO):
system *%W(usermod -p #{@options['shadow']} #{@options['username']})
Аргументы таким образом передаются непосредственно в вызов execve
, поэтому вам не нужно беспокоиться о подлых трюках оболочки.
Ответ 2
Если вам нужен не только статус выхода, но и результат, вы, вероятно, захотите использовать Open3.popen3
:
require 'open3'
stdin, stdout, stderr = Open3.popen3('usermod', '-p', @options['shadow'], @options['username'])
stdout.gets
sterr.gets
Дополнительная информация здесь: Получение вывода вызовов system() в Ruby
Ответ 3
Я бы предложил изучить модуль "shellwords". Этот script:
require 'shellwords'
parts = ['echo', "'hello world'; !%& some stuff", 'and another argument']
command = Shellwords.shelljoin( parts )
puts command
output = `#{ command }`
puts output
выводит экранированный текст и ожидаемый результат:
echo \'hello\ world\'\;\ \!\%\&\ some\ stuff and\ another\ argument
'hello world'; !%& some stuff and another argument
Ответ 4
Это старый вопрос, но поскольку это в значительной степени единственный реальный ответ, который вы найдете при поиске в Google, я думал, что добавлю оговорку. Многопользовательская версия системы кажется разумно безопасной для Linux, но она НЕ на Windows.
Попробуйте system "dir", "&", "echo", "hi!"
в системе Windows. Будут выполняться как dir, так и echo. Разумеется, эхо может быть чем-то гораздо менее безобидным.
Ответ 5
Я знаю, что это старый поток, но есть еще один вариант, который слегка коснулся Саймон Хюрлиманн.
В этой теме не так много информации, и я думаю, что это может помочь другим, нуждающимся в помощи.
В этом примере мы будем использовать Open3
, который дает вам возможность запускать команды синхронно или асинхронно и предоставляет stdout, stderr, коды выхода и PID.
Open3 предоставляет вам доступ к stdout, stderr, кодам выхода и потоку для ожидания дочернего процесса при запуске другой программы. Вы можете указать различные атрибуты, перенаправления, текущий каталог и т.д. Программы так же, как и для Process.spawn. (Источник: Open3 Docs)
Я решил форматировать вывод как объект CommandStatus
. Это содержит наши stdout
, stderr
, pid
(из рабочего потока) и exitstatus
.
class Command
require 'open3'
class CommandStatus
@stdout = nil
@stderr = nil
@pid = nil
@exitstatus = nil
def initialize(stdout, stderr, process)
@stdout = stdout
@stderr = stderr
@pid = process.pid
@exitstatus = process.exitstatus
end
def stdout
@stdout
end
def stderr
@stderr
end
def exit_status
@exitstatus
end
def pid
@pid
end
end
def self.execute(command)
command_stdout = nil
command_stderr = nil
process = Open3.popen3(ENV, command + ';') do |stdin, stdout, stderr, thread|
stdin.close
stdout_buffer = stdout.read
stderr_buffer = stderr.read
command_stdout = stdout_buffer if stdout_buffer.length > 0
command_stderr = stderr_buffer if stderr_buffer.length > 0
thread.value # Wait for Process::Status object to be returned
end
return CommandStatus.new(command_stdout, command_stderr, process)
end
end
cmd = Command::execute("echo {1..10}")
puts "STDOUT: #{cmd.stdout}"
puts "STDERR: #{cmd.stderr}"
puts "EXIT: #{cmd.exit_status}"
При чтении буферов STDOUT/ERR я использую command_stdout = stdout_buffer if stdout_buffer.length > 0
для управления назначением переменной command_stdout
или нет. Вы должны передать nil
вместо ""
, когда данных нет. Это более понятно при передаче данных позже.
Вероятно, вы заметили меня, используя command + ';'
. Причина этого основана на документации Kernel.exec(что использует popen3):
Если строка из первой формы (exec ( "command" )) следует этим простые правила:
- нет метасимволов
- без зарезервированного слова оболочки и специального встроенного
- Ruby вызывает команду напрямую без оболочки
Вы можете принудительно вызвать вызов оболочки, добавив ";" к строке (потому что ";" является метасимволом)
Это просто запрещает Ruby бросать ошибку 'spawn': No such file or directory
, если вы передадите неверную команду. Вместо этого он передаст это прямо в ядро, где ошибка будет решена изящно и появится как STDERR вместо неперехваченного исключения.