[Tuts] SQL Injection là gì? Làm thế nào để ngăn chặn lỗ hổng SQL Injection

designnt

Member
Jul 25, 2013
405
1
6
33
Các lỗ hổng SQL injection phát sinh khi bạn tạo ra các truy vấn cơ sở dữ liệu một cách không an toàn. Hiểu đơn giản là người dùng có thể xem được database của website bạn bằng cách nhập câu truy vấn vào URL hoặc form điền thông tin. Đừng coi thường SQL injection nhé vì nó nằm trong Top 10 lỗ hổng bảo mật web theo công bố OWASP 2020 đấy.

sql injection


Cách khai thác lỗ hổng SQL Injection


Đơn giản nhất là dùng tool auto exploit SQLi. Mình thường dùng SQLmap, vì theo kinh nghiệm đây là tool có khả năng khai thác tốt nhất. Nhưng nhược điểm là bạn phải dùng lệnh thay vì giao diện.

Hoặc bạn có thể dùng công cụ SQL Dumper có giao diện với nhiều tính năng dễ thiết lập hơn. Nếu mới tìm hiểu bạn có thể dùng thử công cụ này để khai thác database của Website.

Làm thế nào để ngăn chặn lỗ hổng SQL injection?


Cách tốt nhất để ngăn chặn các lỗ hổng SQL injection là sử dụng framework cho phép bạn lọc dữ liệu đầu vào một cách an toàn trước khi đưa vào database. ORM (Object Relational Mapper) là một lựa chọn tốt mà bạn nên thử. Đối với các lớp bảo mật bổ sung, hãy xác thực tất cả đầu vào và sử dụng WAF (Web Application Firewall).

Ví dụ đơn giản


Giả sử mình có một ứng dụng Java cho phép người dùng truy xuất tài liệu của họ bằng ID. Mình có thể làm như thế này:

String query = "SELECT * FROM documents WHERE ownerId=" + authContext.getUserId() + " AND documentName = '" + request.getParameter("docName") + "'";
executeQuery(query);

Nếu ID người dùng là 25 và URL là https://www.example.com/documents/?docName=ABC123, thì truy vấn sẽ là:

SELECT * FROM documents WHERE ownerId=25 AND documentName='ABC123';

Vẫn ổn mà đúng không? Nhưng điều gì sẽ xảy ra nếu URL là https://www.example.com/documents/?docName=ABC123’OR’1’=’1?

Bây giờ mình sẽ nhận được truy vấn sau đây trả về tất cả các tài liệu của tất cả người dùng (vì 1 = 1 luôn đúng):

SELECT * FROM documents WHERE ownerId=25 AND documentName='ABC123' OR '1'='1';

Vậy làm thế nào để tránh lỗi này?

Sử dụng Object Relational Mapping


Lấy Java làm ví dụ, sử dụng ORM chẳng hạn như hibernate để triển khai JPA (Java Persistence API) có thể trông như thế này.

Đầu tiên, xác định model.

@Entity
public class Document {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String documentName;
private Integer ownerId;
}

Sau đó, xác định class repository.

@Repository
public interface DocumentRepository extends JpaRepository<Document, Long> {
List<Document> findByDocumentNameAndOwnerId(String documentName, Integer ownerId);
}

Cuối cùng, bạn có thể sử dụng repository và tìm nạp các tài liệu như sau:

List<Document> docs = documentRepository.findByDocumentNameAndOwnerId(request.getParameter("docName"), authContext.getUserId());

ORM sẽ xử lý tất cả các tham số một cách an toàn. Bây giờ, giả sử bạn muốn kiểm soát các truy vấn nhiều hơn. Trong trường hợp đó, nhiều ORM cung cấp trình tạo truy vấn mà bạn có thể sử dụng, chẳng hạn như Hibernate Criteria API.

Nếu bạn sử dụng Python, Django cũng có ORM tuyệt vời không kém; còn nếu bạn không sử dụng Django, sqlalchemy là một lựa chọn tuyệt vời.

PHP có Doctrine. Bạn chỉ cần google để tìm kiếm các ORM phù hợp với công nghệ mà bạn chọn.

Cảnh báo


Các framework ORM không hoàn hảo 100%.

Đầu tiên là chúng vẫn có chức năng hỗ trợ truy vấn SQL thô/query parts. Bạn chỉ cần tránh sử dụng các tính năng đó là được.

Thứ hai là các framework ORM thường có lỗ hổng bảo mật, giống như bất kỳ gói phần mềm nào khác. Vì vậy, hãy tìm hiểu các phương pháp hay khác: xác thực tất cả dữ liệu đầu vào, sử dụng WAF, cập nhật các package…

Prepared statements


Prepared statements là một sự lựa chọn thủ công hơn và nên tránh vì so với ORM, nó có nguy cơ mắc lỗi do con người cao hơn đáng kể. Tuy nhiên, cách này vẫn đánh bại phương pháp nối chuỗi đơn giản (như ví dụ trên). Cách tiếp cận này giống như sau:


String query = "SELECT * FROM documents WHERE ownerId=? AND documentName = ?";
PreparedStatement ps = conn.prepareStatement(query);
ps.setString(1, authContext.getUserId());
ps.setString(2, request.getParameter("docName"));
ResultSet rs = ps.executeQuery();

Về lý thuyết, cách này khá an toàn. Tuy nhiên, theo kinh nghiệm của mình, khi codebase phát triển lớn hơn, các sai lầm sẽ bắt đầu xuất hiện. Bạn chỉ cần một lần mắc sai lầm là hoàn toàn có thể bị tấn công. Các trường hợp như mảng (documentId IN (“foo”, “bar”)) là nơi dev thường mắc sai lầm.

Vì vậy, nếu bạn quyết định sử dụng phương pháp này, hãy cẩn thận với nó khi bạn mở rộng codebase.

Web Application Firewall


Các sản phẩm WAF không nên được coi là một biện pháp kiểm soát SQL injection tốt. Nhưng chúng là một lớp bổ sung bảo mật tuyệt vời và thường khá hiệu quả để chống lại các cuộc tấn công SQL injection.

Một giải pháp mã nguồn mở tuyệt vời là triển khai Apache với ModSecurity CRS trước webapp của bạn.

Database Firewall


Tùy thuộc vào database và ngân sách của bạn, bạn có thể cân nhắc dùng thử database firewalls. Mình thì chưa từng thử cái này, nhưng các bạn cũng có thể tìm hiểu link dưới, biết đâu nó sẽ giúp ích cho bạn.

Kết luận


SQL injection là một lỗ hổng injection đơn giản. Và giống như tất cả các lỗ hổng bảo mật khác, bạn có thể ngăn chặn nó bằng cách sử dụng một thư viện hoặc framework thích hợp để xây dựng protocol, trong trường hợp này là SQL.


ORM an toàn hơn prepared statements. Và nếu bạn không cần kiểm soát các truy vấn quá nhiều, hãy sử dụng ORM cấp thấp hơn thường được gọi là trình tạo truy vấn (query builder). WAF có thể thêm một lớp bảo mật, nhưng bạn đừng bao giờ dựa vào nó để bảo mật.

Sưu tầm và Tổng hợp
www.hanoiyeu.com