Rails Dynamic Render to RCE
Xin chào tất cả các bạn. Vừa rồi mình có đọc được một số kiến thức khá hay về bảo mật qua loạt bài viết trên các diễn đàn công nghệ thế giới. Mình đã lược dịch và tìm hiểu thêm thông tin liên quan để chia sẻ lại, mong rằng bài viết sẽ mang lại thêm kiến thức mới cho các bạn. Nếu là một lập trình ...
Xin chào tất cả các bạn. Vừa rồi mình có đọc được một số kiến thức khá hay về bảo mật qua loạt bài viết trên các diễn đàn công nghệ thế giới. Mình đã lược dịch và tìm hiểu thêm thông tin liên quan để chia sẻ lại, mong rằng bài viết sẽ mang lại thêm kiến thức mới cho các bạn.
Nếu là một lập trình viên ROR, đã bao giờ bạn sử dụng dynamic render path chưa? Chẳng hạn như render params[:path], nếu đã sử dụng rồi rất có thể các bạn đã tạo ra một kẽ hở bảo mật đấy. Lỗ hổng này là "Remote Code Execution". Bạn đã nghe nói đến bao giờ chưa?.
Về cơ bản "Remote Code Execution" là cách thức tin tặc lợi dụng một lỗ hổng nào đó để truy cập từ xa vào máy tính của bạn và tiến hành chạy phần mềm mã độc (Malware). Hãy update phiên bản rails của bạn, hoặc refactor lại controller của bạn để ngăn chặn lỗ hổng này.
Vậy lỗ hổng này sinh ra như thế nào? Controller của Rails để render ra file view dựa vào các hàm được gọi. Ví dụ khi bạn gọi phương thức show, controller sẽ tìm kiếm trong thư mục view và render file show.html.erb. Trong nhiều trường hợp, chúng ta muốn render file với nhiều định dạng khác nhau, như XML, TEXT, JSON... hoặc 1 file view khác như ERB, HAML và Rails cung cấp 1 số phương thức để thực hiện việc này. Phương thức chúng ta hay dùng nhất đó là render, tài liệu của Rail cũng cung cấp khá chi tiết cách sử dụng phương thức này. Chúng ta có thể viết như sau
def show render params[:template] end
Thoạt nhìn, code này có vẻ rất đẹp và sáng sủa đúng không?. Chúng ta có thể hiểu là: Action show sẽ render ra template show (được truyền qua params :template). Tuy nhiên thực tế thì nó có vẻ không được rõ ràng cho lắm, Rails sẽ tìm ra template đó ở đâu, ở trong view, trong toàn thư mục root hay ở bất kì chỗ nào khác?, params nhận được là file name hay file name với phần mở rộng?. Có rất nhiều vấn đề với cách viết này. Hàm render này là 1 ví dụ điển hình của 1 chức năng cố gắng thực hiện nhiều hành động, và đây cũng chính là vấn đề.
Giả sử chúng ta muốn render file app/views/user/#{params[:template]}, như vậy khi chúng ta gõ địa chỉ app/views/user/dashboard, ta truyền vào tham số dashboard, và ta muốn render file dashboard với phần mở rộng bất kì, có thể là .html, .haml, .html.erb... Vậy nếu chúng ta truyền .../admin/dashboard vào thì sao? Khi truyền như thế, rails trả về error như sau
Sau khi xem xét lỗi này, ta thấy lỗi được sinh ra khi Rails đã tìm kiếm file trong project, bao gồm RAILS_ROOT/app/views.
Điều này có vẻ không tốt lắm, bởi chúng ta không bao giờ muốn render file từ thư mục root của hệ thống. Các hacker có thể truyền vào /etc/passwdvà có thể xem được file passwd của chúng ta. Như vậy khá là đáng lo ngại.
Nếu có thể đọc được file passwd, chúng ta có thể đọc được source code, các file config như file config/initializers/secrettoken.rb
Điều này xảy ra là do chúng ta đã sử dụng hàm render động bên trên, hàm này có 1 số kẽ hở và hacker có thể lợi dụng kẽ hở này để xem được file cầu hình, và source code, tuy nhiên đó chưa phải tất cả. Chúng có thể lợi dụng kẽ hở này để chạy lệnh shell trong ứng dụng của chúng ta. Vấn đề này xảy ra do chúng ta render file mà không có đường dẫn cụ thể cho file template, do vậy rails sẽ duyệt từng file một (như file code ERB), theo cách thông thường thì việc duyệt file này trả về 1 nội dung không thể chạy (excute) được, như CSV chẳng hạn. Do đó, thực tế là, bạn không chỉ đọc được source code và các file hệ thống khác, mà còn excute được code ruby nữa. Khá là nguy hiểm đúng không? Một yếu tố quan trọng trong cách tấn công này, đó là kỹ thuật "log file tainting". Rails được thiết kể để ghi log lại thông tin của các request cùng các tham số ở trong file enviroment's log (development.log chẳng hạn). File log này chứa thông tin dạng plain text, nó sẽ bị lợi dụng để tạo các request đến web app, chèn các dòng code Ruby có nghĩa vào trong tham số. Như ví dụ dưới đây, ta có thể chèn lệnh ls vào params và gửi request lên server.
Khi xem log file, ta có thế thấy kết quả là lệnh ls đã được thực thi nếu file dashboard được render. Chúng ta cũng có thể truyền vào một lệnh khác để xem được log file như trong hình:
Và như thế, chúng ta có thể chạy được khá nhiều lệnh mà web-server cho phép. Điều này là không tốt chút nào cả.
Vậy chúng ta xử lý tình huống này như thế nào?. Đầu tiền, bạn có thể sử dụng patch của Rails đưa ra sửa lỗi này. Tìm bản patch theo version Rails của bạn tại đây [https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00]
Ngoài ra, các bạn có thể xem 1 số giải pháp sau, nó có thể giúp code của chúng ta an toàn hơn.
- Hạn chế các file được duyệt. Đưa các file bạn muốn render vào hash, sau đó kiểm tra và render file, như vậy chúng ta có thể lọc và giới hạn các file được render.
def show template = params[:id] valid_templates = { "dashboard" => "dashboard", "profile" => "profile", "deals" => "deals" } if valid_templates.include?(template) render " #{valid_templates[template]}" else # throw exception or 404 end end
- Tìm kiếm các file trong 1 thư mục xác định
def show template = params[:id] d = Dir["myfolder/*.erb"] if d.include?("myfolder/#{template}.erb") render "myfolder/#{template}" else # throw exception or 404 end end
Bài viết của mình tham khảo nội dung chính ở địa chỉ và tìm kiếm thêm thông tin từ một số nguồn tài liệu khác, tuy nhiên do khả năng đọc và dịch còn nhiều hạn chế nên vẫn có nhiều thiếu sót. Rất mong nhận được sự đóng góp từ các bạn để bài viết hoàn thiện hơn. Cảm ơn các bạn đã dành thời gian theo dõi và hẹn gặp lại trong các bài viết sau (yeah).