Формирование команд санитарной оболочки или системных вызовов в Ruby - PullRequest
13 голосов
/ 10 января 2011

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

Демон будетзабрать действия из очереди и выполнить их.Однако, так как я буду принимать ввод от пользователей, я хочу убедиться, что им не разрешено вводить что-то опасное в команду привилегированной оболочки.

Вот фрагмент, который иллюстрирует мою проблему:

def perform
  system "usermod -p #{@options['shadow']} #{@options['username']}"
end

Суть, объясняющая больше: https://gist.github.com/773292

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

Как я могу гарантировать, что веб-приложение, которое будет создавать и сериализовать действия, не может пройти опаснотекст в привилегированный процесс, который получает действия?

Спасибо за помощь
arb

Ответы [ 5 ]

19 голосов
/ 11 января 2011

Не похоже, что вам нужна оболочка для того, что вы делаете.См. Документацию для 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, поэтомувам не нужно беспокоиться о хитрых уловках.

16 голосов
/ 12 мая 2011

Если вам нужен не только статус выхода, но и результат, который вы, вероятно, хотите использовать Open3.popen3:

require 'open3'
stdin, stdout, stderr = Open3.popen3('usermod', '-p', @options['shadow'], @options['username'])
stdout.gets
sterr.gets

Дополнительная информация здесь: Получение выходных данных вызовов system () в Ruby

2 голосов
/ 19 апреля 2012

Я бы посоветовал заглянуть в модуль «shellwords». Этот скрипт:

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
1 голос
/ 17 апреля 2013

Это старый вопрос, но так как это практически единственный реальный ответ, который вы найдете, когда погуглите, я решил добавить предостережение.Версия с несколькими аргументами системы кажется достаточно безопасной в Linux, но она НЕ в Windows.

Попробуйте system "dir", "&", "echo", "hi!" в системе Windows.Будут запущены как dir, так и echo.Эхо, конечно, может быть чем-то гораздо менее безобидным.

0 голосов
/ 22 апреля 2017

Я знаю, что это старая ветка, но есть еще один вариант, который был слегка затронут Саймон Хюрлиманн .

По этой теме не так много информации, и я думаю,это может помочь другим нуждающимся.

В этом примере мы будем использовать 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 вместо необработанного исключения.

...