Làm thế nào để tiếp cận hiệu quả Pentest trong kiểm thử bảo mật ứng dụng Web (Phần I)
Bài đăng này đã không được cập nhật trong 7 năm
Như chúng ta thấy yêu cầu đối với một tester càng ngày càng cao, bên cạnh việc kiểm thử chức năng dưới vai trò người dùng đầu cuối, nhiều dự án đã và đang đòi hỏi các tester cần nhiều kỹ hơn test chuyên sâu vào các vấn đề phức tạp hơn như kiểm thử bảo mật, dữ liệu, ..... Một trong những chủ đề kiểm thử không còn xa lạ nhưng luôn khiến một kiểm thử viên, thậm chí các lập trình viên khá e ngại đó chính là Penetration Testing. Sau khi đọc rất nhiều tài liệu về Penetration Testing, mình nhận thấy đây là một chuyên đề khá thú vị nhưng rất rộng và dễ bị "loạn" nên muốn tìm cách tổng hợp các vấn đề cần khai thác và chọn lọc các bài tiếng anh về chủ đề này để dịch ra theo cách hiểu của mình sao cho kiểm thử viên có thể tiếp cận nhanh nhất và sâu dần theo từng chủ đề nhỏ để có thể khi cần sẽ ứng dụng vào dự án của mình phù hợp nhất.
Khái niệm tóm tắt
Có 2 khái niệm mình nghĩ mọi người cần nắm bắt khi tiếp cận Penetration testing chính là:
Penetration testing là gì? Penetration testing hay còn gọi là Pentest là quá trình kiểm thử bảo mật cho các ứng dụng web bằng cách giả lập các cuộc tấn công vào website để tìm kiếm và phát hiện các lỗ hổng, các vấn đề bảo mật trong trang web. Kiểm thử viên sẽ đóng vai trò là các hacker và giả lập các tấn công vào các trang web mục tiêu.
OWASP là gì? OWASP là chuẩn phục vụ cho việc kiểm thử của Pentester do tổ chức phi lợi nhuận Open Web Application Security Project đưa ra nhằm phục vụ cho công việc pentest hiệu quả và chi tiết, liệt kê các công việc mà người kiểm thử nên làm và các checklist để thực hiện các công việc. Theo [thống kê 2017 của OWASP | https://www.owasp.org/index.php/Top_10_2007-Injection_Flaws] , Top 10 lỗi nguy hiểm về bảo mật ứng dụng web, bao gồm:
- Injection
- Broken Authentication and Session Management
- Cross-Site Scripting (XSS)
- Broken Access Control (As it was in 2004)
- Security Misconfiguration
- Sensitive Data Exposure
- Insufficient Attack Protection (NEW)
- Cross-Site Request Forgery (CSRF)
- Using Components with Known Vulnerabilities
- Underprotected APIs (NEW)
Dựa theo danh sách này, chuỗi bài viết của mình sẽ đi sâu vào từng lỗi nguy hiểm về bảo mật ứng dụng web và sau đó là các tool chúng ta có thể tận dụng để tối ưu hóa việc kiểm thử này. Phần I của chuỗi bài viết này sẽ dựa vào https://www.owasp.org/index.php/Testing_for_SQL_Injection_(OTG-INPVAL-005) để dịch ra những phần về kiến thức chung cần có, giới thiệu kỹ thuật truy lỗi cũng như các kỹ thuật SQL Injection chuẩn.
Injection / SQL Injection
Thiết kế một trang web luôn đòi hỏi các lập trình viên phải xem xét các vấn đề an toàn nhằm giảm thiểu tối đa các lỗ hỏng có khả năng bị tấn công bởi hacker. Một trong những nguy cơ tiềm ẩn mà các hacker thường chú ý chính là các đoạn mã của ứng dụng và họ thường lợi dụng lỗ hỏng này để chèn vào các câu lệnh truy vấn SQL nhằm xâm nhập cơ sở dữ liệu của các ứng dụng web. Tấn công bằng kỹ thuật chèn SQL có thể đọc được các dữ liệu nhạy cảm từ CSDL, chỉnh sữa dữ liệu (chèn thêm/thay đổi hay xóa đi), thực hiện các quyền admin trên cơ sở dữ liệu như tắt đi DBMS, khôi phục nội dung một file nào đó trên hệ thống file DBMS hay viết thêm các file vào hệ thống tập tin, thậm chí trong vài trường hợp các hacker còn xuất ra các lệnh vào hệ điều hành. Vì thế với các ứng dụng web có CSDL được quản lý bằng SQL Server, Oracle, BD2, hay Sysbase, thì kiểm thử SQL Injection rất quan trọng.
Thông tin chung về SQL Injection
Cụ thể hơn, ta có thể hiểu rằng các ứng dụng web dựng nên các câu truy vấn SQL liên quan đến cú pháp SQL mà các lập trình viên viết ra sẽ được kết hợp cùng với cơ sở dữ liệu người dùng. Chẳng hạn như câu lệnh sau:
select title, text from news where id=$id
Ở câu lệnh trên, biến $id chính là dữ liệu được cung cấp cho người dùng, và phần còn lại chính là một phần SQL mà lập trình viên viết ra. Các hacker có thể tận dụng điều này để thay đổi logic của câu lệnh SQL bằng cách chèn thay đổi dữ liệu thành "10 or 1=1" vào mệnh đề WHERE.
select title, text from news where id=10 or 1=1
Các tấn công dạng SQL Injection có thể được phân thành 3 loại sau:
- Inband: dữ liệu được lấy bằng cách sử dụng giống kênh được dùng để chèn mã SQL vào. Đây là cách trực diện nhất, trong đó dữ liệu được truy xuất sẽ được thể hiện trực tiếp trên trang web ứng dụng.
- Out-of-band: dữ liệu được truy vấn bằng cách sử dụng một kênh khác như gửi email kèm theo có đó là câu truy vấn sẽ được tạo ra và gửi về.
- Inferential hay Blind: không có truyền dữ liệu thực tế nào, nhưng kiểm thử viên có thể dựng lại thông tin bằng cách gửi các yêu cầu cụ thể và quan sát hành vi nhận về của server dữ liệu.
Một cuộc tấn công SQL Injection thành công đòi hỏi kẻ tấn công phải tạo ra một truy vấn SQL đúng cú pháp. Nếu ứng dụng trả về một message lỗi được tạo ra từ một truy vấn không đúng, nó có thể dễ dàng cho kẻ tẩn công dựng lại logic của một truy vấn ban đầu, vì thế kẻ tấn công có thể hiểu được cách thực hiện injection đúng như thế nào. Tuy nhiên, nếu ứng dụng ẩn đi được các thông tin chi tiết về lỗi, thì kiểm thử viên phải có khả năng đảo ngược lại logic của truy vấn ban đầu.
Về các kỹ thuật để khai thác các lỗ hổng SQL Injection thường có 5 kỹ thuật phổ biến sau. Hơn nữa, 5 kỹ thuật này đôi lúc có thể dùng kết hợp nhau như union operator và out-of-band.
- Union Operator: có thể được sử dụng khi lỗ hổng SQL injection xảy ra trong một câu lệnh SELECT, làm cho nó có thể kết hợp hai truy vấn vào một kết quả hoặc tập kết quả.
- Boolean: sử dụng các điều kiện Boolean để xác minh xem các điều kiện nào là đúng hay sai.
- Dựa vào lỗi: kĩ thuật này sẽ ép cơ sở dữ liệu tạo ra lỗi sai, bằng cách cung cấp cho kiểm thử viên/kẻ tấn công các thông tin mà theo đó họ có thể lọc được việc chèn vào.
- Out-of-band: được dùng khi cần truy hồi dữ liệu bằng cách sử dụng một kênh khác, chẳng hạn tạo ra một kết nối HTTP để gửi kết quả đến web server.
- Time delay: dùng các câu lệnh cơ sở dữ liệu (chẳng hạn sleep) để làm trễ các câu trả lời trong các truy vấn điều kiện. Điều này sẽ có ích khi kẻ tấn công không có một số loại câu trả lời (kết quả, output hay error) từ ứng dụng.
Làm thế nào?
Kỹ thuật Truy Lỗi
Đầu tiên ta cần hiểu khi nào ứng dụng sẽ tương tác với một server CSDL để truy cập vào một số dữ liệu. Các ví dụ cụ thể khi một ứng dụng cần giao tiếp với DB gồm các form authentication (như tên người dùng và mật khẩu), các search engine, các trang thương mại điện tử (như các sản phẩm và đặc điểm của chúng gồm giá, mô tả, có sẵn hàng hay không, ....).
Theo đó kiểm thử viên phải tạo ra một danh sách đủ các trường nhập vào mà giá trị của chúng có thể được sử dụng khi tạo ra một truy vấn SQL, kể cả các trường được ẩn đi của các request POST và sau đó kiểm tra chúng riêng lẻ, bằng cách thử gây trở ngại bằng câu truy vấn và tạo ra lỗi. Ngoài ra còn xem xét đến HTTP Header và các Cookie.
Kiểm thử đầu tiên nhất thường dùng là thêm vào dấu nháy đơn (') hoặc chấm phẩy ( vào trường đó hay parameter khi kiểm tra. Dấu nháy đơn dùng trong SQL như một một dấu kết string, vì thế nếu ứng dụng không lọc nó, sẽ dẫn đến câu truy vấn sai. Chấm phẩy thường được dùng để kết thúc một câu lệnh SQL vì thế nếu không được lọc đi, nó cũng có thể tạo ra lỗi. Đầu ra của một trường có thể bị lỗ hổng có thể tương tự như sau (ví dụ này là trong môi trường Microsoft SQL Server):
Microsoft OLE DB Provider for ODBC Drivers error '80040e14' [Microsoft][ODBC SQL Server Driver][SQL Server]Unclosed quotation mark before the character string ''. /target/target.asp, line 113
Ngoài ra còn dùng dấu phân cách comment (-- hay / /) và các từ khóa SQL khác như "AND" hay "OR để chỉnh sửa câu truy vấn. Một kỹ thuật khá là đơn giản nhưng hiệu quả chính là chỉ việc chèn vào một string vị trí mà một số ta muốn, giống như một error bên dưới có thể được tạo ra:
Microsoft OLE DB Provider for ODBC Drivers error '80040e07' [Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value 'test' to a column of data type int. /target/target.asp, line 113
Ta cần quan sát tất cả các response từ web server và nhìn vào mã nguồn HTML/Javascript. Thỉnh thoảng lỗi sẽ xuất hiện trong đó nhưng vì một số lý do mà chúng không xuất hiện với người dùng. Một message lỗi đầy đủ thông tin như thế sẽ giúp kiểm thử viên có thông tin quan trọng để thực hiện một tấn công chèn mã SQL thành công.
Kiểm thử SQL Injection Chuẩn
Ví dụ 1 (Chèn SQL kiểu truyền thống): Chẳng hạn xét câu truy vấn SQL sau:
SELECT * FROM Users WHERE Username='$username' AND Password='$password'
Một câu truy vấn tương tự có thể được dùng từ ứng dụng web để xác minh người dùng. Nếu câu truy vấn này trả về một giá trị có ý nghĩa là bên trong cơ sở dữ liệu có tồn tại người dùng với một chuỗi các thông tin xác nhận thì người dùng sẽ được phép đăng nhập vào hệ thống hoặc truy cập bị từ chối. Giá trị của các trường đầu vào thường được lấy từ người dùng thông qua các form web. Chẳng hạn như chúng ta chèn vào các giá trị Tên Người Dùng và Mật Khẩu sau đây:
$username = 1' or '1' = '1' $password = 1' or '1' = '1
Theo đó câu truy vấn sẽ là:
SELECT * FROM Users WHERE Username='1' OR '1' = '1' AND Password='1' OR '1' = '1'
Chẳng hạn chúng ta giả sử các giá trị của các tham số sẽ được gửi đến server thông qua phương thức GET, và nếu domain của trang web có thể bị lỗ hổng là www.example.com, thì request mà chúng ta cần làm sẽ là: http://www.example.com/index.php?username=1'%20or%20'1'%20=%20'1&password=1'%20or%20'1'%20=%20'1
Sau khi phân tích ngắn gọn, chúng ta sẽ chú ý thấy rằng câu truy vấn sẽ trả về một giá trị (hoặc một chuỗi giá trị) vì điều kiện đó luôn luôn đúng (OR 1=1). Theo cách này hệ thống đã xác thực người dùng đó mà không cần biết thông tin về tên người dùng và mật khẩu. Trong một số hệ thống, dòng đầu tiên trong bảng dữ liệu người dùng sẽ là người dùng có vai trò admin. Đây có thể là hồ sơ dữ liệu được trả về trong vài trường hợp. Một ví dụ truy vấn khác:
SELECT * FROM Users WHERE ((Username='$username') AND (Password=MD5('$password')))
Trong trường hợp này, có 2 vấn đề: một là sử dụng dấu ngoặc đơn và một là sử dụng chức năng mã hóa MD5. Trước tiên là vấn đề về các dấu ngoặc đơn. Chỉ đơn giản là thêm vào một vài dấu ngoặc đơn đóng đến khi chúng ta có được một câu truy vấn được chỉnh sửa. Để giải quyết vấn đề thứ hai, chúng ta sẽ tìm cách tránh đi điều kiện thứ hai. Chúng ta sẽ thêm vào câu truy vấn một kí hiệu cuối cùng có nghĩa là một bình luận đang bắt đầu. Theo cách này, mọi thứ mà theo sau kí hiệu như thế sẽ được xem là một comment. Tất cả DBMS đều có syntax riêng cho comment, tuy nhiên, một ký hiệu hay gặp ở hầu hết các cơ sở dữ liệu là /* (trong Oracle sẽ là "--"). Theo đó, các giá trị mà chúng ta sẽ dùng làm Username và Password là:
$username = 1' or '1' = '1'))/* $password = foo
Theo đó, sẽ có câu truy vấn sau:
SELECT * FROM Users WHERE ((Username='1' or '1' = '1'))/*') AND (Password=MD5('$password')))
(Do đã bao hàm ký hiệu comment trong giá trị $username nên phần mật khẩu của câu truy vấn sẽ bị bỏ qua.)
Câu request URL sẽ là:
http://www.example.com/index.php?username=1'%20or%20'1'%20=%20'1'))/*&password=foo
Câu request URL này có thể trả về một vài giá trị. Đôi khi, mã xác thực sẽ xác minh xem số lượng các record/kết quả trả về có chính xác bằng 1 không. Ở các ví dụ trước đây, trường hợp này có thể khó thực hiện (trong cơ sở dữ liệu vì chỉ có một giá trị cho mỗi người dùng). Để khắc phục vấn đề này, chúng ta chỉ cần chèn vào một câu lệnh SQL - câu lệnh này sẽ đặt một điều kiện sao cho số lượng kết quả trả về chỉ có một. Để thực hiện nó, chúng ta cần dùng toán tử "LIMIT <num>", trong đó <num> là số lượng kết quả trả về/record mà chúng ta muốn được trả về. Nếu như thế, giá trị Tên người dùng và Mật khẩu sẽ được sửa lại như sau:
$username = 1' or '1' = '1')) LIMIT 1/* $password = foo
Request URL sẽ là: http://www.example.com/index.php?username=1'%20or%20'1'%20=%20'1'))%20LIMIT%201/*&password=foo
Ví dụ 2 (câu SELECT đơn giản): Xem xét câu truy vấn sau:
SELECT * FROM products WHERE id_product=$id_product
Và xem xét script sẽ thực hiện câu truy vấn trên: http://www.example.com/product.php?id=10
Khi kiểm thử viên thử một giá trị hợp lệ (chẳng hạn như 10), ứng dụng sẽ trả về mô tả sản phẩm. Một cách khá hay để kiểm tra xem ứng dụng đó có bị lỗ hổng hay không trong trường hợp này chính là thử logic đó, bằng cách dùng các toán tử AND và OR.
Xét request sau: http://www.example.com/product.php?id=10 AND 1=2
SELECT * FROM products WHERE id_product=10 AND 1=2
Trong trường hợp này, ứng dụng có thể sẽ trả về một số tin nhắn cho chúng ta biết không có nội dung nào như thế hoặc trả về một trang trống. Sau đó, kiểm thử viên có thể gửi một câu truy vấn đúng và kiểm tra xem kết quả có hợp lệ không: http://www.example.com/product.php?id=10 AND 1=1
Ví dụ 3 (Nhiều câu truy vấn): Tùy vào API mà ứng dụng web và DBMS đang dùng mà có thể thực thi nhiều truy vấn cùng một lúc. Xem xét truy vấn SQL sau:
SELECT * FROM products WHERE id_product=$id_product
Một cách để khai thác ngữ cảnh trên sẽ là: http://www.example.com/product.php?id=10; INSERT INTO users (…)
Cách này có thể dùng để thực thi nhiều truy vấn trong cùng một hàng và độc lập với truy vấn đầu tiên.
THAM KHẢO:
All rights reserved