我正在构建一个可以帮助我管理服务器的守护进程。 Webmin运行良好,就像打开服务器的shell一样,但我更喜欢能够从我设计的UI控制服务器操作,并向最终用户公开一些功能。
守护程序将从队列中获取操作并执行它们。但是,由于我将接受用户的输入,我想确保他们不被允许在特权shell命令中注入危险的东西。
这是一个例证我问题的片段:
def perform
system "usermod -p #{@options['shadow']} #{@options['username']}"
end
解释更多内容的要点:https://gist.github.com/773292
如果输入的典型逃避和消毒对于这种情况来说已经足够了,我并不乐观,作为一名设计师,我没有大量与安全相关的经验。 我知道这对我来说应该是显而易见的,但不是!
如何确保将创建和序列化操作的Web应用程序无法将危险文本传递到接收操作的特权进程中?
感谢您的帮助 ARB
答案 0 :(得分:19)
看起来你需要一个shell来做你正在做的事情。请参阅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
调用,所以你不必担心偷偷摸摸的shell技巧。
答案 1 :(得分:16)
如果您不仅需要退出状态,还需要结果,您可能需要使用Open3.popen3
:
require 'open3'
stdin, stdout, stderr = Open3.popen3('usermod', '-p', @options['shadow'], @options['username'])
stdout.gets
sterr.gets
答案 2 :(得分:2)
我建议查看'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
答案 3 :(得分:1)
这是一个老问题,但因为它几乎是你在谷歌搜索时会发现的唯一真正的答案,我想我会添加一个警告。系统的多参数版本似乎在Linux上相当安全,但它不在Windows上。
试试system "dir", "&", "echo", "hi!"
在Windows系统上。 dir和echo都会运行。 Echo当然也可以是一种无害的东西。
答案 4 :(得分:0)
我知道这是一个老话题,但SimonHürlimann轻轻触及了另一个选项。
关于这个主题的信息不多,我认为这可能会帮助其他有需要的人。
对于此示例,我们将使用Open3
,这使您能够同步或异步运行命令,并提供标准输出,标准,退出代码, 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”))遵循这些 简单的规则:
- 没有元字符
- 没有shell保留字,没有特殊的内置
- Ruby直接调用命令而不使用shell
您可以通过添加“;”来强制进行shell调用到字符串(因为 “;”是一个元字符)
如果传递格式错误的命令,这只会阻止Ruby抛出'spawn': No such file or directory
错误。相反,它会将它直接传递给内核,在那里优雅地解决错误并显示为STDERR而不是未捕获的异常。
答案 5 :(得分:0)
现代、安全且简单的解决方案(popen
将为您转义):
IO.popen(['usermod', '-p', @options['shadow'], @options['username']]).read
(#read
会在返回前关闭 IO)