ruby - rails - llamadas al sistema linux
FormaciĆ³n de comandos de shell sanitario o llamadas al sistema en Ruby (5)
No parece que necesites un caparazón para lo que estás haciendo. Consulte la documentación del system
aquí: http://ruby-doc.org/core/classes/Kernel.html#M001441
Deberías usar la segunda forma de system
. Su ejemplo anterior se convertiría en:
system ''usermod'', ''-p'', @options[''shadow''], @options[''username'']
Una manera más agradable (IMO) de escribir esto es:
system *%W(usermod -p #{@options[''shadow'']} #{@options[''username'']})
Los argumentos de esta manera se pasan directamente a la llamada execve
, por lo que no tiene que preocuparse por los trucos de shell astuto.
Estoy construyendo un daemon que me ayudará a administrar mi (s) servidor (es). Webmin funciona bien, al igual que abrir un shell para el servidor, pero prefiero poder controlar las operaciones del servidor desde una interfaz de usuario que diseño, y también exponer algunas funciones a los usuarios finales.
El daemon recogerá acciones de una cola y las ejecutará. Sin embargo, dado que aceptaré los comentarios de los usuarios, quiero asegurarme de que no se les permita inyectar algo peligroso en un comando de shell privilegiado.
Aquí hay un fragmento que ejemplifica mi problema:
def perform
system "usermod -p #{@options[''shadow'']} #{@options[''username'']}"
end
Una esencia que explica más: https://gist.github.com/773292
No estoy seguro si el escape típico y la desinfección de los insumos son suficientes para este caso, y al ser diseñador, no tengo mucha experiencia relacionada con la seguridad. Sé que esto es algo que probablemente debería ser obvio para mí , ¡pero no lo es!
¿Cómo puedo asegurarme de que la aplicación web que creará y serialice las acciones no pueda pasar texto peligroso al proceso privilegiado que recibe las acciones?
Gracias por la ayuda
arb
Sugiero buscar en el módulo ''shellwords''. Este script:
require ''shellwords''
parts = [''echo'', "''hello world''; !%& some stuff", ''and another argument'']
command = Shellwords.shelljoin( parts )
puts command
output = `#{ command }`
puts output
genera el texto escapado y el resultado esperado:
echo /'hello/ world/'/;/ /!/%/&/ some/ stuff and/ another/ argument
''hello world''; !%& some stuff and another argument
Esta es una vieja pregunta, pero ya que es casi la única respuesta real que encontrarás al buscar en Google, pensé que agregaría una advertencia. La versión multi-argumento del sistema parece razonablemente segura en Linux, pero NO en Windows.
Prueba el system "dir", "&", "echo", "hi!"
en un sistema de Windows. Tanto dir como echo se ejecutarán. Eco podría, por supuesto, ser algo mucho menos inofensivo.
Sé que este es un hilo viejo, pero hay otra opción que fue ligeramente tocada por Simon Hürlimann .
No hay mucha información sobre este tema y creo que esto podría ayudar a otros que lo necesiten.
Para este ejemplo, utilizaremos Open3
que le brinda la capacidad de ejecutar comandos de forma síncrona o asincrónica, y proporciona stdout , stderr , códigos de salida y PID .
Open3 le otorga acceso a stdout, stderr, códigos de salida y un hilo para esperar el proceso secundario cuando ejecuta otro programa. Puede especificar varios atributos, redirecciones, directorio actual, etc., del programa de la misma manera que para Process.spawn. ( Fuente: Open3 Docs )
Elegí formatear la salida como un objeto CommandStatus
. Este contiene nuestro stdout
, stderr
, pid
(Del hilo de trabajo) y 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}"
Mientras leo los búferes STDOUT / ERR, uso command_stdout = stdout_buffer if stdout_buffer.length > 0
para controlar si la variable command_stdout
está asignada o no. Debería pasar nil
lugar de ""
cuando no hay datos presentes. Es más claro cuando se entregan datos más adelante.
Probablemente me has notado usando el command + '';''
. La razón para esto se basa en la documentación de Kernel.exec (que es lo que utiliza popen3):
Si la cadena de la primera forma (exec ("comando")) sigue estas reglas simples:
- sin metacaracteres
- sin shell palabra reservada y sin función especial incorporada
- Ruby invoca el comando directamente sin shell
Puede forzar la invocación del shell agregando ";" a la cadena (porque ";" es un meta caracter)
Esto simplemente evita que un Ruby arroje un ''spawn'': No such file or directory
error de ''spawn'': No such file or directory
si pasa un comando mal formado. En su lugar, pasará directamente al núcleo donde el error se resolverá correctamente y aparecerá como STDERR en lugar de una excepción no detectada.
Si necesita no solo el estado de salida sino también el resultado, probablemente quiera utilizar Open3.popen3
:
require ''open3''
stdin, stdout, stderr = Open3.popen3(''usermod'', ''-p'', @options[''shadow''], @options[''username''])
stdout.gets
sterr.gets
Más información aquí: Obtención de salida de llamadas al sistema () en Ruby