2011-01-10 36 views
12

Tôi đang xây dựng một daemon sẽ giúp tôi quản lý (các) máy chủ của mình. Webmin hoạt động tốt, cũng như chỉ mở một trình bao cho máy chủ, nhưng tôi muốn có thể kiểm soát các hoạt động của máy chủ từ thiết kế UI I và cũng hiển thị một số chức năng cho người dùng cuối.Tạo các lệnh vỏ vệ sinh hoặc các cuộc gọi hệ thống trong Ruby

Trình tiện ích sẽ nhận các hành động từ hàng đợi và thực thi chúng. Tuy nhiên, vì tôi sẽ chấp nhận đầu vào từ người dùng, tôi muốn chắc chắn rằng họ không được phép tiêm thứ gì đó nguy hiểm vào một lệnh shell đặc quyền.

Dưới đây là một đoạn mà tiêu biểu cho vấn đề của tôi:

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

Một ý chính mà giải thích thêm: https://gist.github.com/773292

Tôi không tích cực nếu thoát điển hình và vệ sinh đầu vào là đủ cho trường hợp này, và trở thành một nhà thiết kế, tôi không có nhiều kinh nghiệm liên quan đến bảo mật. Tôi biết rằng đây là điều gì đó có lẽ nên rõ ràng với tôi, nhưng không phải của nó!

Làm cách nào để đảm bảo rằng ứng dụng web sẽ tạo và tuần tự hóa các hành động không thể chuyển văn bản nguy hiểm vào quá trình đặc quyền nhận hành động?

Thanks for the help
arb

Trả lời

17

Nó không giống như bạn cần một vỏ cho những gì bạn đang làm. Xem tài liệu cho system tại đây: http://ruby-doc.org/core/classes/Kernel.html#M001441

Bạn nên sử dụng biểu mẫu thứ hai là system. Ví dụ của bạn ở trên sẽ trở thành:

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

A (IMO) cách đẹp hơn để viết những dòng này là:

system *%W(usermod -p #{@options['shadow']} #{@options['username']}) 

Những lập luận theo cách này được truyền trực tiếp vào execve cuộc gọi, vì vậy bạn không cần phải lo lắng về thủ thuật vỏ lén lút.

+0

vì vậy, giả sử nếu tôi tiếp xúc với lớp này, hoàn toàn chưa được lọc (tôi sẽ không) cho người dùng cuối và họ đã cung cấp băm bóng và sau đó "tên người dùng; rm -rf /" làm tên người dùng - điều này sẽ không có tác dụng xóa bỏ '/' – arbales

+2

đúng. Các đối số được truyền trực tiếp đến chương trình đã thực hiện. Bạn có thể tự mình xác minh điều này. Hãy thử chạy hệ thống 'ruby -e' * W (ls -l foo; rm-rf /) '' – cam

+0

à, thật tuyệt. Nó có ý nghĩa hoàn hảo. Tôi nghĩ rằng tôi có suy nghĩ này để đảm bảo một ứng dụng được an toàn phải tự nhiên khó hơn nó, khó hơn các bước đơn giản và sự kiện, như thể tồn tại một trường hợp nguy hiểm cho mọi thứ.Điều này có thể vì tôi chưa bao giờ đọc/học được nhiều về nó. – arbales

15

Nếu bạn không cần chỉ là trạng thái thoát mà còn là kết quả mà bạn có thể muốn sử dụng Open3.popen3:

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

biết thêm thông tin ở đây: Getting output of system() calls in Ruby

2

tôi khuyên bạn nên nhìn vào các mô-đun 'shellwords' . Kịch bản này:

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

kết quả đầu ra văn bản đã trốn thoát và đầu ra mong đợi:

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

Đây là một câu hỏi cũ, nhưng vì nó khá nhiều câu trả lời thật duy nhất bạn sẽ tìm thấy khi googling Tôi nghĩ tôi 'd thêm một caveat. Phiên bản đa đối số của hệ thống có vẻ hợp lý an toàn trên Linux, nhưng nó KHÔNG phải trên Windows.

Hãy thử system "dir", "&", "echo", "hi!" trên hệ thống Windows. Cả hai dir và echo sẽ được chạy. Echo có thể tất nhiên cũng chỉ là một cái gì đó ít vô hại.

0

Tôi biết đây là một chuỗi cũ, nhưng có một tùy chọn khác đã được chạm nhẹ vào bởi Simon Hürlimann.

Không có nhiều thông tin về chủ đề này và tôi nghĩ điều này có thể giúp những người khác có nhu cầu.

Ví dụ này, chúng ta sẽ sử dụng Open3 mang đến cho bạn khả năng để chạy các lệnh đồng bộ hoặc không đồng bộ, và cung cấp stdout, stderr, mã thoátPID.

Open3 cấp cho bạn quyền truy cập vào stdout, stderr, mã thoát và chuỗi để chờ quá trình con khi chạy chương trình khác. Bạn có thể chỉ định các thuộc tính khác nhau, chuyển hướng, thư mục hiện tại, vv, của chương trình theo cách tương tự như đối với Process.spawn. (Nguồn: Open3 Docs)

Tôi đã chọn định dạng đầu ra thành đối tượng CommandStatus. Điều này chứa stdout, stderr, pid (của chủ đề công nhân) và 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}" 

Trong khi đọc bộ đệm STDOUT/ERR, tôi sử dụng command_stdout = stdout_buffer if stdout_buffer.length > 0 để kiểm soát xem các biến command_stdout được gán hay không. Bạn nên vượt qua nil thay vì "" khi không có dữ liệu. Nó rõ ràng hơn khi bàn giao dữ liệu sau này.

Bạn có thể nhận thấy tôi sử dụng command + ';'. Lý do cho điều này được dựa trên tài liệu từ Kernel.exec (Đó là những gì popen3 sử dụng):

Nếu chuỗi từ hình thức đầu tiên (exec ("lệnh")) sau những quy tắc đơn giản:

  • không có ký tự meta
  • không vỏ từ dành riêng và không đặc biệt được xây dựng trong
  • của Ruby gọi lệnh trực tiếp mà không cần vỏ

Bạn có thể bắt buộc trình bao bằng cách thêm ";" vào chuỗi (vì ";" là một nhân vật meta)

này chỉ ngăn một Ruby từ ném một lỗi 'spawn': No such file or directory nếu bạn vượt qua một lệnh bị thay đổi. Thay vào đó nó sẽ truyền nó thẳng đến hạt nhân, nơi lỗi sẽ được giải quyết một cách duyên dáng và xuất hiện như là STDERR thay vì một ngoại lệ chưa được bắt.

Các vấn đề liên quan