2011-01-10 20 views
12

Estoy construyendo un demonio que le ayudará a controlar mi servidor (s). Webmin funciona bien, al igual que abrir un shell para el servidor, pero preferiría poder controlar las operaciones del servidor desde una interfaz de usuario que diseño, y también exponer algunas funcionalidades a los usuarios finales.La formación de comandos shell sanitarias o llamadas al sistema en Ruby

El demonio recogerá las acciones de una cola y ejecutarlas. 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 

un GIST que explica más: https://gist.github.com/773292

no soy positivo si el escape típico y desinfección de las entradas es suficiente para este caso, y siendo un 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 es así!

¿Cómo puedo asegurar que la aplicación web que va a crear y serializar las acciones no puede pasar de texto peligroso en el proceso privilegiado que recibe las acciones?

Gracias por la ayuda
arb

Respuesta

17

No se ve como que necesita una cáscara de lo que está haciendo. Consulte la documentación para system aquí: http://ruby-doc.org/core/classes/Kernel.html#M001441

Debe usar la segunda forma de system. Su ejemplo anterior se convertiría en:

system 'usermod', '-p', @options['shadow'], @options['username'] 

manera más elegante (OMI) para 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 Preocúpate por trucos de shell astuto.

+0

Así que, digamos si expuse esta clase, completamente sin filtrar (no lo haré) a un usuario final, y proporcionaron un hash shadow y luego "nombre de usuario; rm -rf /" como nombre de usuario: esto no tiene el efecto de borrar '/' – arbales

+2

Correcto. Los argumentos se pasan directamente al programa ejecutado. Puede verificar esto usted mismo. Intenta ejecutar el sistema 'ruby -e '* W (ls -l foo; rm -rf /)' ' – cam

+0

ah, bueno genial. Tiene perfecto sentido. Creo que tengo esta idea de que garantizar una aplicación segura es, naturalmente, más difícil de lo que es, más difícil que simples pasos y hechos, como si existiera un caso extremo peligroso para todo.Esto es probable porque nunca he leído/aprendido mucho al respecto. – arbales

15

Si no necesita sólo el estado de salida, sino también el resultado es probable que desee 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í: Getting output of system() calls in Ruby

2

yo sugeriría mirar en el módulo '' shellwords . Esta secuencia de comandos:

require 'shellwords' 
parts = ['echo', "'hello world'; !%& some stuff", 'and another argument'] 
command = Shellwords.shelljoin(parts) 
puts command 
output = `#{ command }` 
puts output 

emite el texto escapado y la salida esperada:

echo \'hello\ world\'\;\ \!\%\&\ some\ stuff and\ another\ argument 
'hello world'; !%& some stuff and another argument 
1

Ésta es una cuestión de edad, pero ya que es prácticamente la única respuesta real que encontrará cuando googlear pensé agregar una advertencia. La versión multi-argumento del sistema parece razonablemente segura en Linux, pero NO en Windows.

Pruebe system "dir", "&", "echo", "hi!" en un sistema Windows. Tanto dir como echo se ejecutarán. Eco podría, por supuesto, ser algo mucho menos inofensivo.

0

Sé que este es un hilo antiguo, 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 vamos a utilizar Open3 que le da la capacidad de ejecutar órdenes sincrónica o asincrónica, y proporciona la salida estándar , 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 se ejecuta otro programa. Puede especificar varios atributos, redirecciones, directorio actual, etc., del programa de la misma manera que para Process.spawn. (Fuente: Open3 Docs)

me eligió para dar formato a la salida como un objeto CommandStatus. Esto 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}" 

Durante la lectura de las memorias intermedias STDOUT/ERR, utilizo command_stdout = stdout_buffer if stdout_buffer.length > 0 para controlar si se asigna o no la variable command_stdout. Debe pasar nil en lugar de "" cuando no haya datos. Es más claro cuando se entregan datos más adelante.

Probablemente me haya visto usando command + ';'. La razón de esto se basa en la documentación de Kernel.exec (¿Qué es lo que utiliza popen3):

Si la cadena de la primera forma (exec ("orden")) sigue estas reglas simples :

  • no hay meta caracteres
  • ninguna palabra reservada cáscara y sin incorporado especial
  • Rubí invoca el comando directamente sin cáscara

Puede forzar la invocación del shell agregando ";" a la cadena (porque ";" es un personaje meta)

Esto simplemente impide que un rubí de lanzar un error de 'spawn': No such file or directory si pasa un comando incorrecto. 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.

Cuestiones relacionadas