Giao diện
SQL Security: Injection, Roles & Grants
Một câu SQL injection có thể xóa sạch database của bạn. Bài này sẽ dạy cách tấn công để hiểu cách phòng thủ.
1️⃣ SQL Injection: Hiểu để Phòng
Cơ chế tấn công
SQL Injection xảy ra khi user input được nối trực tiếp vào câu SQL mà không được escape.
Code ính lỗ hổng:
python
# SECURITY: Never hardcode credentials in production code
# Use environment variables or secure credential management systems
# ❌ NGUY HIỂM: Nối string trực tiếp
username = request.GET['username'] # User nhập: admin' --
password = request.GET['password'] # User nhập: anything
query = f"SELECT * FROM users WHERE username = '{username}' AND password = os.getenv("DB_PASSWORD")"
# Kết quả:
# SELECT * FROM users WHERE username = 'admin' --' AND password = os.getenv("DB_PASSWORD")
# Phần sau -- bị comment hóa -> Chỉ cần đúng username!Các kiểu tấn công phổ biến
1. Authentication Bypass
-- SECURITY: Never hardcode credentials in production code -- Use environment variables or secure credential management systems -- Input: username = ' OR '1'='1 -- Password: anything SELECT * FROM users WHERE username = '' OR '1'='1' AND password = ENV["DB_PASSWORD"]; -- '1'='1' luôn TRUE -> Trả về TẤT CẢ users! SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'anything'; -- '1'='1' luôn TRUE -> Trả về TẤT CẢ users!
#### 2. Data Exfiltration (UNION Attack)
```sql
-- Giả sử app hiển thị kết quả tìm kiếm sản phẩm
-- Input: ' UNION SELECT id, username, password FROM users --
SELECT name, price FROM products WHERE name LIKE '%' UNION SELECT id, username, password FROM users --%';
-- Attacker thấy được toàn bộ users table!3. Database Destruction
sql
-- Input: '; DROP TABLE users; --
SELECT * FROM users WHERE id = ''; DROP TABLE users; --';
-- Nếu app cho phép multiple statements -> Xóa sạch bảng users!4. Blind SQL Injection (Khi không thấy output)
sql
-- Attacker đoán dần dần
-- Input: ' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin') = 'a' --
-- Nếu response khác -> ký tự đầu của password là 'a'
-- Lặp lại 1000 lần -> Có được passwordCách phòng thủ: Prepared Statements
Prepared Statements (Parameterized Queries) TÁCH BIỆT code SQL và data. Database biết đâu là lệnh, đâu là giá trị.
python
# ✅ AN TOÀN: Prepared Statement với placeholder
username = request.GET['username'] # User nhập: admin' --
password = request.GET['password']
# Python với psycopg2
cursor.execute(
"SELECT * FROM users WHERE username = %s AND password = %s",
(username, password) # Parameters truyền riêng!
)
# Database nhận:
# Query: SELECT * FROM users WHERE username = $1 AND password = $2
# Params: ["admin' --", "anything"]
# -> Tìm user có username là "admin' --" (đúng literally) -> Không tìm thấy!javascript
// Node.js với pg
const result = await client.query(
'SELECT * FROM users WHERE username = $1 AND password = $2',
[username, password]
);java
// Java với JDBC
PreparedStatement stmt = connection.prepareStatement(
"SELECT * FROM users WHERE username = ? AND password = ?"
);
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();Prepared Statements hoạt động thế nào?
⚠️ Prepared Statements KHÔNG bảo vệ mọi thứ
- Table/Column names: Không thể parameterize
SELECT * FROM $1-> Phải whitelist. - ORDER BY:
ORDER BY $1không hoạt động -> Validate và whitelist. - LIMIT/OFFSET: Một số driver cho phép, một số không.
2️⃣ RBAC: Role-Based Access Control
Concept
Thay vì cấp quyền trực tiếp cho user, bạn tạo Role và gán user vào role. Một user có thể có nhiều roles.
[User] --member of--> [Role] --has--> [Permissions]Tạo Roles và Users
sql
-- Tạo roles
CREATE ROLE readonly; -- Role chỉ đọc
CREATE ROLE readwrite; -- Role đọc/ghi
CREATE ROLE admin; -- Role admin
CREATE ROLE app_backend; -- Role cho backend service
-- Tạo user (login role)
CREATE USER analyst WITH PASSWORD 'secure_password';
CREATE USER backend_service WITH PASSWORD 'another_secure_password';
-- Gán role cho user
GRANT readonly TO analyst;
GRANT readwrite TO backend_service;GRANT & REVOKE Permissions
sql
-- Cấp quyền SELECT cho role readonly
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
-- Cấp quyền CRUD cho role readwrite
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO readwrite;
-- Cấp quyền cụ thể trên từng bảng
GRANT SELECT ON users TO readonly;
GRANT SELECT, INSERT ON orders TO readwrite;
GRANT ALL PRIVILEGES ON products TO admin; -- SELECT, INSERT, UPDATE, DELETE, TRUNCATE, etc.
-- Cấp quyền trên sequence (cho auto-increment)
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO readwrite;
-- Thu hồi quyền
REVOKE DELETE ON orders FROM readwrite;
REVOKE ALL PRIVILEGES ON users FROM readonly;Column-level Permissions
sql
-- Chỉ cho phép đọc một số cột (ẩn sensitive data)
GRANT SELECT (id, email, created_at) ON users TO readonly;
-- readonly KHÔNG thể SELECT password, phone, addressRow-level Security (PostgreSQL)
sql
-- Bật RLS cho bảng
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
-- Policy: User chỉ thấy orders của chính mình
CREATE POLICY user_orders_policy ON orders
FOR ALL
TO readwrite
USING (user_id = current_setting('app.current_user_id')::INT);
-- Khi query, set context
SET app.current_user_id = '42';
SELECT * FROM orders; -- Chỉ trả về orders có user_id = 423️⃣ Principle of Least Privilege
Vấn đề: App User là postgres hoặc root
Nhiều developer dùng superuser cho app vì "tiện". Đây là sai lầm nghiêm trọng.
Nếu app bị SQL Injection và user là postgres:
→ Attacker có thể DROP DATABASE
→ Attacker có thể đọc pg_shadow (hashed passwords)
→ Attacker có thể tạo user mới
→ Attacker có thể COPY TO PROGRAM (chạy shell command!)Cách setup đúng
sql
-- 1. Tạo schema riêng cho app
CREATE SCHEMA app_data;
-- 2. Tạo role cho từng môi trường/service
CREATE ROLE app_api; -- API backend
CREATE ROLE app_worker; -- Background jobs
CREATE ROLE app_readonly; -- Reporting/Analytics
-- 3. Cấp quyền TỐI THIỂU
-- API chỉ cần CRUD trên một số bảng
GRANT USAGE ON SCHEMA app_data TO app_api;
GRANT SELECT, INSERT, UPDATE ON app_data.users TO app_api;
GRANT SELECT, INSERT, UPDATE ON app_data.orders TO app_api;
GRANT SELECT ON app_data.products TO app_api; -- Chỉ đọc products
-- Worker cần quyền khác
GRANT USAGE ON SCHEMA app_data TO app_worker;
GRANT SELECT, UPDATE ON app_data.orders TO app_worker; -- Xử lý orders
GRANT INSERT ON app_data.job_logs TO app_worker;
-- Readonly cho reporting
GRANT USAGE ON SCHEMA app_data TO app_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA app_data TO app_readonly;
-- 4. Tạo users từ roles
CREATE USER api_user WITH PASSWORD 'xxx' IN ROLE app_api;
CREATE USER worker_user WITH PASSWORD 'yyy' IN ROLE app_worker;
CREATE USER report_user WITH PASSWORD 'zzz' IN ROLE app_readonly;
-- 5. Deny nguy hiểm
REVOKE CREATE ON SCHEMA public FROM PUBLIC; -- Không ai tạo table lung tung
REVOKE ALL ON pg_catalog.pg_shadow FROM app_api; -- Không đọc password hashesConnection String trong App
python
# Development (có thể dùng superuser, TUY NHIÊN vẫn nên tập thói quen)
DATABASE_URL = "postgresql://postgres:password@localhost/mydb"
# Production: PHẢI dùng restricted user
DATABASE_URL = "postgresql://api_user:secure_password@db.example.com/production_db"Audit: Kiểm tra quyền hiện tại
sql
-- Xem quyền của một role
SELECT
table_schema,
table_name,
privilege_type
FROM information_schema.role_table_grants
WHERE grantee = 'app_api';
-- Xem members của một role
SELECT
r.rolname AS role,
m.rolname AS member
FROM pg_auth_members am
JOIN pg_roles r ON r.oid = am.roleid
JOIN pg_roles m ON m.oid = am.member;4️⃣ Security Checklist
Application Layer
| ✅ | Hành động |
|---|---|
| ☐ | Dùng Prepared Statements cho MỌI query có user input |
| ☐ | Whitelist table/column names nếu cần dynamic |
| ☐ | Validate và sanitize input (length, type, format) |
| ☐ | Log failed queries để detect injection attempts |
Database Layer
| ✅ | Hành động |
|---|---|
| ☐ | Tạo app-specific user, KHÔNG dùng superuser |
| ☐ | Cấp quyền TỐI THIỂU cần thiết |
| ☐ | Enable SSL/TLS cho connection |
| ☐ | Đặt password policy mạnh |
| ☐ | Regular audit quyền của các users |
| ☐ | Backup và test restore thường xuyên |
Network Layer
| ✅ | Hành động |
|---|---|
| ☐ | Database không expose ra public internet |
| ☐ | Chỉ cho phép connection từ app servers (whitelist IP) |
| ☐ | Dùng VPC/Private Network |
Tổng kết
| Threat | Mitigation |
|---|---|
| SQL Injection | Prepared Statements, Input Validation |
| Privilege Escalation | Least Privilege, Role-based Access |
| Data Breach | Column/Row-level Security, Encryption |
| Unauthorized Access | Strong Passwords, Network Isolation |
💡 HPN Golden Rule
"Assume every user input is malicious.
Assume every app account will be compromised.
Design your database security like the attacker already has your app credentials."