Chương 16: Database Security & Hardening
Bảo vệ toàn diện tầng dữ liệu: Xác thực IAM Database, phân quyền RBAC tối thiểu, chuẩn hóa PreparedStatement, mã hóa TDE tĩnh/động và kiểm toán pgAudit.
#01 Database Authentication (Xác thực Cơ sở Dữ liệu)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Cơ sở dữ liệu là hầm chứa két sắt của ngân hàng. Nếu nhân viên chỉ nói 'Tôi là nhân viên' mà không có vân tay và thẻ từ (xác thực yếu hoặc mật khẩu mặc định), bất kỳ ai mặc đồng phục cũng vào vơ vét được tiền. Xác thực CSDL chuẩn yêu cầu chứng chỉ số TLS, tài khoản riêng biệt, hoặc IAM Role động với hạn dùng ngắn.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Database Authentication (Xác thực Cơ sở Dữ liệu)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
user: sa, pass: adminuser: root, pass: emptyuser: app_service, auth: aws_iam_token
⚠️ Xác thực yếu với tài khoản root/sa dùng chung cho mọi dịch vụ!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// THẢM HỌA: Dùng tài khoản 'root' hoặc 'postgres' với mật khẩu đơn giản
spring.datasource.url=jdbc:postgresql://db.prod:5432/main_db
spring.datasource.username=postgres
spring.datasource.password=postgres123
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Dùng AWS IAM Database Authentication hoặc Spring Cloud Vault
@Configuration
public class HikariIamDataSourceConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://db.prod:5432/main_db?sslmode=verify-full");
config.setUsername("app_iam_user");
// Tự động sinh IAM Auth Token có thời hạn 15 phút
config.setPassword(RdsIamTokenGenerator.generateAuthToken());
return new HikariDataSource(config);
}
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Tại sao nên dùng IAM Database Authentication thay vì mật khẩu tĩnh?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#02 Database Authorization & RBAC (Phân quyền CSDL theo vai trò)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Trong một khách sạn, khách thuê phòng chỉ mở được cửa phòng mình, lao công mở được các phòng dọn dẹp, chỉ quản trị viên mới vào được phòng tài vụ. Nếu bạn cấp thẻ vạn năng (quản trị CSDL) cho người phục vụ bàn (ứng dụng đọc sản phẩm), họ có thể lỡ tay hoặc bị ép xóa sạch sổ sách khách sạn.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Database Authorization & RBAC (Phân quyền CSDL theo vai trò)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
ROLE: app_read, QUERY: SELECT * FROM productsROLE: app_read, QUERY: DROP TABLE usersROLE: app_admin, QUERY: ALTER TABLE
⚠️ Ứng dụng kết nối CSDL với toàn quyền DDL/DML (SUPERUSER)!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
-- NGUY HIỂM: Cấp quyền sở hữu toàn bộ bảng hoặc SUPERUSER cho tài khoản ứng dụng
CREATE USER web_user WITH PASSWORD 'secret';
ALTER USER web_user WITH SUPERUSER; -- CỰC KỲ NGUY HIỂM!
✅ Secure Java Code (Chuẩn bảo mật)
-- CHUẨN AN TOÀN: Tạo Role riêng biệt và chỉ GRANT quyền tối thiểu cần thiết
CREATE ROLE app_readonly_role;
GRANT CONNECT ON DATABASE main_db TO app_readonly_role;
GRANT USAGE ON SCHEMA public TO app_readonly_role;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_readonly_role;
CREATE USER catalog_service WITH PASSWORD 'StrongR4nd0mP@ss!';
GRANT app_readonly_role TO catalog_service;
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Quyền nào dưới đây TUYỆT ĐỐI KHÔNG NÊN cấp cho tài khoản CSDL của ứng dụng Web thông thường?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#03 Principle of Least Privilege in DB (Nguyên tắc Đặc quyền Tối thiểu)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Khi thuê thợ sơn nhà, bạn chỉ giao chìa khóa mở cổng và sân trước, chứ không bao giờ đưa chìa khóa két sắt hay phòng ngủ chính. Tài khoản CSDL của microservice chỉ được cấp đúng những bảng và thao tác mà service đó cần để hoạt động.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Principle of Least Privilege in DB (Nguyên tắc Đặc quyền Tối thiểu)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
SERVICE: notification, ACCESS: email_queueSERVICE: notification, ACCESS: credit_cards
⚠️ Service có thể đọc toàn bộ CSDL bao gồm cả thông tin thẻ tín dụng!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// TẤT CẢ SERVICE DÙNG CHUNG 1 TÀI KHOẢN DB DUY NHẤT CÓ QUYỀN TOÀN CỤC
spring.datasource.username=db_global_user
✅ Secure Java Code (Chuẩn bảo mật)
// MỖI MICROSERVICE DÙNG TÀI KHOẢN RIÊNG VỚI SCHEMA ĐỘC LẬP
// Auth Service: chỉ truy cập bảng users, refresh_tokens
// Payment Service: chỉ truy cập bảng transactions, payment_methods
// Reporting Service: chỉ được cấp quyền SELECT trên bản sao Read-Replica
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Hệ quả nguy hiểm nhất khi vi phạm nguyên tắc Least Privilege trong CSDL là gì?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#04 PreparedStatement & Parameterized Queries (Truy vấn tham số hóa)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Hãy tưởng tượng bạn đưa một bức thư mẫu đã được in sẵn khung chữ, chỉ có một ô trống nhỏ để người dùng điền tên bằng bút mực. Dù họ có cố viết vào đó câu lệnh 'HÃY ĐỐT NHÀ ĐI', thì hệ thống vẫn coi đó chỉ là cái tên của một người, chứ không bao giờ thi hành lệnh đốt nhà.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): PreparedStatement & Parameterized Queries (Truy vấn tham số hóa)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
admin' OR 1=1 --standard_user1; DROP TABLE users;
⚠️ Nguy cơ SQLi nếu ghép chuỗi trực tiếp vào câu lệnh SQL!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// THẢM HỌA: Nối chuỗi SQL thô trong Java
public User findUser(String username) throws SQLException {
String sql = "SELECT * FROM users WHERE username = '" + username + "'"; // SQL INJECTION!
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
return mapUser(rs);
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Dùng PreparedStatement hoặc Spring Data JPA Parameterized Query
public User findUser(String username) throws SQLException {
String sql = "SELECT * FROM users WHERE username = ?";
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.setString(1, username); // Dữ liệu được escape an toàn ở tầng driver
try (ResultSet rs = pstmt.executeQuery()) {
return mapUser(rs);
}
}
}
// Hoặc trong Spring Data JPA:
@Query("SELECT u FROM User u WHERE u.username = :username")
Optional findByUsername(@Param("username") String username);
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Tại sao PreparedStatement có thể triệt tiêu hoàn toàn SQL Injection?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#05 Stored Procedure Security & Dynamic SQL (Bảo mật Thủ tục lưu trữ)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Thủ tục lưu trữ (Stored Procedure) giống như một cái máy bán hàng tự động: bạn ấn nút A1, máy nhả ra lon coca. Nhưng nếu kỹ sư chế tạo chiếc máy đó lại lắp một cánh cửa mở toang bên sườn để ai muốn lấy gì thì tự thò tay vào bốc (dùng Dynamic SQL EXECUTE trong Procedure), thì chiếc máy mất hoàn toàn ý nghĩa bảo vệ.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Stored Procedure Security & Dynamic SQL (Bảo mật Thủ tục lưu trữ)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
p_user_id: 105; TRUNCATE TABLE logs;p_user_id: 105p_name: '' OR 1=1
⚠️ Stored Procedure nếu dùng Dynamic SQL ghép chuỗi vẫn bị SQL Injection như thường!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
-- NGUY HIỂM: Stored Procedure ghép chuỗi động (Dynamic SQL Injection)
CREATE PROCEDURE FindUserDynamic(IN p_username VARCHAR(100))
BEGIN
SET @sql = CONCAT('SELECT * FROM users WHERE username = '', p_username, ''');
PREPARE stmt FROM @sql;
EXECUTE stmt; -- LỖ HỔNG SQL INJECTION NẰM TRONG DB!
DEALLOCATE PREPARE stmt;
END;
✅ Secure Java Code (Chuẩn bảo mật)
-- CHUẨN AN TOÀN: Stored Procedure tĩnh có tham số hoặc sp_executesql
CREATE PROCEDURE FindUserSafe(IN p_username VARCHAR(100))
BEGIN
SELECT * FROM users WHERE username = p_username; -- Biên dịch tĩnh an toàn 100%
END;
-- Gọi an toàn từ Java Spring Boot:
SimpleJdbcCall jdbcCall = new SimpleJdbcCall(dataSource)
.withProcedureName("FindUserSafe");
Map out = jdbcCall.execute(new MapSqlParameterSource("p_username", username));
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Quan niệm: 'Chỉ cần chuyển hết truy vấn vào Stored Procedure là hệ thống an toàn tuyệt đối khỏi SQL Injection' là đúng hay sai?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#06 Encryption at Rest — TDE (Mã hóa CSDL khi lưu trữ tĩnh)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Nếu trộm đột nhập vào kho dữ liệu và khiêng trọn ổ cứng máy chủ về nhà cắm vào máy của chúng, chúng có đọc được dữ liệu không? Nếu có TDE (Transparent Data Encryption), toàn bộ ổ đĩa chỉ là một mớ rác vô nghĩa trừ khi chúng có chìa khóa giải mã nằm trong chip bảo mật HSM phần cứng riêng biệt.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Encryption at Rest — TDE (Mã hóa CSDL khi lưu trữ tĩnh)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
STORAGE_STATE: unencrypted_raw_diskSTORAGE_STATE: aws_kms_ebs_encryptedSTORAGE_STATE: postgresql_tde_active
⚠️ Ổ cứng lưu trữ CSDL chưa được mã hóa tĩnh!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
# NGUY HIỂM: Tạo ổ đĩa lưu trữ CSDL không mã hóa trên Đám mây
resource "aws_ebs_volume" "db_storage" {
availability_zone = "us-east-1a"
size = 100
encrypted = false # RỦI RO VI PHẠM PCI-DSS / HIPAA!
}
✅ Secure Java Code (Chuẩn bảo mật)
# CHUẨN AN TOÀN: Bắt buộc mã hóa ổ cứng và CSDL với AWS KMS
resource "aws_db_instance" "prod_db" {
allocated_storage = 100
engine = "postgres"
instance_class = "db.r6g.xlarge"
storage_encrypted = true
kms_key_id = aws_kms_key.db_encryption_key.arn # Quản lý khóa tập trung
}
// Trong Java Spring Data: Mã hóa cột nhạy cảm ở tầng ứng dụng (JPA AttributeConverter)
@Convert(converter = EncryptedStringConverter.class)
private String ssn;
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Encryption at Rest (TDE) bảo vệ hệ thống trước tình huống nguy cơ nào sau đây?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#07 Encryption in Transit (Mã hóa đường truyền CSDL với SSL/TLS)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Bạn gọi điện thoại nói mật khẩu cho bạn mình. Nếu đường dây điện thoại bị kẻ khác kẹp thiết bị nghe lén (Man-in-the-Middle trên mạng nội bộ LAN/Cloud), chúng nghe rõ từng chữ bạn nói. Bật TLS trên kết nối CSDL giống như việc hai người nói chuyện bằng ngôn ngữ mật mã mà chỉ có 2 người hiểu.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Encryption in Transit (Mã hóa đường truyền CSDL với SSL/TLS)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
sslmode=disablesslmode=requiresslmode=verify-full
⚠️ sslmode=disable: Gói tin SQL và thông tin đăng nhập truyền đi dạng văn bản rõ trên mạng!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// NGUY HIỂM: Kết nối JDBC không mã hóa hoặc tắt kiểm tra SSL
spring.datasource.url=jdbc:postgresql://db.company.internal:5432/finance?sslmode=disable
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Bắt buộc SSL với sslmode=verify-full và Truststore CA
spring.datasource.url=jdbc:postgresql://db.company.internal:5432/finance?ssl=true&sslmode=verify-full&sslrootcert=/etc/ssl/certs/rds-ca.pem
// Cấu hình HikariCP nâng cao:
hikari.data-source-properties.ssl=true
hikari.data-source-properties.sslmode=verify-full
hikari.data-source-properties.sslrootcert=/etc/ssl/certs/rds-ca.pem
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Trong kết nối JDBC PostgreSQL, mức độ sslmode nào là an toàn nhất chống MITM?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#08 Database Auditing & Query Logging (Kiểm toán CSDL & Ghi log truy vấn)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Trong bảo tàng lưu giữ báu vật, ở mỗi tủ kính đều có một camera an ninh quay 24/7 ghi lại chính xác: Ai đã mở tủ? Vào lúc mấy giờ mấy phút? Đã lấy món gì ra? Nếu không có camera kiểm toán, đến khi kho báu biến mất bạn hoàn toàn mù tịt không biết ai đã đánh cắp.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Database Auditing & Query Logging (Kiểm toán CSDL & Ghi log truy vấn)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
UPDATE accounts SET balance = 50000000 WHERE id = 1SELECT * FROM public_postsDROP TABLE audit_trail
⚠️ Thao tác sửa đổi số dư tài khoản không có dấu vết kiểm toán!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
-- NGUY HIỂM: CSDL không cấu hình ghi log hoạt động DDL và thay đổi dữ liệu
-- File postgresql.conf để mặc định không audit
✅ Secure Java Code (Chuẩn bảo mật)
-- CHUẨN AN TOÀN: Kích hoạt Extension pgAudit trong PostgreSQL
-- postgresql.conf:
shared_preload_libraries = 'pgaudit'
pgaudit.log = 'write, ddl, role'
pgaudit.log_catalog = off
pgaudit.log_parameter = on -- Ghi cả tham số thực thi
-- Trong Spring Data JPA: Áp dụng Hibernate Envers lưu lịch sử thay đổi từng phiên bản
@Entity
@Audited // Tự động sinh bảng users_AUD lưu vết mọi lần INSERT/UPDATE/DELETE
public class User { ... }
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Tính năng chính của pgAudit hoặc Database Audit Logging là gì?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)