Có thể bạn đã nghe nói về 10 lỗ hổng hàng đầu của OWASP hoặc 10 lỗ hổng hàng đầu đe dọa các ứng dụng web. OWASP cũng định kỳ chọn danh sách mười lỗ hổng bảo mật hàng đầu đe dọa API, được gọi là mười lỗ hổng hàng đầu của API OWASP. Mười lỗ hổng API hàng đầu hiện tại là:
- Broken Object Level Authorization
- Broken User Authentication
- Excessive Data Exposure
- Lack of Resources & Rate Limiting
- Broken Function Level Authorization
- Mass Assignment
- Security Misconfiguration
- Injection
- Improper Assets Management
- Insufficient Logging & Monitoring
Đây là những lỗ hổng ảnh hưởng đến API nhiều nhất. Vậy nên trong bài viết này, chúng ta hãy nói về lỗ hổng API thứ 8, Injection, một loại lỗ hổng ảnh hưởng đến hầu hết các ứng dụng và hệ thống API.
Injection là vấn đề cơ bản của một số lượng lớn các lỗ hổng, chẳng hạn như SQL injection, OS command injection và XML injection. Cùng với nhau, Injection chiếm một tỷ lệ lớn các lỗ hổng được tìm thấy trong các ứng dụng và API trong thế giới thực.
Cách hoạt động của injection
Nói ngắn gọn, Injection xảy ra khi một ứng dụng không thể phân biệt chính xác giữa dữ liệu người dùng và mã không đáng tin cậy.
Dữ liệu người dùng không đáng tin cậy có thể là các tham số request HTTP, HTTP headers và cookie. Chúng cũng có thể đến từ cơ sở dữ liệu hoặc tệp được lưu trữ mà người dùng có thể sửa đổi. Nếu ứng dụng không xử lý đúng cách dữ liệu người dùng không đáng tin cậy trước khi chèn nó vào lệnh hoặc truy vấn, trình thông dịch của chương trình sẽ nhầm lẫn dữ liệu người dùng nhập vào là một phần của lệnh hoặc truy vấn. Trong trường hợp này, những kẻ tấn công có thể gửi dữ liệu độc hại làm đầu vào, khiến mục đích ban đầu của lệnh bị thay đổi.
Trong một cuộc tấn công SQL injection, chẳng hạn, kẻ tấn công đưa dữ liệu vào để thao tác các lệnh SQL. Và trong một cuộc tấn công command injection, kẻ tấn công sẽ tiêm dữ liệu điều khiển logic của các lệnh hệ điều hành trên máy chủ lưu trữ. Bất kỳ chương trình nào kết hợp dữ liệu người dùng với các lệnh hoặc mã lập trình đều có khả năng bị tấn công.
Các lỗ hổng injection cũng có thể ảnh hưởng đến các hệ thống API vì API chỉ là một cách khác mà người dùng nhập liệu vào một ứng dụng. Hãy xem cách các lỗ hổng bảo mật xuất hiện trong API.
Ví dụ # 1: Truy xuất các bài đăng trên blog
Giả sử rằng một API cho phép người dùng truy xuất các bài đăng trên blog bằng cách gửi một yêu cầu request GET như sau:
GET /api/v1.1/posts?id=12358
Yêu cầu này sẽ khiến API trả về bài đăng 12358. Máy chủ sẽ truy xuất bài đăng blog tương ứng từ cơ sở dữ liệu bằng truy vấn SQL, trong đó post_id là id được người dùng chuyển vào qua URL.
SELECT * FROM posts WHERE post_id = 12358
Bây giờ, điều gì sẽ xảy ra nếu người dùng gửi yêu cầu này từ điểm cuối API?
GET /api/v1.1/posts?id=
12358; DROP TABLE users
Máy chủ SQL sẽ diễn giải phần id sau dấu chấm phẩy như một lệnh SQL riêng biệt. Vì vậy, công cụ SQL trước tiên sẽ thực hiện lệnh này để truy xuất bài đăng trên blog như bình thường:
SELECT * FROM posts WHERE post_id = 12358;
Sau đó, nó sẽ thực hiện lệnh này để xóa bảng người dùng, khiến ứng dụng mất dữ liệu được lưu trữ trong bảng đó.
DROP TABLE users
Đây được gọi là một cuộc tấn công SQL injection và có thể xảy ra bất cứ khi nào đầu vào của người dùng được chuyển vào các truy vấn SQL theo cách không an toàn. Lưu ý rằng đầu vào của người dùng trong một API không chỉ được nhập thông qua các tham số URL, mà còn có thể thông qua yêu cầu POST, tham số đường dẫn URL,… Vì vậy, điều quan trọng là bạn phải bảo vệ những nơi đó.
Ví dụ # 2: Đọc File hệ thống
Giả sử trang web cho phép người dùng đọc các tệp mà họ đã tải lên thông qua điểm cuối API:
GET /api/v1.1/files?id=1123581321
Yêu cầu này sẽ khiến máy chủ truy xuất tệp của người dùng thông qua lệnh hệ thống:
cat /var/www/html/users/tmp/1123581321
Trong trường hợp này, người dùng có thể đưa các lệnh mới vào hệ điều hành bằng cách thêm các lệnh bổ sung sau dấu chấm phẩy.
GET /api/v1.1/files?id=1123581321; rm -rf /var/www/html/users
Lệnh này sẽ buộc máy chủ xóa thư mục nằm tại /var/www/html/users
, đây là nơi ứng dụng lưu trữ thông tin người dùng.
rm -rf /var/www/html/users
Ngăn chặn các lỗ hổng injection trong API
Đây là những ví dụ đơn giản về lỗ hổng injection. Trên thực tế, điều quan trọng cần nhớ là không phải lúc nào lỗ hổng injection cũng rõ ràng như vậy. Và thao tác này có thể xảy ra bất cứ lúc nào phần dữ liệu này đang được xử lý hoặc sử dụng. Ngay cả khi dữ liệu độc hại không được ứng dụng sử dụng ngay lập tức, dữ liệu này có thể di chuyển đến một nơi nào đó trong chương trình nơi nó có thể làm điều gì đó xấu, chẳng hạn như một chức năng nguy hiểm hoặc một truy vấn không được bảo vệ. Và đây là nơi chúng gây ra thiệt hại cho ứng dụng.
Đó là lý do tại sao injection rất khó phòng ngừa. Dữ liệu không đáng tin cậy có thể tấn công bất kỳ thành phần ứng dụng nào. Và đối với mỗi phần dữ liệu không đáng tin cậy mà ứng dụng nhận được, nó cần phát hiện và vô hiệu hóa các cuộc tấn công nhắm vào mọi phần của ứng dụng. Và ứng dụng có thể nghĩ rằng một phần dữ liệu là an toàn vì nó không chứa bất kỳ ký tự đặc biệt nào được sử dụng để XSS khi kẻ tấn công có ý định SQL injection. Không phải lúc nào cũng dễ xác định dữ liệu nào là an toàn và dữ liệu nào không, vì dữ liệu an toàn và không an toàn trông rất khác nhau trong các phần khác nhau của ứng dụng.
Xác thực đầu vào
Vì vậy, làm thế nào để bảo vệ ứng dụng trước những mối đe dọa này? Điều đầu tiên bạn có thể làm là xác thực dữ liệu không đáng tin cậy. Điều này có nghĩa là bạn cần triển khai danh sách chặn để từ chối bất kỳ đầu vào nào có chứa các ký tự nguy hiểm có thể ảnh hưởng đến các thành phần ứng dụng. Hoặc bạn triển khai danh sách chỉ cho phép các đầu vào có các ký tự tốt đã biết. Ví dụ: giả sử bạn đang triển khai chức năng đăng ký. Vì bạn biết rằng dữ liệu sẽ được chèn vào một truy vấn SQL, bạn từ chối bất kỳ đầu vào tên người dùng nào là các ký tự đặc biệt trong SQL, chẳng hạn như dấu ngoặc kép. Hoặc, bạn có thể triển khai quy tắc chỉ cho phép các ký tự chữ và số.
Nhưng đôi khi danh sách chặn rất khó thực hiện vì bạn không phải lúc nào cũng biết ký tự nào sẽ quan trọng đối với các thành phần ứng dụng.
Và danh sách cho phép có thể quá hạn chế và trong một số trường hợp, đôi khi bạn có thể cần phải chấp nhận các ký tự đặc biệt như dấu ngoặc kép trong các trường nhập của người dùng. Ví dụ: nếu người dùng có tên Conan O’Brien đang đăng ký, anh ta phải được phép sử dụng một dấu nháy đơn duy nhất trong tên của mình.
Tham số hóa
Một biện pháp bảo vệ khác có thể chống lại injection là tham số hóa. Tham số hóa đề cập đến việc biên dịch phần mã của lệnh trước khi chèn bất kỳ tham số nào do người dùng cung cấp.
Điều này có nghĩa là thay vì ghép đầu vào của người dùng vào các lệnh chương trình và gửi nó đến máy chủ để được biên dịch, bạn xác định tất cả logic trước, biên dịch nó, sau đó chèn đầu vào của người dùng vào lệnh ngay trước khi thực thi. Sau khi đầu vào của người dùng được chèn vào lệnh cuối cùng, lệnh sẽ không được phân tích cú pháp và biên dịch lại. Và bất kỳ thứ gì không có trong câu lệnh ban đầu sẽ được coi là dữ liệu chuỗi và không phải là mã thực thi. Vì vậy, phần logic chương trình của lệnh sẽ vẫn còn nguyên vẹn.
Điều này cho phép cơ sở dữ liệu phân biệt giữa phần mã và phần dữ liệu của lệnh, bất kể đầu vào của người dùng trông như thế nào. Phương pháp này rất hiệu quả trong việc ngăn chặn một số lỗ hổng injection, nhưng không thể được sử dụng trong mọi ngữ cảnh trong code.
Mã hoá ký tự đặc biệt
Bạn mã hóa các ký tự đặc biệt trong đầu vào của người dùng để chúng được coi là dữ liệu chứ không phải là ký tự đặc biệt. Bằng cách sử dụng các dấu đặc biệt và cú pháp để đánh dấu các ký tự đặc biệt trong dữ liệu nhập của người dùng, cho phép trình thông dịch biết rằng dữ liệu nào không được thực thi.
Nhưng phương pháp này cũng đi kèm với các vấn đề của nó. Bạn phải sử dụng cú pháp mã hóa chính xác cho mọi trình phân tích cú pháp hoặc có nguy cơ các giá trị được mã hóa bị trình phân tích cú pháp hiểu sai. Bạn cũng có thể quên một số ký tự mà kẻ tấn công có thể sử dụng để vô hiệu hóa các nỗ lực mã hóa của bạn. Vì vậy, chìa khóa để ngăn chặn các lỗ hổng bảo mật là hiểu cách thức hoạt động của các trình phân tích cú pháp của các ngôn ngữ khác nhau và trình phân tích cú pháp nào chạy trước trong các quy trình của bạn.