Rails Security (part1)
1. Giới thiệu
Việc tạo ra framworks dùng để phát triển các ứng dụng web đã hỗ trợ đắc lực cho các developer trong quá trình xây dựng các ứng dụng web. Một trong những tính năng ưu điểm đó là cơ chế bảo mật website. Thực tế, nếu chúng ta sử dụng các tính năng bảo mật có sẵn một cách đúng đắn trên một framework thì chúng ta cũng có thể xây dựng các ứng dụng bảo mật trên nhiều frameworks khác.
Ruby on Rails có một số các phương thức thông minh để bảo mật ứng dụng như chống lại tấn công bằng SQL (SQL injection) – một vấn đề tương đối khó. Có thể nói những ứng dụng được xây dựng trên Rails có mức độ bảo mật khá cao.
Nhìn chung, không có dạng bảo mật theo kiểu plug-n-play. Bảo mật phụ thuộc vào người sử dụng framework, đôi khi phụ thuộc vào phương thức phát triển sản phẩm và phụ thuộc vào tất cả các layers của môi trường ứng dụng web đang chạy như: back-end storage, tình trạng web server và bảo mật của bản thân ứng dụng đó.
Công ty Gartner (http://www.gartner.com/technology/home.jsp) đã ước tính rằng 75% các vụ tấn công là ở tầng ứng dụng web, trong số 300 trang web kiểm tra thì có tới 97% trong số đó có thể dễ dàng bị tấn công. Nguyên nhân là các ứng dụng web dễ bị tấn công hơn những ứng dụng khác vì chúng khá đơn giản để hiểu và thao tác thậm trí với cả những người dùng thông thường.
Các mối đe dọa của ứng dụng web chủ yếu xuất phát từ việc lấy tài khoản người dùng để pass qua khả năng kiểm soát truy cập của hệ thống, lấy được các dữ liệu nhạy cảm hoặc đăng các nội dung giả mạo để lừa đảo. Các hacker cũng có thể cài các virus Trojan horse hoặc các đoạn mã độc vào email để gửi tới người dùng. Mục đích chủ yếu của việc làm này là chiếm đoạt tài sản, kiếm tiền hoặc làm mất uy tín của thương hiệu để cạnh tranh. Cần ngăn ngừa các vụ tấn công, tối thiểu hóa thiệt hại khi bị tấn công và hạn chế các điểm truy cập nguy hiểm mà hacker có thể lợi dụng để chiếm quyền kiểm soát hệ thống. Để đạt được mục đích trên, trước tiên chúng ta phải hiểu một cách đầy đủ về các phương thức tấn công để tìm ra biện pháp ngăn ngừa và cách thức để đối phó với tấn công bảo mật. Đó chính là mục đích của bài viết này.
Để phát triển được các ứng dụng web bảo mật cao, chúng ta phải bảo mật trên tất cả các tầng của ứng dụng và một điều quan trọng là phải hiểu “kẻ thù” tương lai của hệ thống. Một trong những điều quan trọng nhất là chúng ta cần phải liên tục lên các diễn đàn, các blog để cập nhật thông tin về bảo mật. Trong bải viết này tôi sẽ đề cập đến vấn đề bảo mật trong session và quản lí người dùng.
2. Session
– Cách tốt nhất để hiểu về bảo mật ứng dụng web là tìm hiểu khái niệm session.
2.1 Session là gì?
“Bản chất HTTP là một giao thức không trạng thái, Session làm cho nó có trạng thái”.
Phần lớn các ứng dụng web cần lưu vết trạng thái cụ thể cho từng người dùng khi thao tác với ứng dụng. Nó có thể là thông tin giỏ hàng hoặc ID của user đang thực hiện thao tác với hệ thống. Tất cả mọi request lên server đều phải được xác thực định danh người dùng. Mỗi khi người dùng truy cập vào hệ thống, Rails sẽ tự động khởi tạo một session hoặc hệ thống sẽ kiểm tra session đang tồn tại nếu như user đã và đang thao tác với ứng dụng.
Một session luôn bao gồm một hash và session id – 32 kí tự để xác định hash. Thông tin của cookies sẽ được gửi về trình duyệt của clients (trong đó bao gồm session id), hay nói theo một cách khác: trình duyệt sẽ gửi nó tới server khi có bất cứ một request nào. Trong Rails, chúng ta có thể lưu trữ và lấy dữ liệu session bằng cách:
session[:user_id] = @current_user.id User.find(session[:user_id]
2.2 Session id
(*) “The session id is a 32 byte long MD5 hash value.”
Một session id là giá trị hash của một chuỗi ngẫu nhiên. Chuỗi ngẫu nhiên này được sinh ra theo thời gian hiện tại kết hợp với số 0 hoặc 1, process id do trình biên dịch của ruby sinh ra và một chuỗi cố định. Tại thời điểm hiện tại thì không có cách nào có thể giả mạo được session id trong Rails, kể cả việc tính toán kiểu “trâu bò” (brute-force). Cho tới nay, việc sử dụng MD5 có ưu điểm vượt trội trong mã hóa, nhưng về lí thuyết thì vẫn có thể tạo được một chuỗi đầu vào giống như chuỗi hash. Tuy nhiên cho tới ngày nay, chưa có một tấn công an ninh bảo mật nào mà có thể phá được mã hóa MD5.
2.3 Chiếm đoạt session
“Stealing a user’s session id lets an attacker use the web application in the victim’s name.”
Đa số các ứng dụng web đếu có một hệ thống xác thực: người dùng nhập tài khoản (username – password), hệ thống sẽ kiểm tra chúng và lưu trữ user id trong một session hash tương ứng, sau khi đăng nhập, session của một user đã có giá trị. Mỗi khi client gửi request tới server, toàn bộ thông tin về user sẽ được xác định bởi user id được lưu giữ trong session mà không cần phải xác thực thêm lần nào nữa. Session id lưu trữ trong cookie sẽ định danh session để phục vụ cho mục đích xác thực. Khi Hacker có thể bắt được cookie của bất kì người dùng nào đó thì chúng có thể connect tới ứng dụng web như là một user hợp lệ. Cách thức như vậy được gọi là chiếm đoạt session (hijack a session), dưới đây sẽ là giải pháp cho tấn công này:
2.3.1 Thắt chặt việc kiểm soát cookie trong mạng (ví dụ: mạng Lan không dây). Trong một mạng wireless LAN không được mã hóa sẽ rất dễ dàng để bắt được các connect của clients, đây là một trong các lí do chúng ta không nên làm việc tại các quán cafe. Từ Rails 3.1 trở đi, chúng ta sẽ thiết định các kết nối an toàn tới hệ thống bằng cách thiết lập kết nối SSL có tất cả các connetions:
config.force_ssl = true
2.3.2 Phần lớn người dùng không có thói quen xóa cookies sau khi làm việc với các thiết bị công cộng, vì vậy nếu tại lần làm việc cuối cùng user không log out ra khỏi hệ thống thì nguy cơ tài khoản sẽ bị chiếm đoạt bởi một user khác là rất cao. Trong trường hợp này thì ứng dụng nên thiết kế nút log-out nổi bật để gợi ý người dùng logout trong lần sử dụng cuối cùng.
2.3.3 Rất nhiều các đoạn mã độc (XSS) sẽ được chèn vào request với mục đích lấy cookie của người dùng. Chúng ta sẽ phải ngăn chặn tác dụng của những đoạn mã này (chi tiết sẽ được đề cập trong phần sau).
Mục đích chính của phần lớn các cuộc tấn công là để kiếm tiền. Theo báo cáo của tổ chức bảo mật internet toàn cầu (Symantec Global Internet Security Threat Report) thì mức thiệt hại cho mỗi tài khoản ngân hàng bị đánh cắp vào khoảng $10 – $1000 (phục thuộc vào số dư sẵn có), $0.4 – $20 đối với thẻ credit, $1-$8 cho các giao dịch với tài khoản online và $4 – $30 cho email passwords.
2.4 Những chỉ dẫn về session
Dưới đây là những chỉ dẫn chung khi sử dụng session:
Không được lưu trữ quá nhiều các đối tượng dữ liệu trong session, thay vào đó hãy lưu trữ dữ liệu của chúng trong database và lưu trữ id trong session. Điều này sẽ loại bỏ các vấn đề về đồng bộ hóa và sẽ không làm tăng không gian lưu trữ session. Những dữ liệu quan trọng không nên lưu trữ ở session. Nếu người dùng xóa cookies hoặc đóng trình duyệt thì những dữ liệu này sẽ bị mất, và người dùng có thể đọc được dữ liệu session lưu trữ ở phía client.
2.5. Lưu trữ session
“Rails provides several storage mechanisms for the session hashes. The most important is ActionDispatch::Session::CookieStore.”
Rails 2 giới thiệu một kiểu mặc định lưu trữ session là CookieStore. CookieStore lưu session hash trong cookie bên phía client, điều này sẽ giúp cải thiện tốc độ của ứng dụng rất nhiều, nhưng lựa chọn lưu trữ này đang gây nhiều tranh cãi và chúng ta sẽ phải còn suy nghĩ về khả năng bảo mật của nó:
Cookies được hiểu là một bộ nhớ có giới hạn kích thước là 4kB. Client có thể nhìn thấy bất cứ thử gì được lưu trữ trong session bởi vì nó được lưu trữ dưới dạng text (trên thực tế là mã hóa Base64-encoded, không phải mã hóa). Vì vậy đương nhiên là chúng ta không nên lữu trữ bất cứ dữ liệu bí mật nào trong đây. Để ngăn ngừa việc giả mạo session hash, một chuỗi số được tạo ra và được insert vào cuối cookie và truyền về phía client.
Điều này có nghia là việc bảo mật trong việc lưu trữ session phụ thuộc vào chuỗi bí mật này (hay chính xác hơn là thuật toán tạo ra chuỗi bí mật – mặc định sẽ là SHA1 và khả năng tương thích). Vì vậy không nên sử dụng một thuật toán bình thường để tạo chuỗi bí mật như kiểu sử dụng một từ trong từ điển hoặc một chuỗi ngắn hơn 30 kí tự. config.secret_key_base được sử dụng để chỉ định một key cho phép ứng dụng xác minh để tránh việc giả mạo session. Trong Rails 3 thì sử dụng secret_token thay thế cho secret_key_base và được sử dụng trong lớp EncryptedCookieStore.
2.6 Session Fixation
Ngoài việc lấy cắp session id của người dùng, kẻ tấn công có thể cố định được session id đã biết của người dùng. Kiểu tấn công này được gọi là “Session Fixation”. Mục đích của tấn công này nhằm cố định session id của người dùng đã biết trước, và buộc trình duyệt của người dùng phải sử dụng session id này.
2.6.1 Kẻ tấn công tạo một session id có giá trị: Hacker sẽ load trang login của ứng dụng web và lấy sesion id từ trong cookie.
2.6.2 Hacker sẽ chỉnh sửa các thông số trong session mà chúng nhận được: thời gian sống: ví dụ 20′.
2.6.3 Hacker sẽ buộc trình duyệt của người dùng phải sử dụng session id này để gửi tới server. Khi người dùng không thể thay đổi cookie, Hacker có thể chạy mộ đoạn mã Javascript ví dụ:
document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";
2.6.4 Hacker lừa các nạn nhân đến một trang mã nhiễm độc, mục đích của trang này là gửi session id cố định về trình duyệt của client bằng cách sử dụng các đoạn mã javascript.
2.6.5 Khi user truy cập tới ứng dụng web, về bản chất trình duyệt của user sẽ gửi session id cố định này lên server, server sẽ yêu cầu user đăng nhập.
2.6.6 Sau khi user đã nhập tài khoản và đăng nhập được vào ứng dụng, Hacker cũng sẽ sử dụng session id cố định để truy cập tới hệ thống và thực hiện hành vi phá hoại mà người dùng không hề hay biết.
2.7 Biện pháp đối phó với việc cố định session
“One line of code will protect you from session fixation.”
Biện pháp hiệu quả nhất trong trường hợp này là phát sinh một session mới và khai báo session cũ không hợp lệ sau khi người dùng đăng nhập hệ thống. Như vậy thì Hacker sẽ không thể xác định được một session id cố định. Tạo một new version trong Rails:
"reset_session"
Một giải pháp khác là lưu các thuộc tính của user vào session, xác minh chúng tại tất cả các request, từ chối truy cập nếu bất cứ thông tin nào không phù hợp. Các thuộc tính có thể là địa chỉ IP từ xa của user agent (web browser..).
3 User Management
Phần lớn mọi ứng dụng web đều có xử lí về chứng thực và xác minh. Thay vì việc tự xây dựng phân quyền thì chúng ta nên sử dụng các Plugins dùng chung và luôn cập nhật phiên bản mới nhất của chúng sẽ giúp hệ thống tăng tính bảo mật hơn.
Có một số Plugins xác thực được sử dụng trong Rails, trong số đó plugins phổ biến là devise và authlogic, chỉ lưu trữ passwords đã được mã hóa. Trong Rails 3.1 chúng ta có thể sử dụng phương thức has_secure_password với các tính năng tương tự.
Sau khi thực hiện đăng kí user, người dùng sẽ nhận được mã active để xác thực trong link được gửi tới email. Sau khi tài khoản đã được active, cột active_code trong database được set giá trị NULL.
http://localhost:3006/user/activate http://localhost:3006/user/activate?id=xxx
Cách tìm ra active_code của user như sau:
User.find_by_activation_code(params[:id])
Nếu như params[:id] là null thì sẽ tương tự như câu lệnh SQL sau:
"SELECT * FROM users WHERE (users.activation_code IS NULL) LIMIT 1"
3.1 Brute-Forcing Accounts
Tấn công brute-Force trên Account là việc thử nghiệm và tìm ra các lỗi đễ tấn công hệ thống bằng cách thoát khỏi việc xác thực của hệ thống. Chúng ta có thể chống lại tấn công này bằng cách sử dụng khi đăng nhập CAPTCHA.
Một danh sách username của ứng dụng có thể được sử dụng trong tấn công brute-force với các password tương ứng bởi phần lớn mọi người không sử dụng password thật sự an toàn. Đối với trường hợp này, kẻ tấn công có thể đạt được mục đích bằng cách dùng một chương trình sinh password và thử với tất cả user name của hệ thống, thực tế là chỉ trong vài phút chúng sẽ tìm ra.
Bởi lẽ đó, đa số ứng dụng đều hiển thị error message như “user name or password not correct” để hạn chế khả năng tấn công theo kiểu này. Tuy nhiên, phần lớn những người thiết kế hệ thống thường bỏ qua việc thiết kế trang “forgot-password” cho ứng dụng. Điều này cho phép những Hacker có thể sử dụng phương pháp brute-force để phá hoại.
Để giảm thiểu những tấn công loại như thế này, chúng ta nên xây dựng một trang khôi phục password trong trường hợp đăng nhập không thành công. Ngoài ra thì cũng nên yêu cầu nhập CAPTCHA sau một số lượng đăng nhập thất hại xuất phát từ một địa chỉ IP.
3.2 Account Hijacking
Có rất nhiều ứng dụng web có thể lấy cắp tài khoản một cách dễ dàng. Tại sao chúng ta lại để xảy ra vấn đề đó?
3.2.1 Passwords
Hãy cùng suy nghĩ về tính huống một Hacker có session của một user nào đó và chúng có thể đăng nhập được vào hệ thống. Nếu dễ dàng có thể thay đổi được password của tài khoản thì tài khoản sẽ bị mất chỉ trong vài clicks. Hoặc nếu việc thay đổi định dạng pasword có thể được thực hiện bằng tấn công CSRF thì Hackers có thể hướng client đến một trang giả mạo, để có thể thực hiện tấn công theo phương pháp CSRF. Giải pháp là tạo một form đổi password để chống lại tấn công CSRF, trong đó yêu cầu người dùng nhập password cũ và thay đổi nó.
3.2.2 Email
Hacker cũng có thể lấy được tài khoản bằng cách thay đổi địa chỉ email nhận. Sau khi thay đổi email, Hacker sẽ tới trang quên password và password mới sẽ được gủi đến email giả mạo, để khống chế trường hợp này, cần yêu cầu người dùng nhập password khi thay đổi địa chỉ email.
3.2.3 Other
Các dạng tấn công khác thì còn phụ thuộc vào từng ứng dụng web cụ thể. Trong rất nhiều trường hợp thì có thể bị tấn công bằng CSRF hoặc XSS. Ví dụ, một lỗ hổng trong gmail, kẻ tấn công sẽ thu hút nạn nhân đến trang web mà chúng điều khiển.
Kết quả là thiết lập cấu hình trong bộ lọc Gmail bị thay đổi. Nếu nạn nhân đăng nhập Gmail, kẻ tấn công sẽ thực hiện foward toàn bộ email để gửi tới mail của chúng.
3.3. CAPTCHAs
Một captcha là một cách kiểm tra respond để xác định rằng respond đó không được sinh ra từ một tính toán nào đó. Nó thường được sử dụng để ngăn ngừa các chương trình spam tự động bằng cách yêu cầu user nhập chuỗi kí tự được làm nhiễu ở trong ảnh.
Việc spam tự động là một vấn đề mà còn là một hương trình giúp đăng nhập tự động. Một CAPTCHA API phổ biến là reCAPTCHA, nó hiển thị 2 từ trong ảnh và yêu cầu người dùng nhập bằng tay.
reCaptcha là một Rails plugin có cách hoạt động giống như API. Trên thực tế hình ảnh trong captcha có độ góc cạnh, cong vênh và nhiều chi tiết làm nhiễu để chỉ có thể đọc được chuỗi số bằng mắt thường.
3.4 Logging
“Tell Rails not to put passwords in the log files”.
Mặc định thì Rails sẽ ghi logs tất cả các request truy cập đến như vậy thì trong log file có thể chứa những thông tin quan trọng của người dùng.
Khi thiết kế chức năng bảo mật hệ thống, chúng ta nên nghĩ tới việc các Hackers sẽ truy cập tới server quá nhiều lần. Việc mã hóa bí mật và passwords sẽ trở nên vô dụng
nếu chúng ta ghi những thông tin bí mật vào log. Chúng ta có thể kiểm tra bằng cách dùng filer đối với log. Ví dụ:
"config.filter_parameters <<; :password"
==> trong trường hợp này trạng thái của log sẽ là [FILTERED]
3.5 Regular Expressions
Khi xem xét về biểu thức chính quy, đối với Ruby thì có cách tiếp cận khá lỏng lẻo so với những ngôn ngữ khác trong việc quy định match bắt đầu hay kết thúc 1 chuỗi.
Chúng ta cùng đề cập trường hợp sau:
/^https?:\/\/[^\n]+$/i
Với biểu thức chính quy này thì validate đúng trên nhiều ngôn ngữ, tuy nhiên đối với Ruby thì nó lại thực hiện match theo dòng. Vì vậy trong trường hợp sau sẽ pass được qua validate này:
javascript:exploit_code();/* http://hi.com */
+) URL trên đã pass qua bộ lọc của biểu thức chính quy bởi chuỗi đã đc match ở dỏng thứ 2.
+) Chúng ta tưởng tượng rằng có một trang homepage show URL trên như sau:
link_to “Homepage”, @user.homepage
+) tưởng chừng với link như trên là bình thường, tuy nhiên khi người dùng cliked vào link trên thì hàm Javascript “exploit_code” sẽ được thực thi.
*** Giải pháp: Chúng ta nên thay thế \^ và \$ bằng \A và \z
/\Ahttps?:\/\/[^\n]+\z/i
Chính vì lí do bảo mật này nên Rails sử dụng validates_format_of exception nếu chúng ta sử dụng \^ và \$ thay cho \A và \z. Vì vậy nên trong trường hợp nào đó bạn bắt buộc phải sử dụng chúng ta sẽ làm như sau:
# content should include a line "Meanwhile" anywhere in the string validates :content, format: { with: /^Meanwhile$/, multiline: true }
Chúng ta luôn phải chú ý rằng, \^ và \$ trong Ruby luôn là bắt đầu và kết thúc của một dòng chứ ko phải của một chuỗi.
Tham khảo:
http://guides.rubyonrails.org/security.html
http://railssecurity.com/
http://www.rorsecurity.info/
http://www.railsdispatch.com/posts/security