Trao đổi với tôi

http://www.buidao.com

12/12/09

[Security] Bảo mật trong ngôn ngữ web (phần 1)

clip_image001Giao dịch thông qua Internet ngày càng phát triển, hàng loạt ngôn ngữ và giao thức thiết kế trang web cũng ra đời để phục vụ cho những mục đích nhất định, có thể kể các ngôn ngữ phổ biến như HTML, Perl, ASP, PHP... Bài viết này đề cập đến một vấn đề thường gặp là bảo mật và kinh nghiệm phòng tránh để tăng độ an toàn cho trang web.

HTML được phát triển từ 1989 và được sử dụng rộng rãi cho đến nay. Một trang HTML là một tệp dữ liệu dạng văn bản có phần mở rộng .htm hay .html, bao gồm những thành phần HTML. Khi người dùng xem một trang HTML, trình duyệt sẽ có nhiệm vụ phân tích và hiển thị thông tin tương ứng với những thành phần đó. Ở đây chúng ta chỉ phân tích về khía cạnh bảo mật của các thành phần HTML thông dụng.

1. <form>

Đây là đối tượng không thể thiếu khi trang web của bạn cần nhận thông tin từ người sử dụng. Tuy nhiên xét về khía cạnh bảo mật, các thành phần cho phép người dùng nhập thông tin lại là khởi đầu cho nguy cơ bị khai thác – hay nói cách khác là mục tiêu đầu tiên mà hacker nhắm đến. Ở các chương trình thiết kế không tốt, nếu thông tin nhận từ người dùng không phù hợp định dạng hoặc không như mong muốn chúng có thể tạo nên những kết quả khó lường. Chúng ta sẽ cùng phân tích một ví dụ đơn giản về một trang HTML có form cho phép nhập tên người dùng và mật khẩu, địa chỉ truy cập là http://www.sampleweb.com/login.html có nội dung như sau:

<html> 

<form method=POST action=”admin/login.asp>

Hãy nhập thông tin đăng nhập vào trang quản trị:<br>

Username:

<input value=”” size=40 maxlength=80 name=”username><br>

Password:

<input value=”” size=40 maxlength=80 name=”password>

<input type=hidden name=”user_rolevalue=1>

<input type=hidden name=”triesvalue=3>

<input type=submit value=”Login>

</form>

</html>




HTML form ở ví dụ trên thoạt nhìn rất đơn giản nhưng lại tiềm ẩn nhiều nguy cơ. Giả định khi người dùng truy cập vào địa chỉ http://www.sampleweb.com/login.html và nhập tên người dùng là “admin”, mật khẩu là “testpassword”, sau đó nhấn login. Nhiệm vụ của form là nhận tên người dùng và mật khẩu rồi dùng phương thức POST để gửi yêu cầu đến một ứng dụng web kiểm tra login ở máy chủ có tên là “login.asp”. Tuy nhiên, nếu phân tích kĩ hơn ta có thể thấy trang login này bộc lộ nhiều sơ hở mà hacker có thể lợi dụng. Điều rõ ràng nhất ta nhận thấy khi người dùng truy cập vào địa chỉ http:// thì dữ liệu được truyền đi trên mạng từ browser của người dùng đến máy chủ dưới dạng plaintext, tức là không được mã hóa. Như vậy chỉ cần có trong tay một chương trình network sniffer (xem lén nội dung các gói tin truyền trên mạng) như Ethereal và một chút may mắn cộng kiên nhẫn, một tay hacker hạng xoàng ngồi ở một máy client khác cũng có thể bắt được các thông điệp HTML truyền đi từ máy người dùng trên. Dưới đây là nội dung một thông điệp như vậy:

[ POST /admin/login.asp HTTP/1.1Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*Accept-Language: en-usContent-Type: application/x-www-form-urlencodedAccept-Encoding: gzip, deflateUser-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)Host: www.sampleweb.comContent-Length: 68Connection: Keep-AliveCache-Control: no-cache 

username=admin&password=testpassword&%3Fuser_role%3F=1&%3Ftries%3F=3

Date: Mon, 30 May 2005 09:00:34 GMTServer: ApacheExpires: Mon, 30 May 2005 10:00:34 GMTConnection: closeTransfer-Encoding: chunkedContent-Type: text/html
]




 

Nội dung gói tin bắt được ở phần màu đậm cho thấy rõ yêu cầu (POST request) gửi đi bao gồm tên người dùng (username), mật khẩu (password), quyền hạn (user_role), số lần đăng nhập thử (tries) và các thông tin quan trọng khác. Sẽ thật sự nguy hiểm hơn nếu bằng cách nào đó hacker chiếm được quyền điều khiển của một máy chủ gateway (cổng ra Internet) và chạy sniffer trên máy này, khi đó tất cả thông tin từ các trạm làm việc trong mạng nội bộ sẽ bị tóm bởi sniffer này trước khi chúng được gửi ra Internet. Với một tay hacker có hạng, gói tin bắt được có thể bị thay đổi vài thông tin quan trọng sau đó tiếp tục được gửi đến máy chủ và thông tin hồi đáp từ máy chủ có thể sẽ không như người dùng thật sự mong muốn nhưng lại thoả mãn yêu cầu của hacker.

Kinh nghiệm phòng tránh:

Trước khi bàn đến chuyện phòng tránh, chúng ta cần hiểu rằng không có hệ thống nào là bảo mật hoàn hảo, cũng không có biện pháp nào phòng tránh tuyệt đối. Những nỗ lực đưa ra chỉ có hiệu lực ở mức độ nhất định và tồn tại trong một thời gian nhất định đến khi người ta tìm ra cách mới để vượt qua nó.

Để bảo đảm tính an toàn cao cho dữ liệu trao đổi qua lại giữa người dùng và server, các nhà quản trị thường chọn giải pháp sử dụng SSL (Secure Socket Layer) cho trang web (trong phạm vi bài viết này tôi không mô tả chi tiết về SSL và cách thức cài đặt máy chủ hỗ trợ SSL, bạn đọc có thể tham khảo các tài liệu trên Internet). Khi đó người sử dụng sẽ truy cập vào trang web theo địa chỉ https:// thay vì http:// và những thông tin trao đổi sẽ được mã hoá trên đường truyền với mức độ an toàn rất cao.

Quay lại chủ đề hiện tại, ở phía trình duyệt người dùng, các thông tin được truyền đi từ HTML form cần được mã hóa trước khi gửi đi (trang đăng nhập của Yahoo mail cũng áp dụng cách thức này). Như vậy trang web cần cài đặt thêm mã lệnh để mã hóa dữ liệu người dùng nhập vào ở mức client trước khi gửi yêu cầu đến máy chủ. Đối với các giá trị ngầm định được đặt sẵn như trong ví dụ trên:

<input type=hidden name=”user_rolevalue=1> 

<input type=hidden name=”triesvalue=3>




Nếu trang web của bạn thật sự cần những thông số ngầm định như vậy, hãy mã hóa chúng khi thiết kế và khi server nhận được yêu cầu, server sẽ giải mã các tham số trước khi xử lý và trả kết quả về cho người dùng. Ví dụ:

<input type=hidden name=”user_rolevalue=!”#$%&gt; 

&lt;input type=hidden name=”triesvalue=!”#$%Xxls3>



Ở phía server, khi thiết kế ứng dụng bạn luôn luôn nhớ nguyên tắc sau:

Dữ liệu nhận được từ client luôn có thể bị thay đổi ngoài ý muốn, đừng bao giờ tin cậy hoàn toàn các dữ liệu này. Ứng dụng của bạn cần lọc và kiểm tra chặt chẽ các dữ liệu này ngay khi nhận được trước khi tiến hành các bước xử lý tiếp theo.

2. <form action>

Thuộc tính action dùng để chỉ định một ứng dụng web trên máy chủ cho đối tượng form có nhiệm vụ nhận, xử lý thông tin và trả kết quả về cho người dùng. Ví dụ: <form action = “admin/login.asp”> ... Khi biết được tên chương trình, người ta có thể tìm hiểu thêm các thông tin có giá trị về web server, về thư mục hiện hành của chương trình.

Thử phân tích thuộc tính action trong ví dụ trên, trước hết ta có thể thấy trang web đăng nhập được đặt tại thư mục /admin trên thư mục gốc của website (web root) - nhiều nhà thiết kế web có thói quen đặt các tệp dữ liệu quan trọng như các tệp cơ sở dữ liệu, danh sách khách hàng... vào thư mục /admin hoặc /database và đặt tên rất dễ đoán, điều này sẽ tạo cơ hội cho hacker thử tài. Kế đến, ta biết được ngôn ngữ phía server sử dụng là ASP, từ đó có thể suy ra máy chủ sử dụng là IIS và hệ điều hành Windows Server. Với kinh nghiệm mày mò trên net, hacker có thể dễ dàng tìm được rất nhiều lỗ hổng bảo mật liên quan đến các máy chủ Windows IIS và ngôn ngữ ASP.

Kinh nghiệm phòng tránh:

Kết hợp với các phương pháp ở mức HTML form vừa đề cập, ở mức <form action> cần hạn chế tối đa việc chỉ định tường minh tên thư mục trên server như /admin... thay vào đó hãy tạo các thư mục ảo (virtual directory) trên web server và cài đặt cấu hình web server để chuyển hướng các yêu cầu đến URL cụ thể vào thư mục thật sự trên máy chủ. Ngoài ra, hãy cài đặt và sử dụng giao thức https khi gọi một ứng dụng web trên máy chủ, ví dụ <form action = “https://www.sampleweb.com/admin/login.asp”> . Điều này đảm bảo thông tin truyền đi trong yêu cầu được mã hóa và giảm thiểu khả năng bị khai thác.

3. <form method>

Phương thức của form, định nghĩa cách thức dùng để gửi thông tin người dùng nhập vào đến máy chủ web cho chương trình xử lý. Khi hiểu được cách thức gửi thông tin (POST, GET, PUT...), hacker có thể theo dõi và “bắt” các thông tin được gửi trên mạng, thậm chí có thể thay đổi chúng và gửi đi với giá trị khác để tạo ra những kết quả mà quản trị viên không mong muốn, đặc biệt là các thông tin liên quan đến quá trình đăng nhập vào trang web. Thực tế trên mạng có rất nhiều công cụ được tạo ra để phục vụ mục đích này, điển hình như Brutus-AET hoặc WebCracker có khả năng tạo tự động các yêu cầu GET hoặc PUT chứa username và password thử đăng nhập vào trang web từ một danh sách các username và password dạng từ điển có sẵn.

Trong các phương thức gửi yêu cầu đến ứng dụng trên server, POST và GET được sử dụng hầu hết ở các HTML form. Xét về mặt bảo mật, điểm khác biệt giữa 2 phương thức này ở cách thức hoạt động của chúng, POST request được đặt trong phần header của thông điệp HTML gửi đến server (xem lại phần bắt gói tin ở phần HTML form ở trên) do đó request này sẽ không được ghi lại (log) trên server và client, trong khi đó GET request sẽ được ghi lại vì các thông tin gửi đi được đặt ngay trên URL, ví dụ:

http://www.sampleweb.com/admin/login.asp?username=admin&password=
testpassword&%3Fuser_role%3F=1&%3Ftries%3F=3

Những yêu cầu đến ứng dụng web trên server thông qua phương thức GET sẽ được ghi lại ở các nơi sau:

Nhật kí truy cập (access log) của web server và proxy server

Vùng dữ liệu tạm (temporary cache và history) của trình duyệt phía người dùng

Nhật kí truy cập của các chương trình tường lửa (firewall) ở cả server và client.

Kinh nghiệm phòng tránh:

Việc lưu lại các thông tin và yêu cầu truy cập tạo điều kiện thuận lợi cho hacker dò tìm các thông tin nhạy cảm, do đó để hạn chế khả năng bị xem lén thông tin trên các log ta nên lưu ý như sau:

Tránh sử dụng phương thức GET trong HTML form, hãy sử dụng POST.

Ứng dụng web phía server được viết để nhận dữ liệu theo phương thức cụ thể nào (POST, GET, PUT...) cần kiểm tra yêu cầu nhận được có đúng được gửi theo phương thức được hỗ trợ không, nếu không cần thông báo lỗi cho người dùng. Điều này giúp tránh trường hợp hacker lợi dụng các công cụ “dò” web bằng cách thử liên tục các giá trị tên người dùng và mật khẩu thông qua các request dạng GET hoặc PUT.

4. <script language=<variable>>

Biến truyền vào là ngôn ngữ script phía client, chẳng hạn như javascript, vbscript, XML... Ở các trang đăng nhập hệ thống hoặc các trang buôn bán trực tuyến, người thiết kế web thường dùng các script chèn vào trang html để kiểm tra tính hợp lệ của dữ liệu phía người dùng (client). Bằng cách thay đổi loại ngôn ngữ hoặc thậm chí cài đặt thêm mã bằng ngôn ngữ khác, hacker có thể vượt qua các bộ lọc kiểm tra được cài đặt sẵn trong trang web.

Lấy ví dụ HTML form ở trên, để tránh trường hợp người dùng nhập các kí tự đặc biệt vào cho trường dữ liệu tên và mật khẩu, người ta viết thêm đoạn script kiểm tra như sau:

<html> 

<script language=”javascript>

function CheckForm(){

đoạn mã kiểm tra username để loại các kí tự đặc biệt như: - + = -- “”...

đoạn mã kiểm tra password để loại các kí tự đặc biệt như: - + = --“” ...

}

</script>

<form method=POST action=”admin/login.asponsubmit = “return CheckForm();”>
....

<input type=submit value=”Login>

</form>

</html>





 


Như vậy, chỉ cần tạo 1 bản sao của trang web đăng nhập dùng chức năng View Source của browser và lưu lại trên đĩa, sau đó sửa lại nội dung ở các phần kiểm tra và địa chỉ trình thực thi trong HTML form thì hacker có thể gửi tên và mật khẩu với nội dung mong muốn đến web server mà không còn chịu sự kiểm tra ở mức client.

Kinh nghiệm phòng tránh:

Trong trường hợp này, ngoài việc kiểm tra ở phía client, trình thực thi cũng cần kiểm tra bổ sung ở phía server khi nhận được request. Ở ví dụ trên, trong trường hợp hacker cố tình sửa đổi trang đăng nhập để có thể gửi đi yêu cầu có mật khẩu chứa các kí tự đặc biệt ví dụ như “testor 1=1--” thì trang login.asp phía server cũng có thể lọc lại các giá trị không hợp lệ này và thông báo lỗi cho người dùng.

5. <input>

Thành phần textbox để người dùng nhập thông tin vào HTML form. Nếu ứng dụng web không xử lý tốt các thông tin nhập vào, hacker có thể lợi dụng nhập vào các giá trị đặc biệt đánh lừa hệ thống và tạo nên những kết quả không mong muốn. Dưới đây là một số kiểu khai thác thường được hacker sử dụng đối với đối tượng <input>:

Truyền những giá trị đặc biệt yêu cầu hệ thống cơ sở dữ liệu thực thi lệnh khai thác dữ liệu. Ví dụ giá trị username và password dạng “testor 1=1--” dùng trong kĩ thuật SQL Injection (tham khảo bài viết về SQL Injection, TGVT A tháng 12/2002 - trang 96) giúp hacker dễ dàng đăng nhập dưới quyền cấp cao, từ đó có thể tiếp tục lợi dụng thực thi các lệnh hệ thống, điều khiển máy chủ.

Chèn script dạng Cross-site-scripting (CSS/XSS)

Ví dụ: http://www.testweb.com/input.asp?data=<script>alert(Kiểm tra);</script>

hoặc: http://www.testweb.com/input.asp?data=<script>alert(document.cookie)</script>

Hacker có thể lợi dụng dạng sơ hở này để lấy trộm cookies (thông tin người dùng và thông tin truy cập các trang web) của người dùng đang truy cập.

Khai thác lỗi tràn bộ đệm (Buffer over flow). Trong trường hợp ứng dụng web không kiểm tra chiều dài dữ liệu nhập, hacker có thể truyền vào một giá trị rất lớn (ví dụ 1 chuỗi chứa một triệu kí tự A) gây lỗi tràn bộ đệm phía server. Hậu quả có thể làm máy chủ ngừng hoạt động, thậm chí chạy những mã nguy hiểm do hacker đính kèm.

<input type=hidden>

Kiểu đối tượng input không hiện trên trình duyệt. Một số trang web dùng đối tượng input với thuộc tính hidden để chứa giá trị mặc định như giá mua hàng ở các trang shopping hay gán quyền cho đối tượng dữ liệu. Mặc dù là kiểu ẩn (hidden), không hiện ra khi người sử dụng vào trang web, nhưng đối tượng dạng này không thật sự an toàn và có thể dễ dàng bị xem nội dung bằng thao tác xem mã nguồn (View > Source). Lợi dụng đặc điểm này, hacker có thể thay đổi giá trị này thành giá trị mong muốn và lặp lại thao tác gửi đến máy chủ web. Ở các trang web buôn bán, nếu không có những xử lý giá trị phía server, hacker có thể mua được những món hàng với giá giảm đáng kể hoặc không tốn đồng nào.

Thực tế cho thấy đã có một số trang web bị lợi dụng trường hợp này, nếu giả định role=1 là người dùng bình thường, role=0 là người quản trị (administrator) và nếu khi nhận được yêu cầu ứng dụng web phía server chỉ đơn giản mang những giá trị này so sánh với danh sách đặc quyền trong cơ sở dữ liệu và trích ra dữ liệu tương ứng với quyền đó thì một đối tượng nhập liệu mang giá trị định sẵn dạng:

<input type=hidden name=”user_role” value=1>

hoàn toàn có thể bị thay đổi thành:

<input type=hidden name=”user_role” value=0>

Và khi đăng nhập, người dùng đó nghiễm nhiên mang đặc quyền người quản trị.

<input maxlength=<variable>>, <input size=<variable>>

Giá trị maxlength và size xác định chiều dài của giá trị nhập vào đối tượng input. Hacker có thể thay đổi giá trị maxlength cho phép nhập vào những giá trị hoặc chuỗi rất dài quá khả năng xử lý của ứng dụng phía server. Nếu không được xử lý thích hợp, chúng có thể gây nên những lỗi như tràn bộ đệm để thực thi các lệnh điều khiển hoặc thậm chí có thể làm ngừng hoạt động của máy chủ web.

Kinh nghiệm phòng tránh:

Tương tự như ở phần <script> ở trên, các trang web cần cài đặt các đoạn mã kiểm tra tính hợp lệ của dữ liệu nhập vào từ người dùng cho cả dạng <input> và <input type=hidden> nhằm hạn chế tối đa khả năng bị thay đổi thông tin ngoài ý muốn, bao gồm:

Lọc các kí tự đặc biệt để tránh bị chèn thêm các mã lệnh nguy hiểm như dạng Code Injection hoặc SQL Injection. Các đoạn mã lọc cần phải được cài đặt ở cả client và server.

Kiểm tra chiều dài dữ liệu nhận được phía server để tránh trường hợp tràn bộ đệm.

Tránh không dùng dạng đối tượng input ẩn để chứa giá trị phân đặc quyền người dùng ở các trang đăng nhập vào web.

Dưới đây là 1 đoạn mã ví dụ đơn giản sử dụng VBScript để kiểm tra tính toàn vẹn của dữ liệu ở trang đăng nhập, đoạn mã này sẽ được thực thi phía trình duyệt người dùng (lưu ý, bạn cũng cần những đoạn mã kiểm tra tương tự cài đặt trong ứng dụng web phía server).

<html> 

...

<script Language=”VBScript>

<!--

function Form1_onsubmit()

Set theForm = document.FrontPage_Form1

If (theForm.Username.value = “”) Then

MsgBox “Hãy nhập tên người dùng”, 0, “Báo lỗi”

theForm.Username.focus()

Form1_onsubmit = False

Exit Function

End If

If (Len(theForm.Password.value) < 12) Then

MsgBox “Mật khẩu không hợp lệ”, 0, “Báo lỗi”

theForm.Password.focus()

Form1_onsubmit = False

Exit Function

End If

checkOK = “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789”

checkStr = theForm.Username.value

allValid = True

For i = 1 to len(checkStr)

ch = Mid(checkStr, i, 1)

If (InStr(checkOK, ch) = 0) Then

allValid = False

Exit For

End If

Next

If (Not allValid) Then

MsgBox “Tên người dùng không hợp lệ. Hãy nhập lại.”, 0, “Báo lỗi”

theForm.Username.focus()

Form1_onsubmit = False

Exit Function

End If

checkStr = theForm.Password.value

allValid = True

For i = 1 to len(checkStr)

ch = Mid(checkStr, i, 1)

If (InStr(checkOK, ch) = 0) Then

allValid = False

Exit For

End If

Next

If (Not allValid) Then

MsgBox “Mật khẩu không hợp lệ. Hãy nhập lại.”, 0, “Báo lỗi”

theForm.Password.focus()

Form1_onsubmit = False

Exit Function

End If

Form1_onsubmit = True

End Function

-->

</script>

<form METHOD=”POSTACTION=”kiemtra.aspname=”Form1>

...

</form>

</html>




Nguyễn Văn Sơn
Global Cybersoft Vietnam
(theo PC World VN