Tìm hiểu Object#taint và Object#trust trong Ruby
Chúng ta sẽ bắt đầu với một câu chuyện nhỏ: Walter Webcoder có một ý tưởng tuyệt vời cho một cổng thông tin điện tử: The Web Arithmetic Page. Được bao quanh bởi tất cả các loại liên kết toán học và các banner quảng cáo, những thứ sẽ làm cho anh ta giàu có là một khung đơn giản nằm chính giữa màn ...
Chúng ta sẽ bắt đầu với một câu chuyện nhỏ: Walter Webcoder có một ý tưởng tuyệt vời cho một cổng thông tin điện tử: The Web Arithmetic Page. Được bao quanh bởi tất cả các loại liên kết toán học và các banner quảng cáo, những thứ sẽ làm cho anh ta giàu có là một khung đơn giản nằm chính giữa màn hình, chứa một text field và một button. Người dùng sẽ nhập biểu thức toán học vào ô text field này, sau đó nhấn vào button, và kết quả sẽ hiện ra. Tất cả các máy tính của thế giới trở nên lỗi thời trong một sớm một chiều, Walter kiếm tiền và nghỉ hưu để cống hiến cuộc đời mình cho bộ sưu tập biển số xe. "Thực hiện các phép tính thật là đơn giản", Walter nghĩ như vậy. Anh ta truy cập nội dung của trường biểu mẫu sử dụng thư viện CGI của Ruby, và sử dụng phương thức eval để đánh giá chuỗi như một biểu thức.
require 'cgi' cgi = CGI::new("html4") # Fetch the value of the form field "expression" expr = cgi["expression"].to_s begin result = eval(expr) rescue Exception => detail # handle bad expressions end # display result back to user...
Khoảng 7 giây sau khi Walter đặt ứng dụng lên trực tuyến, một cậu bé 12 tuổi đến từ Waxahachie với vấn đề về tuyến tụy và không có một kiểu đời sống thực đã cho dòng system(rm *) vào form của Walter. Và giống như ứng dụng của anh ấy, giấc mơ của Walter cũng đã đổ vỡ. Walter đã học được một bài học quan trọng: Tất cả dữ liệu bên ngoài đều nguy hiểm, đừng để nó gắn với các giao diện có thể sửa đổi hệ thống của bạn. Trong trường hợp này, nội dung của trường biểu mẫu là dữ liệu bên ngoài, và lời gọi đến eval là vi phạm an toàn. May mắn thay, Ruby cung cấp các hỗ trợ để giảm nguy cơ rủi ro này. Tất cả các thông tin từ bên ngoài có thể được đánh dấu là bị ô nhiễm. Khi chạy ở chế độ an toàn (safe mode), các method nguy hiểm tiềm ẩn sẽ raise ra lỗi SecurityError nếu pass qua một đối tượng bị ô nhiễm (tainted object).
Safe levels
Trong Ruby, mỗi đối tượng đều có một vài flags và mang theo với nó, hai trong số đó là Trusted flag và Tainted flag. Những flag này được hành động phụ thuộc vào cái được gọi là safe level. Safe level được lưu trong biến $SAFE. Mỗi luồng và đoạn thực thi trong chuơng trình đều có một safe level. Save level nằm trong khoảng từ 0 đến 4, với 0 là thực thi an toàn và mức 4 là mức nguy hiểm. Safe level 0: ($SAFE = 0) Không kiểm tra việc sử dụng dữ liệu đựoc cung cấp bên ngoài (tainted) được thực hiện. Đây là chế độ mặc định của Ruby. Safe level 1: ($SAFE = 1) Ruby không cho phép sử dụng các dữ liệu ô nhiễm (tainted) bởi các hành động nguy hiểm tiềm ẩn. Safe level 2: ($SAFE = 2) Ruby cấm load các tệp chương trình từ các địa chỉ ghi toàn cục (globally writable locations). Safe level 3: ($SAFE = 3) Tất cả các đối tượng được tạo mới đều bị coi là ô nhiễm. Safe level 4: ($SAFE = 4) Ruby phân vùng chuơng trình đang chạy thành 2, những đối tượng Nontainted sẽ không được sửa đổi. Thông thường, điều này sẽ được sử dụng để tạo một sandbox: chương trình thiết lập một môi trường sử dụng mức $SAFE thấp hơn, sau đó đặt lại $SAFE = 4 để ngăn người các thay đổi tiếp theo cho môi trường đó.
Tainted Objects
Bất kì một đối tượng Ruby nào được lấy từ một số nguồn bên ngoài (ví dụ như chuỗi được đọc từ tệp hoặc một biến môi trường) sẽ tự động được đánh dấu là tainted. Nếu chương trình của bạn sử dụng một tainted object để lấy được một đối tượng mới, thì đối tượng mới đó cũng sẽ bị đánh dấu là tainted, như đoạn code dưới đây. Bất kì đối tượng với dữ liệu bên ngoài ở một nơi nào đó trong quá khứ cũng sẽ bị đánh dấu là tainted. Quá trình tainting này được thực hiện bất kể mức độ an toàn hiện tại.
# internal data # ============= x1 = "a string" x1.tainted? → false x2 = x1[2, 4] x2.tainted? → false x1 =~ /([a-z])/ → 0 $1.tainted? → false # external data # ============= y1 = ENV["HOME"] y1.tainted? → true y2 = y1[2, 4] y2.tainted? → true y1 =~ /([a-z])/ → 1 $1.tainted? → true
Bạn có thể buộc bất kì đối tượng nào trở thành tainted bằng cách gọi phương thức taint của nó. Nếu safe level nhỏ hơn 3, bạn có thể loại bỏ taint từ một đối tượng bằng cách gọi untaint.
Quay trở lại với câu chuyện bên trên, rõ ràng Walter đã nên chạy tập lệnh CGI của mình ở mức an toàn là 1. Điều này sẽ đưa ra một exception khi chương trình cố gắng truyền dữ liệu biểu mẫu để eval. Một khi điều này đã xảy ra, Walter sẽ có một số sự lựa chọn. Anh ta có thể đã chọn để thực hiện một bộ phân tích cú pháp biểu thức thích hợp, bỏ qua những rủi ro vốn có trong việc sử dụng eval. Tuy nhiên, có thể do lười biếng, có nhiều khả năng anh ta đã thực hiện một số kiểm tra đơn giản trên dữ liệu biểu mẫu, và untaint nó nếu nó trông vô hại.
require 'cgi'; $SAFE = 1 cgi = CGI::new("html4") expr = cgi["field"].to_s if expr =~ %r{^[-+*/dseE.()]*$} expr.untaint result = eval(expr) # display result back to user... else # display error message... end
Trust
Về cơ bản, trust đơn giản hơn taint, mặc dù chúng đều hướng tới chung một chức năng. Trust liên quan đến việc một object đến từ một nguồn đáng tin cậy hoặc không đáng tin cậy, cho dù nó ra bất kì điều gì thuộc safe level 4 hoặc ít hơn. Theo như rubydoc, thì trust sẽ tương đương với untaint và untrust sẽ tương đương với taint.