Ok, đầu tiên tụi mình cần biết mình đang bảo vệ cái gì, chính xác hơn là bảo vệ khỏi ai. Ở đầu phần này mình đã nhắc tới SQL-injection, một trong những kiểu tấn công phổ biến và phá hoại nhất nhắm vào database. Cách tấn công này là: hacker gửi mã SQL độc hại vào query của bạn để "lừa" nó và truy cập vào dữ liệu mà lẽ ra không được phép. Giờ mình sẽ giải thích kỹ hơn nhé.
Ví dụ về SQL-injection
Giả sử bạn có một web app với form nhập username và password đơn giản. Backend thực hiện một truy vấn SQL để kiểm tra xem user có trong database không:
SELECT * FROM users WHERE username = 'admin' AND password = 'password123';
Query này nhìn thì ổn... miễn là user nhập dữ liệu hợp lệ. Nhưng chuyện gì xảy ra nếu ai đó nhập:
- Tên đăng nhập:
admin' -- - Mật khẩu: (để trống luôn)
Kết quả là query sẽ thành như này:
SELECT * FROM users WHERE username = 'admin' -- AND password = 'password123';
Chú ý ký tự --, trong SQL nó là bắt đầu comment. Mọi thứ sau đó đều bị bỏ qua. Kết quả là kiểm tra password bị bỏ qua luôn, và hacker sẽ đăng nhập được với quyền admin!
Hậu quả tiềm ẩn của SQL-injection
SQL-injection có thể dẫn đến hậu quả cực kỳ tệ:
- Truy cập trái phép vào dữ liệu. Ví dụ, hacker có thể lấy được thông tin nhạy cảm như password của user.
- Xóa hoặc sửa dữ liệu. Ai đó có thể xóa cả bảng hoặc làm loạn dữ liệu.
- Thực thi mã SQL bất kỳ. Thử tưởng tượng hacker chạy lệnh
DROP DATABASE... Nghe như ác mộng luôn.
Nhưng tụi mình không dễ bị dọa đâu! PostgreSQL có sẵn nhiều công cụ giúp bạn bảo vệ khỏi mấy kiểu tấn công này.
Làm sao để bảo vệ? Các cách ngăn chặn SQL-injection
- Dùng prepared statements (
PREPAREvàEXECUTE)
Prepared statements giống như công thức nấu ăn đã kiểm chứng cho SQL của bạn. Cách hoạt động là: bạn "chuẩn bị" query SQL một lần, sau đó truyền dữ liệu vào riêng biệt. Như vậy không thể chèn mã độc vào được.
Ví dụ cách làm đúng:
Chuẩn bị query bằng PREPARE.
PREPARE user_login (text, text) AS
SELECT *
FROM users
WHERE username = $1 AND password = $2;
Query này có hai tham số $1 và $2, sẽ nhận giá trị thật sau.
Dùng EXECUTE để chạy query.
EXECUTE user_login('admin', 'password123');
Lúc này PostgreSQL sẽ tự động escape mọi dữ liệu từ user, ngăn không cho chèn mã SQL độc hại.
Lợi ích của cách này:
- Không thể thực hiện SQL-injection, vì tham số được xử lý như dữ liệu thường, không phải là một phần của mã SQL.
- Query an toàn hơn và chạy nhanh hơn nhờ caching execution plan.
- Parameterized Queries
Cách này rất phổ biến trong các app viết bằng Python, Java, v.v. Thay vì tự tay dùng PREPARE và EXECUTE, bạn có thể dùng thư viện hoặc ORM, nó sẽ tự động xử lý tham số cho bạn.
Ví dụ với Python và thư viện psycopg2:
import psycopg2
connection = psycopg2.connect(
dbname="your_db",
user="your_user",
password="your_password",
host="localhost",
port="5432"
)
cursor = connection.cursor()
# Dùng parameterized query
username = "admin"
password = "password123"
query = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(query, (username, password))
# Dữ liệu của bạn hoàn toàn an toàn!
result = cursor.fetchall()
print(result)
Chú ý %s trong query SQL — đó là chỗ để truyền tham số vào. Thư viện psycopg2 sẽ lo việc truyền dữ liệu an toàn.
- Kiểm tra dữ liệu đầu vào
Nếu bạn truyền dữ liệu do user nhập vào, hãy chắc chắn nó đúng như bạn mong đợi. Ví dụ:
- Với dữ liệu text, dùng regex để đảm bảo không có ký tự cấm.
- Với dữ liệu số, kiểm tra chắc chắn nó là số thật.
Ví dụ trong Python:
import re
username = input("Nhập tên đăng nhập: ")
# Chỉ cho phép chữ cái, số và dấu gạch dưới
if re.match(r"^\w+$", username):
print("Tên đăng nhập hợp lệ")
else:
print("Tên đăng nhập nguy hiểm!")
- Dùng quyền tối thiểu cần thiết
Hãy đảm bảo role dùng để chạy query chỉ có quyền tối thiểu cần thiết. Ví dụ, đừng cấp quyền DROP TABLE hay ALTER TABLE nếu không cần thiết.
- Log các hành động đáng ngờ
Bạn có thể dùng các tham số của PostgreSQL để theo dõi hoạt động của user:
log_statement = 'all'— log tất cả các query.log_connections = on— log tất cả kết nối tới database.
Những tham số này giúp bạn phát hiện hành động nguy hiểm tiềm ẩn.
Ví dụ thực tế
Ví dụ 1: Dùng prepared statement qua SQL
-- Tạo prepared statement
PREPARE check_credentials (text, text) AS
SELECT * FROM users WHERE username = $1 AND password = $2;
-- Thực thi query với tham số an toàn
EXECUTE check_credentials('admin', 'password123');
Ví dụ 2: Parameterized query trong Python
query = "UPDATE users SET last_login = NOW() WHERE username = %s"
username = "admin"
cursor.execute(query, (username,))
Khuyến nghị về bảo mật
Luôn kiểm tra dữ liệu đầu vào. Đừng bao giờ tin dữ liệu từ user gửi lên.
Chỉ dùng prepared statements hoặc parameterized queries. Đây là tường chắn chính của bạn trước SQL-injection.
Chỉ cấp quyền tối thiểu cần thiết cho role. Như vậy nếu hacker có truy cập thì thiệt hại cũng giảm tối đa.
Cấu hình log và kiểm tra log thường xuyên. Luôn theo dõi hoạt động trong database của bạn.
SQL-injection đúng là con quái vật đáng sợ, nhưng nếu bạn dùng prepared statements, parameterized queries và tuân thủ best practice, database của bạn sẽ được bảo vệ và bạn có thể ngủ ngon mà không lo ai đó "vô tình" xóa sạch bảng dữ liệu. PostgreSQL đã cho bạn đầy đủ công cụ rồi, cứ mạnh dạn mà dùng nhé!
GO TO FULL VERSION