Bảo mật ứng dụng web

Sê-ri 40 phần về bảo mật ứng dụng, mỗi phần dựng lại một lỗ hổng trên ứng dụng tự viết rồi đo cách chặn.

40/40 phần đã đăng Bảo mật
1 Tôi viết 8 route, ứng dụng lại trả lời ở 37 điểm — hai phần ba bề mặt tấn công không hề có trong mã nguồn Mô hình mối đe doạ bắt đầu bằng câu 'ứng dụng của tôi có những cửa nào'. Tôi đếm thật trên một app Flask: 8 route trong đầu, nhưng Flask đăng ký 31 cặp và 37 điểm trả lời từ bên ngoài. Framework tự mọc HEAD, OPTIONS, /static — những cửa bạn chưa bao giờ viết và chưa bao giờ bảo vệ. 02/09/2026 · 9 phút đọc 2 Ba cách chặn SQL injection trông an toàn ngang nhau — cho tới khi tôi đổi từ trường chuỗi sang trường số, và hai cách thủng ngay lập tức Bốn cách chặn SQL injection, đo trên ứng dụng thật. Lọc dấu nháy và thoát ký tự bằng tay trông chắc chắn ngang tham số hoá — trong đúng một ngữ cảnh. Payload '0 OR 1=1' không có lấy một dấu nháy, và nó phơi cả bảng. Chỉ tham số hoá đứng vững, vì lý do sâu hơn 'chặn ký tự xấu'. 02/09/2026 · 9 phút đọc 3 Tham số hoá chặn sạch SQL injection — trừ đúng một chỗ, và ở đó tôi trích trọn khoá bí mật 18 ký tự chỉ bằng thứ tự sắp xếp Tham số hoá là cách chặn SQL injection duy nhất thật sự đủ — nhưng chỉ ở vị trí giá trị. Có một chỗ nó không đặt vào được: tên cột trong ORDER BY. Tôi đo ở đó và đọc trọn một khoá bí mật không route nào hiển thị, một bit mỗi request, 144 request cho cả chuỗi. 02/09/2026 · 9 phút đọc 4 Mật khẩu là một dấu sao '*' đăng nhập thẳng vào tài khoản admin — đây là 'OR 1=1' viết bằng cú pháp LDAP, và cùng một lỗ với command injection SQL không phải nơi duy nhất trộn dữ liệu vào mã. Tôi dựng lại cùng lỗ hổng trên lệnh hệ thống (bộ lọc quên ký tự xuống dòng để lọt) và LDAP (mật khẩu '*' thành admin không cần biết mật khẩu). Cùng một lỗ mặc ba bộ quần áo, cùng một cách chặn: tách dữ liệu khỏi mã. 02/09/2026 · 9 phút đọc 5 Autoescape chặn sạch XSS trong HTML — rồi thủng gọn ở một thuộc tính href tôi tưởng đã an toàn Tôi không đếm xem payload có mặt trong HTML hay không — tôi mở Chromium thật và chờ hộp thoại alert bật lên. Autoescape thắng mọi payload trong nội dung HTML, nhưng để lọt trơn khi biến rơi vào href. Bài học: chặn đúng cho ngữ cảnh này không tự nhiên đủ cho ngữ cảnh kia. 02/09/2026 · 10 phút đọc 6 script-src 'self' chặn cả script inline lẫn eval dù tên chỉ nói 'cùng nguồn' — và một CSP có 'unsafe-inline' thì gần như không chống được XSS chút nào Tôi dựng một trang có năm đường chạy JavaScript, đổi header CSP và dùng Chromium thật xem đường nào bị chặn. Kết quả: 'self' làm nhiều hơn tên gọi (chặn luôn inline và eval — đúng chỗ XSS sống), 'unsafe-inline' mở lại đúng cánh cửa CSP sinh ra để đóng, và 'unsafe-eval' vô dụng nếu đứng một mình. Kèm một bài học đo lường: công cụ đo suýt bị chính thứ nó đang đo chặn lại. 02/09/2026 · 9 phút đọc 7 Cùng một thư viện làm sạch HTML: cấu hình chặt chặn 9/9 payload tấn công, tôi thêm một dòng allowlist là lọt ngay 3 — lỗ nằm ở người, không ở thư viện Ba thư viện làm sạch HTML trên chín payload XSS, xác minh bằng Chromium thật — script có THỰC SỰ chạy không. Cấu hình chặt: cả ba 0/9. Nhưng chính bleach đó, thêm on* handlers vào allowlist, để lọt 3/9. Bài học: bảo mật làm sạch nằm ở danh sách cho phép, không ở tên thư viện. 02/09/2026 · 9 phút đọc 8 Số dư tụt từ 1000 xuống 500 chỉ vì bạn mở nhầm một trang — và vì sao SameSite=Lax chặn được cú POST đó nhưng vẫn để mất tiền qua một route GET Tôi dựng một 'ngân hàng' có phiên thật, một trang tấn công ở tên miền khác và một Chromium thật ở giữa, rồi đo: SameSite=Lax một mình chặn CSRF cho POST không cần token, nhưng một route GET đổi trạng thái vẫn mất 500 đồng ngay cả với Lax. Token và SameSite cắt ở hai mắt xích khác nhau, và bạn nên có cả hai. 02/09/2026 · 9 phút đọc 9 HttpOnly làm cookie phiên vô hình với JavaScript nhưng máy chủ vẫn đọc trọn — và Path thì trông như tường ngăn mà thật ra chẳng ngăn được ai Tôi đặt bốn cookie mang thuộc tính khác nhau rồi đo bằng Chromium thật: JavaScript chỉ thấy đúng cookie không có HttpOnly, máy chủ nhận đủ mọi cookie bất kể thuộc tính, còn Path chỉ đổi nơi cookie được gửi chứ không phải ranh giới bảo mật. Mỗi thuộc tính đóng đúng một cánh cửa — biết cửa nào mới đặt đúng thuộc tính. 02/09/2026 · 8 phút đọc 10 Kẻ tấn công không đánh cắp phiên của bạn — nó cho bạn mượn phiên của nó, chờ bạn đăng nhập, rồi dùng lại. Cách chặn gói trong một dòng Session fixation lật ngược mọi thứ bạn biết về trộm phiên: kẻ tấn công tự chọn ID phiên rồi gieo cho nạn nhân, nên HttpOnly và HTTPS đều vô dụng — không có gì bị đánh cắp cả. Tôi đo cuộc tấn công thật và cách chặn nó bằng đúng một dòng: cấp ID mới khi đăng nhập. 02/09/2026 · 9 phút đọc 11 Tôi đặt alg:none vào một JWT rồi tự phong mình làm admin — máy chủ tin ngay, vì nó hỏi token 'anh muốn kiểm bằng thuật toán nào' Bốn cách dùng sai JWT, dựng lại và tấn công thật: alg:none tự phong admin, secret 'secret' bị bẻ trong một vòng lặp bảy phần tử, và HS/RS confusion thủng đúng khi bạn tự viết verify (PyJWT thì chặn). Sợi chỉ chung: ba trong bốn lỗi là tin vào thứ do chính kẻ tấn công kiểm soát. 02/09/2026 · 9 phút đọc 12 Tôi 'thu hồi' một JWT rồi đo — nó vẫn sống trọn đến khi hết hạn, vì không ai hỏi cơ sở dữ liệu; đây là điểm yếu cốt lõi của token không trạng thái JWT không trạng thái mở rộng tuyệt vời vì máy chủ chẳng lưu gì — nhưng chính vì thế nó không thu hồi được: token bị thu hồi vẫn được chấp nhận cho tới khi exp trôi qua. Tôi đo cửa sổ đó (bằng đúng thời hạn), và cách thoát: access token ngắn + refresh token CÓ trạng thái thu hồi tức thì. 02/09/2026 · 9 phút đọc 13 Ở cùng 200 ms mỗi lần băm, bcrypt và argon2 làm kẻ tấn công chậm y như nhau trên CPU — khác biệt thật là 4 KB so với 512 MB RAM, gấp 131.072 lần Lưu mật khẩu đúng cách nghĩa là không lưu mật khẩu. Tôi hiệu chỉnh bcrypt, scrypt, argon2 về cùng 200 ms/lần băm rồi đo: trên CPU cả ba làm kẻ tấn công chậm như nhau (5–6 lần đoán/giây), nên cấu hình đủ cao quan trọng hơn chọn thuật toán. Khác biệt thật lộ ra trên GPU/ASIC — argon2 dùng 512 MB RAM mỗi lần băm, vô hiệu hoá đúng lợi thế phần cứng của kẻ tấn công. 02/09/2026 · 9 phút đọc 14 bcrypt biến 4 ngày dò thành 3.800 năm — nhưng vẫn để lọt mật khẩu '123456' trong 51 giây: hai lớp khoá bảo vệ hai thứ khác nhau Tôi dựng một cuộc tấn công từ điển thật vào mười bản băm và đo: mật khẩu yếu bị bẻ 6/10 dù dùng bcrypt, còn mật khẩu mạnh biến từ '4 ngày trên SHA256' thành '3.800 năm trên bcrypt'. Hai lớp phòng thủ — mật khẩu mạnh và thuật toán chậm — nhân nhau chứ không cộng, và thiếu một lớp là sụp về mức của lớp yếu hơn. 02/09/2026 · 9 phút đọc 15 Route đăng nhập không giới hạn = máy dò mật khẩu miễn phí: 2.600 lần thử trong 70 giây, và bốn cách bịt nó — cái nào cũng còn một khe hở Một vòng lặp curl chạy chậm vẫn thử được 2.600 mật khẩu trong 70 giây khi route đăng nhập không chặn gì. Bốn chiến lược giới hạn kéo nó xuống 10, rồi 5 — nhưng mỗi cái mở ra một điểm mù riêng, trong đó có một khe hở biến chính lớp phòng thủ thành công cụ DoS. 02/09/2026 · 9 phút đọc 16 Mã TOTP sáu số của bạn không biết nó đang được gõ vào trang giả — và đó là lỗ hổng WebAuthn bịt bằng toán học Tôi dựng TOTP thật rồi đo ba tính chất của nó: mã đổi mỗi 30 giây, cửa sổ chấp nhận ±31 giây, và tự nó KHÔNG chống phát lại. Nhưng điểm yếu chí mạng là phishing thời gian thực — thứ WebAuthn chặn được vì nó gắn chữ ký vào đúng tên miền. Hai công nghệ, hai triết lý. 02/09/2026 · 9 phút đọc 17 Không có PKCE, chỉ cần chặn được một mã là kẻ tấn công đổi được token của bạn — tôi đo đúng lỗ hổng đó trong 'Đăng nhập bằng Google' Đo luồng authorization code của OAuth2: mã dùng một lần, redirect_uri khớp chính xác, và PKCE. Ba dòng cuối bảng đối xứng sáng tỏ — có PKCE thì chặn được mã vẫn vô dụng, không PKCE thì chặn được mã là chiếm được tài khoản. Vì sao authorization code + PKCE là luồng khuyến nghị cho MỌI ứng dụng. 02/09/2026 · 9 phút đọc 18 Đổi một con số trong URL, đọc thẻ tín dụng của người lạ — lỗ đứng đầu OWASP Top 10 chỉ là một câu if bị thiếu IDOR: đăng nhập bằng tài khoản của mình, đổi /don/1 thành /don/2, và server ngoan ngoãn trả về đơn của người khác. Tôi đo lỗ này thật, và chứng minh vì sao id khó đoán (UUID) KHÔNG phải phân quyền — cách chặn duy nhất đúng là kiểm quyền sở hữu ở server. 02/09/2026 · 9 phút đọc 19 Tôi viết SELECT * FROM don_hang quên hẳn lọc quyền — và cơ sở dữ liệu vẫn chỉ trả về đúng đơn của người dùng đó, nhờ 3 câu SQL IDOR là một câu if bị quên ở tầng ứng dụng, và bạn phải nhớ viết nó ở MỌI endpoint, mãi mãi. Row-Level Security của PostgreSQL đẩy phép kiểm quyền xuống chính bảng — nên cả một câu SELECT trần trụi không điều kiện cũng chỉ trả về dữ liệu người dùng được xem. Từ 'nhớ kiểm ở mọi nơi' thành 'định nghĩa một nơi, không thể bỏ qua'. 02/09/2026 · 9 phút đọc 20 Một tệp vừa là ảnh PNG thật (Pillow đọc được) vừa là mã PHP chạy được — và nó trả về uid=33 trên máy chủ của tôi Cho tải tệp lên là một trong những tính năng nguy hiểm nhất. Tôi dựng một tệp 118 byte vừa mở ra thành PNG 1×1 hợp lệ, vừa thực thi lệnh trên máy chủ khi bị phục vụ nhầm. Bài học phản trực giác: 'kiểm xem có phải ảnh không' vô dụng, vì nó THẬT SỰ là ảnh. 02/09/2026 · 9 phút đọc 21 Chức năng 'tải ảnh từ URL' vô hại của tôi đọc trộm khoá cloud của chính máy chủ — đây là cơ chế đằng sau vụ Capital One 100 triệu hồ sơ SSRF biến máy chủ của bạn thành cầu nối vào mạng nội bộ mà internet không chạm tới. Tôi dựng một tính năng tải ảnh từ URL và dùng nó đọc endpoint metadata cloud, rò ra khoá bí mật. Bài học: danh sách chặn thua danh sách cho phép — và lý do sâu hơn 'liệt kê cái xấu là vô tận'. 02/09/2026 · 10 phút đọc 22 Một tham số next=//evil bắt đầu bằng dấu gạch chéo vẫn ném nạn nhân ra host của kẻ tấn công — vì sao 'chỉ cho URL bắt đầu bằng /' là cái bẫy Tôi đo open redirect bằng Chromium thật: phép kiểm 'phải bắt đầu bằng /' chặn được https://evil.com và javascript: nhưng thủng với //evil và /\evil, vì trình duyệt đọc // là một tên miền mới. Chỉ danh sách trắng đích mới chặn sạch. Kèm cái bẫy Referer — token trong URL rò sang trang ngoài — và đo xem trình duyệt hiện đại đã vá được một nửa ra sao. 02/09/2026 · 8 phút đọc 23 Một dòng pickle.loads() và lệnh của kẻ tấn công chạy với quyền root — mà không có bước 'thực thi' nào cả; JSON cùng dữ liệu thì trơ như đá Với pickle, 'đọc dữ liệu' và 'chạy mã' là cùng một hành động: tôi dựng payload thật, và chỉ tải nó lên đã cho ra uid=0(root). JSON cùng chuỗi ấy chỉ trả về một dict. Khác biệt không nằm ở cách dùng mà ở bản chất định dạng — cái này lưu dữ liệu, cái kia lưu cả cách gọi hàm. 02/09/2026 · 9 phút đọc 24 Một tài liệu XML đọc trộm /etc/passwd của máy chủ — nhưng bốn trong sáu parser Python đã lặng lẽ chặn nó Tôi gửi một tài liệu XML có 'thực thể ngoài' trỏ vào /etc/passwd, và một parser cấu hình sai trả về nguyên danh sách người dùng hệ thống. Nhưng điều bất ngờ là bốn trong sáu cấu hình tôi thử đã mặc định an toàn. XXE ngày nay phần lớn là lỗi bật nhầm một cái cờ. 02/09/2026 · 10 phút đọc 25 Một ký tự xuống dòng lọt vào giá trị cookie chèn thêm header Set-Cookie giả và chia đôi cả phản hồi — nhưng thư viện hiện đại đã chặn nó ở tầng HTTP dựng cấu trúc trên một ký tự vô hình: cặp CRLF. Tôi dựng lại lỗ header injection — một \r\n trong giá trị người dùng tạo header giả và cả một thân phản hồi giả — rồi đo cách http.client và Werkzeug từ chối nó ngay. Lỗ chỉ còn sống khi bạn tự nối chuỗi response thô, và nó là cùng một con quái vật với SQL injection, XSS, command injection. 02/09/2026 · 9 phút đọc 26 Một trang lỗi debug quên tắt rò 14.276 ký tự: đường dẫn, số dòng, biến cục bộ — và tên bảng SQL biến kẻ dò mù thành nhắm trúng Thông báo lỗi sinh ra để giúp lập trình viên — và khi hiện cho người dùng, nó giúp luôn kẻ tấn công. Tôi đo lượng thông tin rò ở chế độ debug so với production: gấp ~700 lần, và một lỗi SQL nguyên văn phơi cả tên bảng lẫn câu truy vấn. Bài học: thông báo chung ra ngoài, log chi tiết vào trong. 02/09/2026 · 9 phút đọc 27 So sánh token dừng sớm rò 9 nano giây cho mỗi byte đoán đúng — đủ để dò cả bí mật, byte một, mà phản hồi chẳng nói gì Một timing side channel: cách bạn so sánh token với bí mật để lộ token đúng tới đâu — không qua nội dung, mà qua thời gian. Byte-by-byte dừng sớm rò tuyến tính ~9 ns/byte; compare_digest giữ 666 ns bất biến. Và một bất ngờ: == của Python lại KHÔNG lộ, vì một lý do đáng nhớ. 02/09/2026 · 9 phút đọc 28 Một chuỗi 30 ký tự giữ lõi CPU bận 20 giây, 49 KB nở thành 50 MB — DoS tầng ứng dụng không cần lưu lượng, nó cần chi phí bất đối xứng DoS thường được hình dung là cơn lũ lưu lượng. Nhưng loại nguy hiểm hơn chỉ cần một request nhỏ mà đắt: regex 30 ký tự làm máy chủ bận 20 giây, 49 KB nén giải ra 50 MB, chính cơ chế băm mật khẩu chậm trở thành vũ khí. Bốn kiểu bất đối xứng, đo thật. 02/09/2026 · 9 phút đọc 29 195 KB JSON làm sập cả tiến trình, còn giới hạn body 1 MB không cứu nổi — ba trục bùng nổ khi parse mà một cái trần không chặn hết Kẻ tấn công parse không chỉ điều khiển kích thước dữ liệu mà cả cấu trúc của nó, và mỗi chiều đánh vào một tài nguyên khác nhau: JSON lồng sâu 100.000 cấp (195 KB) làm tràn stack, một số 100.000 chữ số đốt CPU O(n²), một mảng 5 triệu phần tử ngốn RAM. Tôi đo cả ba, và cho thấy vì sao giới hạn kích thước body — biện pháp phổ biến nhất — chỉ chặn được đúng một trục. 02/09/2026 · 8 phút đọc 30 Trang của kẻ tấn công đọc trọn số dư và số thẻ của người dùng đã đăng nhập — chỉ vì một dòng CORS 'phản chiếu Origin' trông y hệt một danh sách trắng động CORS không phải lớp bảo mật thêm vào — nó là cơ chế nới lỏng same-origin policy, và cấu hình sai mở toang cánh cửa vốn được khoá. Tôi đo bằng Chromium thật: cấu hình phản chiếu Origin cho trang evil đọc trọn dữ liệu có credentials, trong khi '*' bị chuẩn chặn và danh sách trắng thì an toàn. Kèm một hiểu lầm cần gỡ: 'Failed to fetch' không có nghĩa request bị chặn. 02/09/2026 · 8 phút đọc 31 Bắt tay TLS tốn chưa tới 1 ms và chỉ trả một lần — nên 'TLS chậm' là cớ; cái thật sự nguy là dòng CERT_NONE viết lúc dev rồi theo bài lên production Cùng một chữ HTTPS trên thanh địa chỉ có thể là TLS 1.3 cipher 256-bit, cũng có thể là máy chủ vẫn nhận TLS 1.0 và chứng chỉ chẳng ai ký. Tôi đo thời gian bắt tay (dưới 1 ms, trả một lần), thử ép giao thức cũ (bị từ chối), và chứng minh vì sao tắt kiểm chứng chỉ là vứt bỏ chống MITM — mã hoá mà không xác minh danh tính là khoá cửa rồi đưa chìa cho người lạ. 02/09/2026 · 9 phút đọc 32 Xoá khoá API khỏi file rồi commit lại — quét lịch sử vẫn thấy nguyên nó, và cái khoá vẫn dùng được sau cả rebase lẫn force-push Phản xạ khi lỡ commit một khoá là xoá dòng, commit lại, thở phào. Tôi đo hai điều cái thở phào bỏ qua: xoá khỏi file không xoá khỏi lịch sử git (quét working-tree 0, quét lịch sử 2), và viết lại lịch sử không vô hiệu credential — token vẫn sống qua rebase/force-push, chỉ chết khi xoay vòng. Lỡ lộ thì xoay vòng trước, dọn git sau. 02/09/2026 · 9 phút đọc 33 Bốn chỗ để một bí mật, xếp theo 'ai đọc được': hardcode lộ cho cả lịch sử git, còn biến môi trường thì mọi tiến trình con và /proc đều thấy Để bí mật ở đâu cho đúng? Tôi đo bốn cách bằng một câu hỏi kiểm chứng được — ai đọc được nó — chứ không xếp thang cảm tính: hardcode (cả repo + lịch sử), biến môi trường (con thừa kế, /proc/environ, docker inspect), .env (an toàn đúng bằng quyền file), và file 400 trên tmpfs (hẹp nhất). Kèm một lần tôi đo sai /proc suýt kết luận biến môi trường kín. 02/09/2026 · 9 phút đọc 34 Tôi khai hai gói, môi trường thật cài bảy — và 17 trong 28 lỗ hổng nằm ở những gói tôi chưa bao giờ chọn import requests không phải thêm một thư viện mà cả một cây. Tôi khai hai dependency cũ rồi quét bằng pip-audit: 28 lỗ hổng, phần lớn ở gói bắc cầu tôi chưa từng nghe tên (urllib3 một mình mang 13). Nâng phiên bản kéo cả 28 về 0 bằng một lệnh — nên cái đắt không phải vá, mà là biết mình cần vá. Kèm một lần tôi đo sai phạm vi phải làm lại. 02/09/2026 · 9 phút đọc 35 Sửa một byte là chữ ký gãy, kẻ tấn công tự ký lại vẫn trượt — nhưng cái checksum SHA256 bên cạnh file tải về thì ai sửa file cũng tính lại được Checksum và chữ ký trông na ná nhau — đều là một chuỗi băm để kiểm — nhưng khác nhau ở toàn bộ vấn đề: cái nào cần bí mật để tạo ra. Tôi dựng quy trình ký Ed25519 và tấn công ba cách: sửa byte lộ, tự ký lại lộ; rồi cho thấy checksum chống được tai nạn nhưng vô dụng trước kẻ kiểm soát nơi phát hành, vì nó không đòi bí mật gì cả. 02/09/2026 · 9 phút đọc 36 Một dòng log 'cho đầy đủ' đẩy mật khẩu, token và số thẻ vào file ở dạng nguyên văn — và bộ lọc che khoá vẫn để lọt bí mật qua một câu thông báo lỗi Nhật ký kiểm toán là thứ bạn biết ơn khi có sự cố, nhưng ghi quá tay thì chính nó thành kho bí mật rò rỉ. Tôi đo: log ngây thơ đẩy 3 bí mật nguyên văn vào file, bộ lọc che theo khoá kéo về 0 mà vẫn giữ đủ dấu vết điều tra — rồi một exception ghi nguyên văn lại lọt bí mật qua đường bộ lọc không ngờ tới. Ghi nhiều hơn không an toàn hơn. 02/09/2026 · 9 phút đọc 37 SAST bắt eval trong hàm chết mà DAST không bao giờ chạm tới; DAST thấy traceback rò ra mà SAST không nằm ở dòng nào — chọn một là tự tin sai chỗ SAST đọc mã mà không chạy, DAST tấn công app đang chạy mà không đọc mã. Tôi gài lỗ hổng vào một app rồi chạy bandit và một DAST tự viết: mỗi công cụ bắt đúng cái công cụ kia mù, gần như bù trừ hoàn toàn — SAST cho danh sách *khả năng*, DAST cho danh sách *thực tế*, và một câu SQLi lệch khuôn thì cả hai đều hụt. 02/09/2026 · 9 phút đọc 38 yaml.load từng chạy lệnh shell của kẻ tấn công chỉ với một dòng — bản vá đổi mặc định sang an toàn, nhưng tôi tự mở lại lỗ hổng bằng đúng cách 'sửa lỗi' ai cũng làm 'Vá thì cứ nâng phiên bản là xong' bỏ qua ba câu hỏi thật: vá sửa cái gì, lỗ hổng có biến mất hẳn không, và bạn phơi nhiễm bao lâu giữa lúc vá ra và lúc thật sự dùng nó. Tôi tái dựng RCE của PyYAML: bản cũ dính, bản mới chặn — rồi vô tình dán lại đúng lỗ hổng bằng cách thêm Loader=yaml.Loader để hết TypeError, đúng cái bẫy khiến lỗ này sống sót qua bản vá. 02/09/2026 · 10 phút đọc 39 Chạy danh sách kiểm bảo mật lên chính cái blog này: tám mục đạt, khoá xanh, header đầy đủ — và một dòng curl vẫn moi ra cookie phiên thiếu cờ Secure Làm sao BIẾT một ứng dụng đã cấu hình đúng, không phải bằng cảm giác 'chắc ổn'? Tôi chạy một danh sách kiểm chỉ-đọc lên chính blog Spring Boot đang chạy: header đủ, actuator đóng kín, trang lỗi sạch — nhưng cookie JSESSIONID thiếu cờ Secure, đúng cái mục mà sự tự tin chung che mất. Danh sách kiểm biến 'có an toàn không' mơ hồ thành một bảng từng dòng đạt/không đạt. 02/09/2026 · 9 phút đọc 40 Bốn lớp lỗ hổng, một bảng: cách an toàn chặn 0/4, cách 'tự tắt bảo vệ' lọt 4/4 — bảo mật không phải thêm gì, mà là đừng gỡ Bài chốt sê-ri bảo mật web: tôi gom bốn lớp lỗ hổng khác hẳn nhau (chạy mã từ chuỗi, SQL injection, shell injection, YAML RCE) vào một phép đo — cách khuyến nghị chặn sạch cả bốn, cách opt-out lọt cả bốn. Công cụ hiện đại đã mặc định an toàn; lỗ hổng gần như luôn đến từ hành động chủ động gỡ lớp bảo vệ có sẵn, không phải từ việc thiếu một lớp mới. 02/09/2026 · 9 phút đọc