SQL Injection trong phát triển di động: nó là gì, phương pháp tấn công và bảo vệ

Tác giả: IT Sectr Đã đăng: 2026-04-06 Thời gian đọc: 9 phút

SQL Injection là một loại tấn công vào cơ sở dữ liệu ứng dụng, trong đó kẻ tấn công tiêm mã SQL độc hại vào các tham số truy vấn, giành quyền truy cập trái phép vào dữ liệu hoặc khả năng sửa đổi chúng. Theo OWASP (2025), SQL Injection vẫn là một trong những lỗ hổng nghiêm trọng có thể dẫn đến xâm phạm hoàn toàn cơ sở dữ liệu. Tiêm mã SQL cho phép kẻ tấn công đọc, sửa đổi và xóa bản ghi, và trong một số trường hợp, giành quyền truy cập vào hệ điều hành máy chủ.

Những điểm chính

  • SQL Injection — tiêm mã SQL qua tham số người dùng, thay đổi logic truy vấn cơ sở dữ liệu
  • Tham số hóa truy vấn — phương pháp bảo vệ chính: tách mã SQL khỏi dữ liệu bằng prepared statements
  • Các loại tấn công — tiêm cổ điển (qua WHERE), Blind SQLi (suy luận logic), UNION-based (đọc bảng khác)
  • Error-based SQLi — trích xuất dữ liệu qua thông báo lỗi cơ sở dữ liệu
  • Khung ORM — giảm rủi ro SQLi khi sử dụng đúng cách, nhưng không loại bỏ hoàn toàn

SQL Injection là gì?

SQL Injection là một lỗ hổng xảy ra khi ứng dụng xây dựng truy vấn SQL bằng cách nối chuỗi với dữ liệu người dùng. Kẻ tấn công truyền một chuỗi được tạo đặc biệt vào tham số truy vấn làm thay đổi cấu trúc của lệnh SQL. Thay vì là dữ liệu, chuỗi được tiêm trở thành một phần của mã SQL, cho phép kẻ tấn công thực thi các truy vấn tùy ý trên cơ sở dữ liệu. Theo Báo cáo vi phạm dữ liệu Verizon (2025), SQL Injection có mặt trong 8% tổng số vụ vi phạm dữ liệu được điều tra.

Tại sao SQL Injection vẫn còn phổ biến?

Mặc dù là một lỗ hổng nổi tiếng (được đề cập lần đầu vào cuối những năm 1990), SQL Injection vẫn được tìm thấy trong các ứng dụng hiện đại. Nguyên nhân là sai sót của con người: nhà phát triển viết mã với phép nối chuỗi, mã kế thừa không được tái cấu trúc, và khung ORM được sử dụng không đúng cách (ví dụ: truy vấn raw với nội suy chuỗi). Theo Veracode (2026), khoảng 14% tổng số ứng dụng được quét có chứa ít nhất một lỗ hổng SQLi.

Kẻ tấn công có thể làm gì?

SQL Injection thành công mang lại cho kẻ tấn công nhiều khả năng: đọc bất kỳ bảng cơ sở dữ liệu nào, bao gồm băm mật khẩu và dữ liệu cá nhân của người dùng; sửa đổi và xóa bản ghi; thực thi các thao tác quản trị (DROP TABLE, TRUNCATE); và trong một số cấu hình, thực thi lệnh từ xa qua xp_cmdshell (MSSQL) hoặc INTO OUTFILE (MySQL). Hậu quả từ rò rỉ dữ liệu người dùng đến mất hoàn toàn quyền kiểm soát hệ thống.

Các loại SQL Injection

SQL Injection được phân loại theo cách trích xuất dữ liệu từ cơ sở dữ liệu. Việc chọn phương pháp phụ thuộc vào cách ứng dụng xử lý kết quả truy vấn và thông báo lỗi. Phân loại OWASP xác định ba loại chính: In-band (trích xuất dữ liệu qua cùng một kênh), Inferential/Blind (suy luận logic) và Out-of-band (truyền dữ liệu qua kênh khác).

LoạiPhương pháp trích xuấtĐộ phức tạpTần suất
In-band (cổ điển)Trực tiếp qua kết quả truy vấnThấpCao
Blind SQLiSuy luận logic từ phản hồi máy chủCaoTrung bình
Out-of-bandQua kênh bên ngoài (DNS, HTTP)Trung bìnhThấp

In-band SQL Injection (Cổ điển)

Loại phổ biến nhất. Kẻ tấn công tiêm mã SQL vào tham số truy vấn và kết quả của việc tiêm hiển thị trực tiếp trong phản hồi máy chủ. Hai loại phụ: Error-based (qua thông báo lỗi DB) và UNION-based (qua toán tử UNION SELECT). Error-based sử dụng thông tin từ thông báo lỗi, như lỗi cú pháp MySQL có thể tiết lộ tên bảng hoặc cấu trúc truy vấn. UNION-based cho phép kết hợp kết quả truy vấn hợp pháp với dữ liệu từ các bảng khác trong cơ sở dữ liệu.

Blind SQL Injection (Mù)

Được sử dụng khi ứng dụng không hiển thị kết quả truy vấn hoặc thông báo lỗi. Kẻ tấn công đặt câu hỏi có/không bằng cách gửi truy vấn với điều kiện logic và phân tích sự khác biệt trong phản hồi máy chủ (ví dụ: thời gian phản hồi hoặc nội dung trang). Time-based Blind SQLi sử dụng hàm trì hoãn (SLEEP, WAITFOR DELAY) để xác nhận điều kiện — nếu trang tải lâu hơn, điều kiện đúng. Phương pháp này rất chậm — trích xuất một bản ghi có thể mất hàng giờ.

python
# Ví dụ về Blind SQL Injection (dựa trên thời gian)
# Nếu SQLi dễ bị tấn công, SLEEP(2) được thực thi nếu điều kiện đáp ứng
import requests
import time

payload = "' OR IF(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a',SLEEP(2),0) -- "
url = "http://example.com/user?id=1" + payload

start = time.time()
response = requests.get(url)
elapsed = time.time() - start

# Nếu phản hồi đến sau >2 giây — chữ cái đầu tiên của mật khẩu là 'a'
print(f"Time: {elapsed:.2f}s - First letter is 'a'" if elapsed > 2 else "First letter is not 'a'")

Out-of-band SQL Injection

Dữ liệu được truyền không qua phản hồi HTTP mà qua kênh thay thế: truy vấn DNS, yêu cầu HTTP đến máy chủ bên ngoài, SMTP. Được sử dụng khi ứng dụng không trả về kết quả truy vấn hoặc hiển thị lỗi. MySQL hỗ trợ hàm LOAD_FILE(), có thể kích hoạt truy vấn DNS và MSSQL có xp_dirtree để gửi dữ liệu đến máy chủ SMB từ xa. Out-of-band SQL Injection hiệu quả nhưng yêu cầu thiết lập máy chủ tấn công bổ sung và các hàm DB cụ thể.

SQL Injection hoạt động như thế nào?

Cơ chế SQL Injection dựa trên việc SQL sử dụng dấu ngoặc kép cho chuỗi ký tự. Nếu ứng dụng chèn đầu vào của người dùng trực tiếp vào truy vấn SQL mà không thoát, kẻ tấn công có thể “đóng” chuỗi và thêm mã SQL tùy ý. Ví dụ, trong truy vấn SELECT * FROM users WHERE name = '$input', chèn ' OR '1'='1 biến nó thành SELECT * FROM users WHERE name = '' OR '1'='1', trả về tất cả người dùng.

Ví dụ cổ điển: Vượt qua xác thực

Xem xét biểu mẫu đăng nhập với truy vấn SELECT * FROM users WHERE username = '$user' AND password = '$pass'. Nếu kẻ tấn công nhập admin' -- vào trường tên người dùng và để trống mật khẩu, truy vấn kết quả trở thành SELECT * FROM users WHERE username = 'admin' -- ' AND password = ''. Các ký tự -- chú thích phần còn lại của truy vấn, vô hiệu hóa kiểm tra mật khẩu. Máy chủ trả về bản ghi người dùng admin và kẻ tấn công đăng nhập mà không biết mật khẩu.

python
# Ví dụ về SQL Injection — Vượt qua xác thực
# MÃ DỄ BỊ TẤN CÔNG: nối chuỗi trực tiếp
def login_vulnerable(username, password):
    query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
    cursor.execute(query)
    return cursor.fetchone() is not None

# username = "admin' --" vô hiệu hóa kiểm tra mật khẩu
# PHIÊN BẢN AN TOÀN: truy vấn tham số hóa
def login_secure(username, password):
    query = "SELECT * FROM users WHERE username = %s AND password = %s"
    cursor.execute(query, (username, password))
    return cursor.fetchone() is not None

UNION-based: Đọc bảng khác

Toán tử UNION SELECT cho phép kết hợp kết quả của hai truy vấn SELECT. Nếu kẻ tấn công tìm thấy tham số dễ bị tấn công, chúng thêm UNION SELECT với truy vấn từ bảng khác. Ví dụ: ' UNION SELECT username, password FROM admins --. Điều kiện thành công là số cột trong cả hai truy vấn phải khớp. Số cột được xác định qua ORDER BY (chèn ' ORDER BY 1--, sau đó 2, 3... cho đến khi có lỗi). Biết số cột, kẻ tấn công chèn UNION SELECT với cùng số lượng trường.

SQL Injection trong ứng dụng di động

Ứng dụng di động phải đối mặt với SQL Injection cả ở phía máy chủ (API) và phía máy khách — trong cơ sở dữ liệu cục bộ (SQLite, Realm). Trong khi SQLi phía máy chủ trong API di động tương tự như ứng dụng web, cơ sở dữ liệu cục bộ tạo ra một vectơ bổ sung. Nếu ứng dụng lưu trữ dữ liệu trong SQLite và thực thi truy vấn với phép nối chuỗi, dữ liệu độc hại xâm nhập vào cơ sở dữ liệu cục bộ qua API có thể kích hoạt SQLi trong quá trình xử lý tiếp theo.

SQL Injection trong SQLite trên Android/iOS

Cơ sở dữ liệu SQLite cục bộ trên thiết bị cũng dễ bị SQL Injection nếu ứng dụng xây dựng truy vấn bằng cách nối chuỗi. Content Providers trên Android và Core Data trên iOS sử dụng tham số hóa mặc định, nhưng truy vấn raw yêu cầu sự chú ý của nhà phát triển. SQLite không hỗ trợ nhiều truy vấn cách nhau bằng dấu chấm phẩy, hạn chế lựa chọn của kẻ tấn công nhưng không bảo vệ khỏi việc đọc dữ liệu qua điều kiện WHERE. Luôn sử dụng selectionArgs trong Android và NSPredicate với tham số trong iOS.

SQL Injection qua API ứng dụng di động

API mà ứng dụng di động giao tiếp cũng dễ bị tấn công như bất kỳ máy chủ web nào. Nhà phát triển di động thường cho rằng SQLi chỉ là vấn đề của backend, nhưng lỗ hổng xảy ra tại điểm cuối API chấp nhận tham số từ máy khách. Phân chia trách nhiệm không bảo vệ: nếu nhà phát triển backend quên tham số hóa truy vấn, ứng dụng di động của người dùng trở thành vectơ tấn công. Yêu cầu backend sử dụng ORM hoặc prepared statements.

kotlin
// Ví dụ về SQL Injection trong SQLite cục bộ trên Android
// MÃ DỄ BỊ TẤN CÔNG: nối trực tiếp
fun getUserVulnerable(db: SQLiteDatabase, userId: String): Cursor {
    return db.rawQuery(
        "SELECT * FROM users WHERE id = $userId", null
    )
}

// PHIÊN BẢN AN TOÀN: tham số hóa qua selectionArgs
fun getUserSecure(db: SQLiteDatabase, userId: String): Cursor {
    return db.rawQuery(
        "SELECT * FROM users WHERE id = ?",
        arrayOf(userId)
    )
}

Phương pháp ngăn chặn SQL Injection

Bảo vệ khỏi SQL Injection dựa trên một nguyên tắc đơn giản: không bao giờ tin tưởng đầu vào của người dùng trong truy vấn SQL. Phương pháp đáng tin cậy duy nhất là tham số hóa truy vấn (prepared statements), nơi mã SQL và dữ liệu được truyền riêng biệt. Tất cả các phương pháp khác — thoát, xác thực, WAF — là các lớp bảo mật bổ sung nhưng không thay thế tham số hóa. Theo OWASP (2025), tham số hóa ngăn chặn 100% các cuộc tấn công SQL Injection.

Prepared Statements (Truy vấn tham số hóa)

Khi sử dụng prepared statements, truy vấn SQL đầu tiên được máy chủ DB biên dịch mà không có dữ liệu, sau đó giá trị tham số được truyền riêng biệt. Cơ sở dữ liệu coi tham số là dữ liệu, không phải mã thực thi. Ngay cả khi kẻ tấn công truyền ' OR '1'='1, cơ sở dữ liệu diễn giải nó như một giá trị chuỗi, không phải mã SQL. Prepared statements được hỗ trợ bởi tất cả các ngôn ngữ và khung hiện đại: PDO trong PHP, PreparedStatement trong Java, cursor.execute trong Python.

Khung ORM và Trình xây dựng truy vấn

ORM hiện đại (Hibernate, Entity Framework, SQLAlchemy, Room) tự động sử dụng tham số hóa khi thực thi truy vấn, trừ khi nhà phát triển chuyển sang truy vấn raw. Tuy nhiên, ORM không bảo vệ hoàn toàn: cấu trúc như @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true) trong JPA yêu cầu truyền tham số qua tham số có tên, không phải nối chuỗi. Trình xây dựng truy vấn (Knex, jOOQ) cũng tham số hóa truy vấn mặc định nếu không sử dụng phương thức raw.

Thoát chuỗi (không được khuyến nghị làm phương pháp chính)

Thoát ký tự đặc biệt (mysql_real_escape_string) là phương pháp lỗi thời không bảo vệ khỏi tất cả các loại SQL Injection. Vấn đề: thoát phụ thuộc vào mã hóa và có thể bị vượt qua khi sử dụng mã hóa đa byte (ví dụ: GBK trong hệ thống châu Á). Chỉ sử dụng thoát trong mã kế thừa khi không thể tham số hóa và luôn kết hợp với xác thực loại đầu vào nghiêm ngặt.

Phương phápHiệu quảKhuyến nghị
Prepared Statements100%Bắt buộc cho mọi truy vấn
ORM (sử dụng đúng cách)99%Khuyến nghị
Thoát chuỗi70% (phụ thuộc mã hóa)Chỉ kế thừa
Xác thực đầu vào (white-list)50% (chỉ số)Bổ sung
WAF (Web Application Firewall)60%Bổ sung

Nguyên tắc đặc quyền tối thiểu cho DB

Tài khoản ứng dụng nên có đặc quyền tối thiểu cần thiết: SELECT, INSERT, UPDATE, DELETE — chỉ trên các bảng mà ứng dụng thực sự cần. Cấm sử dụng DROP, TRUNCATE, CREATE cho tài khoản ứng dụng. Điều này hạn chế thiệt hại ngay cả khi SQL Injection thành công: kẻ tấn công sẽ không thể xóa bảng hoặc thực thi các thao tác quản trị.

Công cụ phát hiện SQL Injection

Kiểm tra SQL Injection thường xuyên nên là một phần của quy trình phát triển bảo mật. Sự kết hợp giữa phân tích tĩnh, quét động và kiểm tra thâm nhập thủ công mang lại kết quả tốt nhất. Theo Báo cáo an ninh mạng Synopsys (2025), máy quét tự động tìm thấy tới 70% lỗ hổng SQLi, nhưng các cuộc tấn công Blind phức tạp yêu cầu kiểm tra thủ công.

  • sqlmap — công cụ phổ biến nhất để phát hiện và khai thác tự động SQL Injection
  • OWASP ZAP — máy quét DAST miễn phí với mô-đun quét SQLi chủ động
  • Burp Suite Scanner — công cụ chuyên nghiệp với tính năng phát hiện SQL Injection tự động
  • SonarQube — phân tích mã tĩnh cho các lỗ hổng, bao gồm cả mẫu SQLi
  • CodeQL — phân tích mã ngữ nghĩa để tìm SQL Injection trong mã nguồn

Đối với ứng dụng di động, phân tích SQLite cục bộ cũng quan trọng: kiểm tra tất cả các lệnh gọi rawQuery, truy vấn ContentProvider và truy vấn Room với rawQuery. Công cụ: Android Studio Lint (phát hiện SQLi trong SQLite), MobSF (Mobile Security Framework) để phân tích tĩnh và động tự động APK/IPA. Cũng nên kiểm tra các điểm cuối API qua sqlmap với chặn proxy lưu lượng ứng dụng di động.

Câu hỏi thường gặp

Sự khác biệt giữa SQL Injection và NoSQL Injection là gì?

SQL Injection tấn công cơ sở dữ liệu quan hệ qua truy vấn SQL. NoSQL Injection ảnh hưởng đến cơ sở dữ liệu phi quan hệ (MongoDB, Couchbase) qua toán tử truy vấn của chúng ($gte, $ne, $where). Trong MongoDB, tiêm có thể xảy ra nếu ứng dụng xây dựng tài liệu BSON từ chuỗi JSON. Cơ chế bảo vệ tương tự: tham số hóa và xác thực loại.

Làm thế nào để tự phát hiện SQL Injection trong mã?

Tìm tất cả các nơi truy vấn SQL được hình thành bằng cách nối chuỗi với dữ liệu người dùng. Tìm kiếm các mẫu như "SELECT ... WHERE id = " + userId hoặc f"UPDATE ... SET name = '{name}'". Mỗi dòng như vậy là một SQL Injection tiềm năng. Thay thế tất cả bằng truy vấn tham số hóa hoặc prepared statements.

ORM có tự động bảo vệ khỏi SQL Injection không?

Khung ORM chỉ tự động bảo vệ nếu bạn sử dụng phương pháp Query Buildertham số có tên của chúng. Nếu bạn sử dụng truy vấn raw (nativeQuery trong JPA, rawQuery trong Room), bảo vệ không hoạt động — bạn phải truyền tham số qua biểu thức đã chuẩn bị, không phải qua nối chuỗi.

Second-Order SQL Injection là gì?

Second-Order SQL Injection là một cuộc tấn công trong đó dữ liệu độc hại được lưu trữ trong cơ sở dữ liệu như an toàn, nhưng sau đó được sử dụng trong một truy vấn khác mà không thoát. Ví dụ, kẻ tấn công đăng ký với tên người dùng như ' OR '1'='1. Dữ liệu được lưu dưới dạng chuỗi — không có tấn công khi đăng ký. Nhưng nếu một truy vấn khác sử dụng tên người dùng trong SQL mà không tham số hóa, việc tiêm sẽ kích hoạt.

NoSQL Injection có thể nguy hiểm hơn SQL Injection không?

NoSQL Injection có thể nguy hiểm hơn do nhận thức của nhà phát triển thấp hơn. Nhà phát triển biết về SQL Injection và hầu hết sử dụng ORM, nhưng ít người biết về NoSQL Injection. Trong MongoDB, một truy vấn được hình thành không đúng cách có thể trả về tất cả tài liệu trong một bộ sưu tập. Bảo vệ cũng tương tự — prepared statements (tham số hóa BSON) và xác thực đầu vào nghiêm ngặt.

Tóm tắt

  • SQL Injection — tấn công tiêm mã SQL qua tham số người dùng không được thoát trong truy vấn DB
  • Các loại chính — In-band (cổ điển), Blind (mù, dựa trên thời gian), Out-of-band (qua kênh bên ngoài)
  • Tham số hóa truy vấn — phương pháp bảo vệ 100% đáng tin cậy duy nhất chống SQL Injection
  • Huyền thoại về ORM — ORM chỉ bảo vệ khi sử dụng phương pháp tích hợp; truy vấn raw với nối chuỗi vẫn dễ bị tấn công
  • Nguyên tắc đặc quyền tối thiểu — hạn chế đặc quyền tài khoản DB giảm thiểu thiệt hại khi tấn công thành công
  • Kiểm tra thường xuyên — sqlmap, OWASP ZAP, SonarQube nên là một phần của quy trình CI/CD
  • SQLite cục bộ — ứng dụng di động cũng phải tham số hóa truy vấn cơ sở dữ liệu cục bộ

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm