Tư duy Feynman • Trực quan hình họa • Java / Spring Boot

Chương 19: Secure Code Review Methodology

Quy trình duyệt mã bảo mật chuyên sâu: Phân tích luồng dữ liệu (Source/Sanitizer/Sink), nhận diện ranh giới tin cậy, kiểm tra phân quyền, chống lộ PII và bí mật.

#01 Source Code Data Flow Analysis (Phân tích luồng dữ liệu mã nguồn)

💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Phân tích luồng dữ liệu giống như việc theo dõi dòng nước từ ống dẫn nước thải thành phố chảy về nhà bạn. Bạn phải lần theo từng khúc cua của đường ống để xem nước bẩn đó chảy qua những đâu, và quan trọng nhất: liệu nó có bị đổ nhầm vào bể nước ăn mà chưa qua máy lọc hay không.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Source Code Data Flow Analysis (Phân tích luồng dữ liệu mã nguồn)
Source (Đầu vào) request.getParameter() Data Flow Path Truyền qua Service -> Repo Sink (Điểm đích) stmt.executeQuery() Kết luận Review Lỗ hổng Injection được chứng minh ⚠️ Phân tích Data Flow: Lần theo dấu vết của dữ liệu độc hại từ Source tới Sink
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm: FLOW: request.getParameter -> service -> db.queryFLOW: input -> validator -> db.query
⚠️ Luồng dữ liệu đi thẳng từ điểm vào người dùng tới câu lệnh CSDL mà không có bước lọ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)
// DÒ TÌM LUỒNG DỮ LIỆU: Dữ liệu 'q' không hề được làm sạch khi đi từ Controller tới CSDL
@GetMapping("/search")
public List search(@RequestParam String q) { // 1. SOURCE
    return itemService.findItems(q);                // 2. FLOW
}
// Trong Service:
public List findItems(String query) {
    return repo.rawQuery("SELECT * FROM items WHERE name LIKE '%" + query + "%'"); // 3. SINK!
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Đặt rào chắn kiểm định (Sanitizer) giữa Source và Sink
@GetMapping("/search")
public List search(@RequestParam @Size(max = 50) @Pattern(regexp = "^[a-zA-Z0-9 ]+$") String q) {
    return itemService.findItems(q); // Dữ liệu đã được kiểm duyệt an toàn
}
// Service dùng tham số hóa Parameterized Query
public List findItems(String query) {
    return repo.findByNameContaining(query); // JPA tự động tham số hóa
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Trong phân tích luồng dữ liệu bảo mật (Taint Analysis), 3 thành phần cốt lõi là gì?
A. Source (nguồn nhận dữ liệu bẩn), Sanitizer (bộ lọc làm sạch), và Sink (điểm đích nhạy cảm).
B. CPU, RAM, và Ổ cứng.
C. HTML, CSS, và JavaScript.
D. Router, Switch, và Cáp mạ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)

#02 Source, Sanitizer, and Sink Identification (Nhận diện Nguồn, Bộ lọc và Điểm đích)

💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Source là cửa nhận thư rác từ ngoài đường; Sanitizer là máy khử khuẩn và rà kim loại; Sink là bàn làm việc của nguyên thủ quốc gia. Một chuyên gia duyệt mã bảo mật giỏi là người có cặp mắt tinh tường chỉ ra được ngay đâu là bức thư chưa qua máy khử khuẩn mà đã đặt thẳng lên bàn nguyên thủ.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Source, Sanitizer, and Sink Identification (Nhận diện Nguồn, Bộ lọc và Điểm đích)
Source HttpServletRequest, Headers Sanitizer Whitelist Regex, HtmlUtils Sink Runtime.exec, SQL, innerHTML Quy tắc Vàng Không có Sanitizer -> Chắc chắn có lỗi 🛡️ Công thức bảo mật: Dữ liệu từ Source bắt buộc phải qua Sanitizer trước khi tới Sink
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm: SOURCE: user_input -> SANITIZER: None -> SINK: Runtime.getRuntime().exec()SOURCE: user_input -> SANITIZER: Whitelist -> SINK: PreparedStatement
⚠️ Dữ liệu đi thẳng vào Sink thực thi lệnh hệ điều hà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)
// SINK NGUY HIỂM TRONG JAVA CẦN CHÚ Ý KHI CODE REVIEW:
// 1. SQL Sink: Statement.execute(), Connection.nativeSQL()
// 2. Command Sink: Runtime.getRuntime().exec(), ProcessBuilder.start()
// 3. File Sink: new File(parent, userInput), Paths.get(userInput)
// 4. Reflection Sink: Class.forName(userInput).newInstance()
// 5. XPath/XML Sink: DocumentBuilder.parse(), XPath.compile()
✅ Secure Java Code (Chuẩn bảo mật)
// DANH SÁCH SANITIZER CHUẨN THEO TỪNG LOẠI SINK:
// Cho SQL Sink -> PreparedStatement
// Cho HTML Sink -> HtmlUtils.htmlEscape() hoặc OWASP Java HTML Sanitizer
// Cho File Sink -> Path.normalize() + startsWith(baseDir)
// Cho Process Sink -> Tuyệt đối không nhận chuỗi lệnh từ user, chỉ dùng ID nội bộ
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Phương pháp nào sau đây thuộc nhóm 'Sink' nguy hiểm nhất trong Java?
A. Runtime.getRuntime().exec() và ProcessBuilder.start().
B. System.out.println().
C. Math.max().
D. String.toLowerCase().
📝 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 Trust Boundary Identification (Xác định Ranh giới Tin cậy)

💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Ranh giới tin cậy giống như cửa khẩu an ninh sân bay. Bên ngoài cửa khẩu là bất kỳ ai cũng có thể đứng; nhưng một khi bước qua cửa kiểm tra soi chiếu an ninh, bạn bước vào khu vực cách ly an toàn. Bất cứ khi nào dữ liệu bước qua một ranh giới (ví dụ từ Internet vào Backend, hoặc từ Microservice này sang Microservice khác), nó bắt buộc phải được kiểm tra lại thẻ căn cước.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Trust Boundary Identification (Xác định Ranh giới Tin cậy)
Untrusted Zone Internet / Web Client Trust Boundary API Gateway / Filter DMZ / Ingress Xác thực danh tính & Payload Trusted Zone Core Backend Services 🛡️ Zero Trust: Không bao giờ tin tưởng mù quáng dữ liệu từ ngoài ranh giới tin cậy
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm: ZONE: public_internet -> ZONE: internal_core_dbZONE: public_internet -> GATEWAY: auth_filter -> ZONE: core
⚠️ Vượt ranh giới tin cậy mà không có chốt kiểm soát an ninh!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// SAI LẦM: Tin rằng request từ mạng nội bộ thì không cần xác thực
@GetMapping("/internal/admin/reset-database")
public String resetDb(HttpServletRequest request) {
    // Nghĩ rằng endpoint này chỉ gọi trong mạng nội bộ nên không cần @PreAuthorize!
    // Kẻ tấn công dùng SSRF từ web công khai gọi vào endpoint này!
    databaseService.wipeAll();
    return "OK";
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN ZERO TRUST: Mọi ranh giới đều phải xác thực, kể cả giữa các microservice nội bộ
@PostMapping("/internal/api/v1/sync")
@PreAuthorize("hasAuthority('SCOPE_service_internal_write')")
public ResponseEntity syncData(@Valid @RequestBody SyncPayload payload,
                                 @AuthenticationPrincipal Jwt jwt) {
    // Xác thực Mutual TLS (mTLS) hoặc Service-to-Service JWT Token
    return ResponseEntity.ok(syncService.process(payload));
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Mô hình kiến trúc 'Zero Trust' yêu cầu điều gì đối với các ranh giới tin cậy?
A. Luôn luôn xác thực và ủy quyền mọi yêu cầu, không bao giờ mặc định tin tưởng kể cả khi request xuất phát từ mạng nội bộ (Never Trust, Always Verify).
B. Tin tưởng 100% tất cả máy tính có địa chỉ IP 192.168.x.x.
C. Không cần đặt mật khẩu cho nhân viên công ty.
D. Tắt toàn bộ tường lửa.
📝 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 Input Validation Code Review (Duyệt mã Kiểm định đầu vào)

💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Khi duyệt mã kiểm tra đầu vào, bạn như một thanh tra kiểm dịch hải quan: bạn kiểm tra xem mã nguồn đã có đủ bộ đồ bảo hộ (Bean Validation), danh sách trắng các ký tự cho phép, và giới hạn độ dài hay chưa. Nếu phát hiện một tham số nhận vào mà không có bất kỳ bộ lọc nào, bạn phải lập biên bản ngay.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Input Validation Code Review (Duyệt mã Kiểm định đầu vào)
Code Under Review public void updateUser() Check 1: @Valid Có annotation DTO không? Check 2: Constraints Có @Size, @Pattern không? Kết luận Đạt chuẩn hoặc Yêu cầu sửa 🛡️ Checklist duyệt mã đầu vào: Kiểm tra Kiểu dữ liệu, Độ dài, Định dạng và Khoảng giá trị
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm: REVIEW: public void pay(@RequestParam double amount)REVIEW: public void pay(@Valid @Positive BigDecimal amount)
⚠️ Thiếu kiểm tra ràng buộc số dương: User có thể truyền amount = -500 để tự cộng tiề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)
// KHI REVIEW PHÁT HIỆN ĐOẠN CODE NÀY: PHẢI TỪ CHỐI PULL REQUEST!
@PostMapping("/transfer")
public String transfer(@RequestParam String toUser, @RequestParam double amount) {
    // 1. Không kiểm tra toUser có chứa ký tự độc hại không
    // 2. Không kiểm tra amount có âm hay không (amount = -1000000)
    // 3. Kiểu double bị lỗi làm tròn tiền tệ
    accountService.moveMoney(toUser, amount);
    return "success";
}
✅ Secure Java Code (Chuẩn bảo mật)
// PHIÊN BẢN ĐÃ ĐƯỢC CHẤP THUẬN SAU REVIEW:
public record TransferDto(
    @NotBlank @Pattern(regexp = "^[a-zA-Z0-9_]{3,30}$") String toUser,
    @NotNull @DecimalMin(value = "1000.00") @Digits(integer = 12, fraction = 2) BigDecimal amount
) {}

@PostMapping("/transfer")
public ResponseEntity transfer(@Valid @RequestBody TransferDto dto) {
    accountService.moveMoney(dto.toUser(), dto.amount());
    return ResponseEntity.ok("Transfer successful");
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Khi kiểm tra mã xử lý tiền tệ trong Java, kiểu dữ liệu nào sau đây được coi là an toàn và chuẩn xác nhất?
A. BigDecimal (đi kèm với ràng buộc @Positive / @DecimalMin).
B. float.
C. double.
D. String.
📝 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 Authentication Logic Review (Duyệt mã Cơ chế Xác thực)

💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Duyệt mã xác thực giống như kiểm tra khóa của cổng chính: Bạn kiểm tra xem chìa khóa có thể bị sao chép dễ dàng không? Nếu có kẻ cố tình tra chìa giả 100 lần liên tiếp thì còi báo động có kêu không? Và sau khi khách rời đi, bạn có thay đổi mã pin của cửa hay không?
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Authentication Logic Review (Duyệt mã Cơ chế Xác thực)
Login Endpoint POST /api/login Check Password BCrypt.checkpw() Check Brute-Force Có khóa tài khoản tạm thời? Session Fixation Có đổi SessionID mới? 🛡️ Checklist duyệt mã xác thực: Thuật toán băm, Chống Brute-force và Chống cố định phiên
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm: REVIEW: if (user.pass == input.pass) { createSession(); }REVIEW: passwordEncoder.matches(raw, hash) + rateLimiter
⚠️ So sánh mật khẩu dạng văn bản rõ và không có cơ chế chống dò 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)
// CODE REVIEW PHẢI BẮT LỖI NGAY:
public boolean login(String username, String password) {
    User user = userRepo.findByUsername(username);
    // 1. So sánh bằng plain-text
    // 2. Không giới hạn số lần thử sai
    // 3. Lỗi NullPointerException nếu user không tồn tại (timing attack lộ username)
    return user.getPassword().equals(password);
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN SPRING SECURITY:
public AuthenticationResponse login(LoginRequest req) {
    // 1. Kiểm tra tỷ lệ thử sai IP / Tài khoản
    rateLimiterService.checkLimit(req.getUsername());
    try {
        Authentication auth = authManager.authenticate(
            new UsernamePasswordAuthenticationToken(req.getUsername(), req.getPassword())
        );
        rateLimiterService.resetLimit(req.getUsername());
        return generateTokens(auth);
    } catch (BadCredentialsException e) {
        rateLimiterService.recordFailedAttempt(req.getUsername());
        throw new InvalidCredentialsException("Tên đăng nhập hoặc mật khẩu không chính xác");
    }
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Khi đăng nhập thất bại, thông báo lỗi nào sau đây là an toàn nhất để tránh lộ sự tồn tại của tài khoản?
A. 'Tên đăng nhập hoặc mật khẩu không chính xác'.
B. 'Tài khoản này không tồn tại trong hệ thống'.
C. 'Mật khẩu nhập bị sai ký tự thứ 3'.
D. 'Tài khoản đúng nhưng mật khẩu 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 Authorization & Privilege Escalation Review (Duyệt mã Phân quyền & Chống leo thang)

💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Leo thang đặc quyền giống như việc một nhân viên bình thường dùng bút xóa sửa chức danh trên thẻ nhân viên của mình từ 'Thực tập sinh' thành 'Tổng giám đốc' để vào phòng ký lệnh chi tiền. Người duyệt mã phải kiểm tra xem ở mọi cánh cửa phòng họp, hệ thống có kiểm tra chức vụ thật trong hồ sơ gốc chứ không chỉ nhìn vào mảnh giấy nhân viên tự trình ra.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Authorization & Privilege Escalation Review (Duyệt mã Phân quyền & Chống leo thang)
HTTP Request POST /admin/delete-user Method Security @PreAuthorize("hasRole('ADMIN')") Object Check Xác minh quyền sở hữu tài nguyên Quyết định Cho phép hoặc 403 Forbidden 🛡️ Chống leo thang đặc quyền ngang (IDOR) và leo thang đặc quyền dọc (BFLA)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm: REVIEW: @PostMapping('/set-admin') public void setAdmin(Long userId)REVIEW: @PreAuthorize("hasRole('SUPER_ADMIN')") public void setAdmin()
⚠️ Endpoint thăng chức admin không có bất kỳ annotation kiểm tra quyền hạn nào!
⚖️ 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 TRONG CONTROLLER: Bỏ quên phân quyền phương thức
@PostMapping("/api/users/{id}/promote")
public ResponseEntity promoteUser(@PathVariable Long id) {
    // Không có kiểm tra người đang gọi API có phải là SuperAdmin hay không!
    userService.grantAdminRole(id);
    return ResponseEntity.ok("Promoted");
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN SPRING SECURITY:
@PostMapping("/api/users/{id}/promote")
@PreAuthorize("hasRole('SUPER_ADMIN')") // Chỉ SuperAdmin mới được cấp quyền
public ResponseEntity promoteUser(@PathVariable Long id, 
                                    @AuthenticationPrincipal UserPrincipal currentUser) {
    auditLogService.logRoleChange(currentUser.getId(), id, "ROLE_ADMIN");
    userService.grantAdminRole(id);
    return ResponseEntity.ok("Promoted successfully");
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Sự khác biệt giữa Leo thang đặc quyền dọc (Vertical) và Leo thang đặc quyền ngang (Horizontal) là gì?
A. Dọc là người dùng thường chiếm quyền của Admin; Ngang là người dùng A truy cập/sửa đổi trái phép dữ liệu của người dùng B có cùng cấp bậc.
B. Dọc xảy ra trên máy tính để bàn, Ngang xảy ra trên điện thoại.
C. Dọc do lỗi phần cứng, Ngang do lỗi phần mềm.
D. Không có điểm gì khác nhau.
📝 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 Injection Flaws Code Inspection (Duyệt mã Lỗ hổng Tiêm mã độc)

💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Khi soi kính lúp tìm lỗi Injection, bạn là chuyên gia rà phá bom mìn: bạn rà soát mọi dòng mã có hành vi ghép chuỗi văn bản (String Concatenation) để tạo ra các lệnh gửi đến trình thông dịch khác (SQL, Shell, LDAP, XPath). Nơi nào có dấu cộng chuỗi '+' kết nối dữ liệu người dùng với câu lệnh, nơi đó tiềm ẩn một quả bom nổ chậm.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Injection Flaws Code Inspection (Duyệt mã Lỗ hổng Tiêm mã độc)
Review Scan Tìm kiếm phép nối chuỗi '+' Đánh giá Sink Gửi vào DB / OS / Parser? Phát hiện lỗi Ghép chuỗi SQL/Command Injection Sửa chữa Thay thế bằng API tham số hóa ⚠️ Quy tắc vàng khi Code Review: Tuyệt đối không nối chuỗi để tạo câu lệnh thực thi
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm: REVIEW: String cmd = "ping -c 1 " + host;REVIEW: ProcessBuilder with fixed array and validated host
⚠️ Ghép chuỗi đầu vào 'host' vào lệnh Shell: Command Injection cực kỳ nguy hiểm!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// ĐOẠN CODE NGUY HIỂM: Nối chuỗi để gọi lệnh hệ điều hành
public void pingHost(String host) throws IOException {
    // Hacker truyền host: "8.8.8.8 && cat /etc/passwd"
    Runtime.getRuntime().exec("ping -c 1 " + host); // COMMAND INJECTION!
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN ĐƯỢC CHẤP THUẬN SAU REVIEW:
private static final Pattern SAFE_HOST = Pattern.compile("^[a-zA-Z0-9.-]{1,100}$");

public void pingHostSafe(String host) throws IOException, InterruptedException {
    if (!SAFE_HOST.matcher(host).matches()) {
        throw new IllegalArgumentException("Địa chỉ host không hợp lệ");
    }
    // Dùng ProcessBuilder với mảng tham số cố định, không khởi động qua Shell (/bin/sh)
    ProcessBuilder pb = new ProcessBuilder("ping", "-c", "1", host);
    pb.redirectErrorStream(true);
    Process process = pb.start();
    process.waitFor(5, TimeUnit.SECONDS);
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Dấu hiệu nhận biết điển hình nhất của một lỗ hổng Injection khi duyệt mã nguồn là gì?
A. Dữ liệu người dùng (User Input) được ghép chuỗi trực tiếp (+) vào câu lệnh thực thi mà không sử dụng cơ chế tham số hóa (Parameterized).
B. Tên biến quá dài.
C. Có quá nhiều lệnh if-else.
D. Sử dụng ngôn ngữ Java.
📝 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 Secret & Credential Exposure Review (Duyệt mã Rò rỉ Bí mật & Mật khẩu)

💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Duyệt tìm bí mật giống như việc kiểm tra túi áo của các công nhân trước khi họ rời khỏi nhà máy đúc tiền. Bạn rà soát từng dòng mã xem có ai 'để quên' chìa khóa API, mật khẩu database, hay token JWT trong mã nguồn, file cấu hình, hay thậm chí trong các dòng comment ghi chú thử nghiệm hay không.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Secret & Credential Exposure Review (Duyệt mã Rò rỉ Bí mật & Mật khẩu)
Automated Scan TruffleHog / GitGuardian Regex Entropy Check Tìm kiếm chuỗi High-Entropy Manual Inspection Soi file test & .env.example Quyết định Revoke khóa & Đưa vào Vault 🛡️ Kiểm tra bí mật: Quét Entropy chuỗi và loại bỏ toàn bộ Hardcoded Credentials
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm: REVIEW: String stripeKey = "sk_live_51NzABC1234567890XYZ";REVIEW: @Value("${stripe.api.key}") private String stripeKey;
⚠️ Khóa Stripe bí mật Production bị hardcode trong mã nguồ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)
// THẢM HỌA: Để lộ Secret Key thật trong mã nguồn hoặc file Test
public class PaymentService {
    // KHÓA THẬT BỊ LỘ TRÊN GITHUB!
    private static final String STRIPE_SECRET = "sk_live_51MzXYZ9876543210ABCDEF";
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Đọc từ Secret Manager và cấu hình pre-commit hook
@Service
public class PaymentService {
    private final String stripeSecret;

    public PaymentService(@Value("${stripe.secret.key}") String stripeSecret) {
        this.stripeSecret = stripeSecret;
    }
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Nếu vô tình commit một khóa API bí mật lên một Git repository công khai (public), việc bạn cần làm NGAY LẬP TỨC là gì?
A. Lập tức vô hiệu hóa (Revoke) khóa API đó trên trang quản trị của nhà cung cấp và phát hành khóa mới, vì các botnet đã cào được khóa chỉ sau vài giây.
B. Chỉ cần git commit xóa dòng đó đi là an toàn.
C. Đổi tên repository thành private.
D. Không cần làm gì cả.
📝 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 Sensitive Data Exposure Review (Duyệt mã Rò rỉ Dữ liệu Nhạy cảm PII)

💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Duyệt dữ liệu PII giống như việc bảo vệ hồ sơ bệnh án của bệnh nhân: Bác sĩ chỉ được nhìn thấy bệnh án khi trực tiếp chữa trị. Nhân viên kế toán bệnh viện chỉ được nhìn thấy viện phí chứ không được nhìn thấy lịch sử xét nghiệm; nhân viên tạp vụ không được nhìn thấy bất kỳ thông tin nào.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Sensitive Data Exposure Review (Duyệt mã Rò rỉ Dữ liệu Nhạy cảm PII)
User Entity Lưu PasswordHash, SSN, Card Jackson Serialization Bỏ quên @JsonIgnore REST Response Lộ nguyên PasswordHash ra JSON Sửa chữa Dùng Response DTO chuyên biệt ⚠️ Tránh trả về Entity trực tiếp ra API khiến lộ các trường PII nhạy cảm
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm: REVIEW: return userRepository.findById(id); // Returns full entityREVIEW: return userMapper.toPublicDto(user);
⚠️ Trả về toàn bộ Entity User làm lộ cả trường password_hash và ssn ra client!
⚖️ 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 KHI REVIEW: Trả về trực tiếp Entity CSDL ra API công khai
@GetMapping("/api/users/{id}")
public User getUser(@PathVariable Long id) {
    // Entity User có các trường: passwordHash, ssn, creditCardNumber, otpSecret
    return userRepository.findById(id).orElseThrow(); // LỘ HẾT RA JSON!
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Luôn sử dụng UserSummaryResponse DTO
public record UserPublicProfileDto(
    Long id,
    String displayName,
    String avatarUrl
) {}

@GetMapping("/api/users/{id}")
public ResponseEntity getUser(@PathVariable Long id) {
    User user = userRepository.findById(id).orElseThrow();
    return ResponseEntity.ok(new UserPublicProfileDto(user.getId(), user.getDisplayName(), user.getAvatarUrl()));
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Tại sao việc trả về trực tiếp JPA Entity ra Controller API lại bị coi là thực hành xấu về bảo mật?
A. Vì nó dễ vô tình làm lộ các trường nhạy cảm trong CSDL (như passwordHash, số CCCD, quyền hạn nội bộ) ra ngoài phản hồi JSON.
B. Vì Jackson không thể serialize được Entity.
C. Vì CSDL sẽ tự động bị khóa.
D. Vì Controller sẽ chạy chậm hơn 100 lần.
📝 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)

#10 Exception Handling & Logging Review (Duyệt mã Xử lý Ngoại lệ & Nhật ký)

💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Khi hệ thống gặp tai nạn, xử lý ngoại lệ giống như cách nhân viên ứng phó khẩn cấp: Bạn phải hướng dẫn khách hàng ra cửa thoát hiểm một cách bình tĩnh (thông báo lỗi an toàn), chứ không phải đứng giữa sảnh la toáng lên: 'Bình ga số 4 ở góc trái nhà bếp đang rò rỉ và sắp nổ tung!' (in chi tiết lỗi hệ thống cho kẻ xấu nghe).
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Exception Handling & Logging Review (Duyệt mã Xử lý Ngoại lệ & Nhật ký)
Code Review Tìm khối catch(Exception e) Check 1: Nuốt lỗi? catch {} để trống? Check 2: Lộ vết? e.printStackTrace() ra Web? Chuẩn hóa @RestControllerAdvice + RFC 7807 🛡️ Checklist duyệt lỗi: Không nuốt ngoại lệ, không in stacktrace ra client, log an toàn
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm: REVIEW: catch (Exception e) { e.printStackTrace(); }REVIEW: catch (Exception e) {} // swallowREVIEW: @ExceptionHandler log.error(id, e); return ProblemDetail;
⚠️ In stack trace ra màn hình hoặc nuốt chửng ngoại lệ khiến không thể giám sát sự 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)
// HAI ANTI-PATTERN XẤU NHẤT KHI REVIEW BẮT LỖI:
// 1. Nuốt ngoại lệ (Swallow exception) làm mất hoàn toàn dấu vết lỗi:
try { doSomething(); } catch (Exception e) {} 

// 2. In thẳng vết ngăn xếp ra màn hình:
try { doSomething(); } catch (SQLException e) {
    e.printStackTrace(); // Lộ tên bảng, cột, IP database ra ngoài!
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN ĐƯỢC PHÊ DUYỆT:
@ExceptionHandler(Exception.class)
public ProblemDetail handleUnexpectedException(Exception ex) {
    String errorTrackingId = UUID.randomUUID().toString();
    // Ghi log chi tiết trên máy chủ để điều tra nội bộ
    log.error("Unhandled exception [TrackingID: {}]", errorTrackingId, ex);

    // Trả về cho Client thông điệp chung an toàn tuyệt đối
    ProblemDetail problem = ProblemDetail.forStatusAndDetail(
        HttpStatus.INTERNAL_SERVER_ERROR,
        "Đã xảy ra sự cố nội bộ. Vui lòng liên hệ hỗ trợ với mã theo dõi: " + errorTrackingId
    );
    problem.setTitle("Internal Server Error");
    return problem;
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Hành vi 'Nuốt ngoại lệ' (Catch Exception mà để trống không xử lý hay ghi log) gây ra nguy hại gì?
A. Làm cho sự cố bị che giấu hoàn toàn, hệ thống rơi vào trạng thái dữ liệu không nhất quán và đội ngũ an ninh không thể phát hiện các hành vi tấn công.
B. Làm hỏng bàn phím của lập trình viên.
C. Làm máy chủ tự động khởi động lại liên tục.
D. Không gây ra nguy hại 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)