Chương 39. Web security thực dụng
Ba chương trước ta đã nói về:
- Authentication: xác nhận bạn là ai.
- Authorization: bạn được làm gì.
- Service-to-service security: service gọi nhau có an toàn không.
Chương này gom lại các vấn đề bảo mật web rất thực tế.
Không phải để biến bạn thành chuyên gia security trong một chương.
Mục tiêu là:
Biết các lỗi phổ biến.
Biết chúng xảy ra khi nào.
Biết cách phòng ở mức thiết kế hệ thống.
Biết câu hỏi cần hỏi trước khi đưa app lên production.
Các chủ đề chính:
- HTTPS.
- CORS.
- CSRF.
- XSS.
- SQL injection.
- SSRF.
- File upload attack.
- Rate limiting.
- Audit log.
Thông điệp chính của chương:
> Web security không phải một lớp rắc thêm sau cùng. Nó phải nằm trong cách ta nhận request, xử lý input, kiểm tra quyền, gọi service, lưu file, log, và vận hành hệ thống.
---
39.1. Ví dụ dễ hiểu: cửa hàng có nhiều cửa
Một cửa hàng không chỉ cần khóa cửa chính.
Còn có:
- Cửa sau.
- Cửa kho.
- Cửa giao hàng.
- Camera.
- Sổ ra vào.
- Quy định ai được mở két.
- Kiểm tra hàng lạ gửi vào.
- Báo động khi có người thử mở cửa quá nhiều lần.
Web app cũng vậy.
Đăng nhập tốt chưa đủ.
Bạn còn cần:
- Kết nối an toàn.
- Không để website khác lợi dụng cookie.
- Không để script độc chạy trong trang.
- Không để input biến thành SQL.
- Không để server bị lừa gọi URL nội bộ.
- Không để upload file độc.
- Không để attacker thử vô hạn.
- Không để hành động nhạy cảm không có audit.
Bảo mật là nhiều lớp.
---
39.2. HTTPS là gì?
HTTPS là HTTP chạy trên TLS.
Nó giúp:
- Mã hóa dữ liệu trên đường truyền.
- Xác thực server mà browser đang nói chuyện.
- Giảm rủi ro bị nghe lén hoặc sửa request/response.
Nếu dùng HTTP thường, người ở giữa mạng có thể thấy hoặc sửa dữ liệu.
Ví dụ:
Password.
Session cookie.
Access token.
Bài nộp.
Feedback riêng.
Thông tin học sinh.
Với sản phẩm thật:
> HTTPS là mặc định, không phải tính năng cao cấp.
---
39.3. HTTPS không chỉ cho trang login
Sai lầm cũ:
Chỉ trang login cần HTTPS.
Sai.
Toàn bộ app nên dùng HTTPS.
Vì sau login, request vẫn mang:
- Cookie session.
- Token.
- Dữ liệu private.
- API response.
- File download.
Nếu một trang sau login dùng HTTP, session có thể bị lộ.
Quy tắc:
Production web app:
HTTPS everywhere.
Ngoài ra nên bật:
Secure cookie
HSTS nếu đã sẵn sàng
Redirect HTTP -> HTTPS
---
39.4. HSTS là gì?
HSTS là HTTP Strict Transport Security.
Nó nói với browser:
Lần sau chỉ được truy cập domain này bằng HTTPS.
HSTS giúp chống downgrade sang HTTP.
Nhưng cần cẩn thận:
- Chỉ bật khi HTTPS đã ổn.
- Cấu hình sai có thể làm domain khó truy cập.
- Include subdomains cần chắc mọi subdomain đều hỗ trợ HTTPS.
Không cần vội hiểu sâu.
Chỉ cần nhớ:
> HTTPS là bắt buộc. HSTS là lớp bổ sung khi production đã cấu hình chắc.
---
39.5. CORS là gì?
CORS là Cross-Origin Resource Sharing.
Nó là cơ chế browser dùng để quyết định:
Website origin A có được gọi API origin B và đọc response không?
Ví dụ:
Frontend: https://app.synvia.com
API: https://api.synvia.com
Hai origin khác nhau.
Browser cần biết API có cho frontend này đọc response không.
Server trả header như:
Access-Control-Allow-Origin: https://app.synvia.com
CORS là chính sách của browser.
Nó không phải cơ chế authentication.
---
39.6. CORS không phải bảo mật backend đầy đủ
Hiểu nhầm thường gặp:
Tôi cấu hình CORS rồi, API an toàn.
Không đủ.
CORS chủ yếu ngăn website khác đọc response trong browser.
Nhưng attacker vẫn có thể gọi API bằng:
- curl.
- script server-side.
- Postman.
- bot.
- mobile client giả.
Backend vẫn phải kiểm tra:
- Authentication.
- Authorization.
- CSRF nếu dùng cookie.
- Rate limit.
- Input validation.
CORS không thay thế permission.
Nó chỉ là một lớp cho browser.
---
39.7. CORS cấu hình sai thường gặp
Lỗi nguy hiểm:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Nếu API dùng cookie/credential, không nên mở bừa cho mọi origin.
Lỗi khác:
Cho phép mọi origin vì dev cho tiện.
Production nên allowlist origin rõ:
https://app.example.com
https://admin.example.com
Không nên tự động echo mọi Origin gửi lên:
Origin: https://evil.com
-> Allow-Origin: https://evil.com
trừ khi có kiểm tra allowlist chặt.
Quy tắc:
> CORS production nên rõ ràng và hẹp.
---
39.8. CSRF là gì?
CSRF là Cross-Site Request Forgery.
Nói dễ hiểu:
Website xấu lừa browser của user gửi request đến website thật.
Vì browser tự gửi cookie cho domain đã đăng nhập.
Ví dụ:
User đang login app.synvia.com.
User mở evil.com.
evil.com tạo form/request POST đến app.synvia.com/change-email.
Browser tự gửi cookie session của app.synvia.com.
Nếu backend không chống CSRF, request có thể được xử lý như user thật.
CSRF đặc biệt liên quan đến cookie-based auth.
---
39.9. Cách chống CSRF
Các cách thường dùng:
- SameSite cookie.
- CSRF token.
- Kiểm tra Origin/Referer cho request nhạy cảm.
- Không dùng GET cho hành động thay đổi dữ liệu.
- Yêu cầu re-auth cho action rất nhạy cảm.
SameSite giúp giảm request cross-site tự mang cookie.
CSRF token là token mà site thật biết, site xấu không biết.
Ví dụ:
Form/request phải gửi csrf_token hợp lệ.
Nếu app dùng bearer token trong Authorization header và không tự động gửi credential cross-site, CSRF ít hơn.
Nhưng vẫn phải hiểu auth flow cụ thể.
Quy tắc:
> Nếu dùng cookie để xác thực, hãy nghĩ đến CSRF.
---
39.10. XSS là gì?
XSS là Cross-Site Scripting.
Nó xảy ra khi attacker đưa được script độc vào trang của bạn.
Ví dụ comment:
<script>stealToken()</script>
Nếu app render thẳng nội dung đó, script chạy trong browser của user khác.
XSS nguy hiểm vì script chạy trong origin thật.
Nó có thể:
- Đọc dữ liệu trên trang.
- Gọi API thay user.
- Lấy token nếu token nằm nơi JavaScript đọc được.
- Sửa UI.
- Gửi dữ liệu ra ngoài.
XSS là một trong những lỗi web phổ biến và đắt.
---
39.11. Stored, reflected, DOM XSS
Stored XSS:
Script độc được lưu vào database rồi hiển thị cho nhiều user.
Ví dụ:
Tên assignment chứa script.
Feedback chứa HTML độc.
Comment chứa script.
Reflected XSS:
Script nằm trong request và được phản hồi ngay trong response.
Ví dụ:
/search?q=<script>...</script>
DOM XSS:
JavaScript frontend xử lý input không an toàn và tự tạo DOM nguy hiểm.
Ví dụ:
element.innerHTML = userInput
Ba loại khác nhau, nhưng cùng ý:
> Dữ liệu không tin cậy được biến thành code chạy trong browser.
---
39.12. Cách phòng XSS
Các nguyên tắc:
- Escape output theo đúng context.
- Không render HTML user nhập nếu không cần.
- Nếu phải render HTML, sanitize bằng thư viện tốt.
- Tránh dùng
innerHTMLvới dữ liệu không tin cậy. - Dùng framework đúng cách.
- Dùng HttpOnly cookie để giảm rủi ro token bị đọc.
- Dùng Content Security Policy nếu phù hợp.
Context rất quan trọng.
Escape trong HTML body khác escape trong attribute, URL, JavaScript string.
Framework hiện đại như React/Vue/Svelte thường escape text mặc định.
Nhưng vẫn có đường nguy hiểm:
dangerouslySetInnerHTML
v-html
innerHTML
Markdown render không sanitize
Nếu app có Markdown/HTML editor, phải cực kỳ cẩn thận.
---
39.13. Content Security Policy là gì?
CSP là header giúp giới hạn script/style/image được tải từ đâu.
Ví dụ:
Content-Security-Policy: script-src 'self'
CSP có thể giảm tác hại XSS.
Nhưng CSP không thay thế việc escape/sanitize.
Nó là lớp phòng thủ bổ sung.
CSP cấu hình sai có thể làm app lỗi.
Nên triển khai từng bước:
- Report-only trước.
- Theo dõi vi phạm.
- Siết dần.
Thông điệp:
> CSP tốt, nhưng đừng dùng nó để che cho render HTML bừa bãi.
---
39.14. SQL injection là gì?
SQL injection xảy ra khi input của user bị ghép thẳng vào câu SQL.
Ví dụ xấu:
SELECT * FROM users WHERE email = '" + email + "'
Nếu attacker nhập:
' OR '1'='1
câu SQL có thể bị đổi nghĩa.
SQL injection có thể dẫn đến:
- Đọc dữ liệu trái phép.
- Bỏ qua login.
- Sửa dữ liệu.
- Xóa dữ liệu.
- Lộ toàn bộ database.
Đây là lỗi cũ nhưng vẫn còn rất nguy hiểm.
---
39.15. Cách phòng SQL injection
Cách chính:
Dùng parameterized query / prepared statement / ORM đúng cách.
Ví dụ tư duy:
SELECT * FROM users WHERE email = ?
Input được truyền như dữ liệu, không phải một phần của SQL.
Ngoài ra:
- Không tự nối chuỗi SQL từ input.
- Cẩn thận với dynamic ORDER BY/filter.
- Validate các field cho phép.
- Dùng quyền database hẹp.
- Không dùng database superuser cho app.
ORM giúp nhiều, nhưng không miễn nhiễm nếu bạn dùng raw query sai.
Đặc biệt cẩn thận với:
search
filter
sort
report builder
admin query
---
39.16. NoSQL injection cũng tồn tại
Không chỉ SQL mới có injection.
Ví dụ Mongo-like query nếu nhận object từ user bừa bãi:
{
"password": { "$ne": null }
}
Nếu backend đưa thẳng vào query, có thể bypass logic.
Nguyên tắc chung:
> User input không được trở thành query logic nếu chưa validate.
Dù là SQL, NoSQL, search query, filter DSL, GraphQL, hay command line, input vẫn phải được kiểm soát.
---
39.17. SSRF là gì?
SSRF là Server-Side Request Forgery.
Nó xảy ra khi attacker khiến server của bạn gọi URL do attacker chọn.
Ví dụ app có tính năng:
Nhập URL để import ảnh.
Tạo preview từ link.
Fetch webhook URL.
Import tài liệu từ URL.
Attacker nhập:
http://localhost:8000/admin
http://internal-service/secrets
http://169.254.169.254/metadata
Server gọi URL đó từ bên trong mạng nội bộ.
Nếu không chặn, attacker dùng server của bạn làm cầu nối vào nội bộ.
---
39.18. Cách phòng SSRF
Nếu có feature fetch URL, cần:
- Allowlist domain nếu có thể.
- Chặn IP private/internal/link-local.
- Chặn redirect tới IP nguy hiểm.
- Resolve DNS và kiểm tra IP thật.
- Giới hạn protocol: chỉ http/https nếu cần.
- Timeout ngắn.
- Giới hạn size response.
- Không gửi credential nội bộ theo request.
- Không cho gọi metadata service.
Không nên chỉ check string đơn giản:
URL không chứa "localhost" là được.
Vì attacker có nhiều cách né:
- IP dạng số khác.
- DNS trỏ về IP nội bộ.
- Redirect.
- IPv6.
- Subdomain lừa.
SSRF khó hơn vẻ ngoài.
Nếu không cần fetch URL tùy ý, đừng mở tính năng này.
---
39.19. File upload attack
Chương 33 đã nói về file storage.
Ở đây ta nhìn từ góc bảo mật.
File upload nguy hiểm vì user đưa bytes lạ vào hệ thống.
Rủi ro:
- Malware.
- File giả dạng.
- Script trong SVG/HTML.
- Zip bomb.
- File quá lớn.
- File nén chứa path nguy hiểm.
- PDF/Office file độc.
- Image parser vulnerability.
- Upload rồi public execute.
- Lộ file private vì bucket public.
Quy tắc:
> File upload là input không tin cậy.
---
39.20. Cách phòng file upload attack
Các biện pháp:
- Giới hạn size.
- Allowlist file type.
- Kiểm tra extension + content type + magic bytes nếu cần.
- Không tin filename user gửi.
- Lưu file ngoài web root.
- Object storage private mặc định.
- Scan virus nếu phù hợp.
- Xử lý file trong sandbox nếu nguy hiểm.
- Không execute file upload.
- Tạo thumbnail/preview bằng worker cô lập.
- Cleanup file tạm.
- Signed URL cho file private.
Ví dụ xấu:
User upload avatar.php
Server lưu vào public/uploads/avatar.php
Web server execute file đó.
Ví dụ tốt hơn:
Upload vào object storage private.
Worker validate/resize.
Chỉ phục vụ ảnh đã xử lý qua CDN.
---
39.21. Zip bomb và giải nén
Zip bomb là file nén nhỏ nhưng giải nén ra cực lớn.
Ví dụ:
upload.zip = 5 MB
giải nén ra = 50 GB
Nếu worker giải nén không giới hạn, server có thể hết disk/RAM.
Khi giải nén file user upload, cần giới hạn:
- Tổng dung lượng sau giải nén.
- Số file.
- Độ sâu thư mục.
- Thời gian xử lý.
- Loại file.
- Path traversal.
Path traversal trong zip:
../../app/config.py
Nếu giải nén sai, file có thể ghi ra ngoài thư mục mong muốn.
AI Judge nhận source zip càng phải cẩn thận.
---
39.22. Chạy code user upload
AI Judge có một rủi ro đặc biệt:
User upload code.
Hệ thống có thể chạy code để test/chấm.
Code user gửi là không tin cậy.
Không được chạy trực tiếp trên server chính.
Cần:
- Sandbox/container.
- CPU limit.
- Memory limit.
- Time limit.
- Disk limit.
- Network disabled hoặc giới hạn.
- User quyền thấp.
- Filesystem tạm.
- Cleanup sau chạy.
- Không mount secret vào môi trường chạy code.
Nếu code user chạy trong môi trường có secret, attacker có thể đọc secret.
Nếu có network, code có thể gọi ra ngoài hoặc dò nội bộ.
Đây là vùng bảo mật rất quan trọng với AI Judge.
---
39.23. Rate limiting là gì?
Rate limiting là giới hạn số request trong một khoảng thời gian.
Ví dụ:
5 lần login sai mỗi phút.
100 request API mỗi phút mỗi user.
10 lần gửi password reset mỗi giờ.
Rate limit giúp chống:
- Brute force.
- Spam.
- Abuse.
- Bot.
- Cost explosion.
- DDoS nhẹ.
- Bug client retry quá nhanh.
Rate limit không chỉ dành cho public API.
Internal API tốn tài nguyên cũng nên có giới hạn phù hợp.
---
39.24. Rate limit theo cái gì?
Có thể limit theo:
- IP.
- User id.
- Tenant id.
- API key.
- Endpoint.
- Device/session.
- Combination.
Ví dụ login:
Limit theo IP + account/email.
Ví dụ AI Judge:
Limit số bài nộp mỗi user mỗi phút.
Limit số job AI mỗi tenant mỗi giờ.
Limit số lần retry.
Limit số export report.
Nếu chỉ limit theo IP, nhiều user cùng trường có thể bị ảnh hưởng.
Nếu chỉ limit theo user, attacker có thể thử nhiều account.
Thường cần kết hợp.
---
39.25. Rate limit và quota khác nhau thế nào?
Rate limit:
Giới hạn tốc độ trong thời gian ngắn.
Ví dụ:
100 request/phút.
Quota:
Giới hạn tổng mức dùng trong kỳ.
Ví dụ:
10.000 bài chấm/tháng.
100 GB storage.
50 USD chi phí AI/ngày.
Trong AI Judge, cả hai đều quan trọng.
Rate limit chống spam tức thời.
Quota kiểm soát tài nguyên và chi phí dài hơn.
---
39.26. Input validation
Input validation là kiểm tra dữ liệu đầu vào.
Ví dụ:
- Email có format hợp lý.
- Page size không quá lớn.
- File size trong giới hạn.
- Enum chỉ nhận giá trị cho phép.
- Date range không quá rộng.
- Sort field nằm trong allowlist.
- Tenant id hợp lệ và user thuộc tenant đó.
Validation không chỉ để UX.
Nó là bảo mật.
Ví dụ:
limit=1000000
nếu không giới hạn có thể làm database nặng.
sort=some_raw_sql
nếu đưa thẳng vào query có thể injection.
Validate càng gần biên hệ thống càng tốt.
---
39.27. Mass assignment
Mass assignment xảy ra khi backend nhận object từ client và update thẳng vào model.
Ví dụ client gửi:
{
"name": "Bao",
"is_admin": true
}
Nếu backend update toàn bộ field được gửi:
user.update(request.body)
attacker có thể sửa field không nên sửa.
Cách phòng:
- Dùng allowlist field.
- DTO/schema riêng cho input.
- Không expose field nhạy cảm.
- Tách admin update khỏi user self-update.
Ví dụ:
User tự sửa profile chỉ được update:
display_name
avatar
Không được update:
role
tenant_id
is_admin
---
39.28. Open redirect
Open redirect là khi app redirect tới URL do user kiểm soát mà không kiểm tra.
Ví dụ:
/login?next=https://evil.com
Sau login, app redirect user sang evil.com.
Attacker dùng domain thật để lừa user.
Cách phòng:
- Chỉ cho relative path.
- Allowlist domain nếu cần.
- Không redirect tới URL tùy ý.
- Kiểm tra kỹ
next,return_url,redirect_uri.
OAuth/OIDC redirect URI càng phải chặt.
Không được allow wildcard bừa bãi.
---
39.29. Security headers cơ bản
Một số header hữu ích:
Strict-Transport-Security
Content-Security-Policy
X-Content-Type-Options: nosniff
X-Frame-Options hoặc CSP frame-ancestors
Referrer-Policy
Permissions-Policy
Không cần nhớ hết ngay.
Ý chính:
- Chống downgrade HTTP.
- Giảm tác hại XSS.
- Tránh browser đoán sai content type.
- Chống clickjacking.
- Giảm lộ referrer.
- Hạn chế API browser không cần.
Security headers là lớp bổ sung.
Không thay thế auth, validation, permission.
---
39.30. Clickjacking
Clickjacking là khi attacker nhúng trang của bạn trong iframe và lừa user bấm.
Ví dụ:
User tưởng bấm nút trên trang xấu.
Thực ra bấm nút trong app của bạn bị che phủ.
Cách phòng:
X-Frame-Options: DENY/SAMEORIGIN
hoặc:
Content-Security-Policy: frame-ancestors ...
Các trang nhạy cảm như admin, billing, change password, grade override không nên bị embed tùy ý.
---
39.31. Audit log là gì?
Audit log là nhật ký các hành động quan trọng.
Không phải log debug bình thường.
Audit log trả lời:
Ai đã làm gì, với resource nào, lúc nào, từ đâu, kết quả ra sao?
Ví dụ:
teacher_score_overridden
rubric_changed
student_data_exported
admin_logged_in
permission_granted
file_downloaded
api_key_created
api_key_revoked
Audit log rất quan trọng khi:
- Có dữ liệu nhạy cảm.
- Có admin/teacher.
- Có multi-tenant.
- Có hành động không dễ rollback.
- Cần điều tra sự cố.
---
39.32. Audit log nên ghi gì?
Một audit event tốt thường có:
actor_id
actor_type
action
resource_type
resource_id
tenant_id
timestamp
ip
user_agent
request_id
result
reason
metadata vừa đủ
Ví dụ:
actor=user_9
action=score.override
resource=grading_result_123
tenant=school_A
result=success
reason="teacher correction"
Không nên log:
- Password.
- Token.
- Secret.
- Nội dung private quá mức cần thiết.
- Dữ liệu nhạy cảm không cần cho audit.
Audit log cũng là dữ liệu nhạy cảm.
Phải phân quyền xem audit log.
---
39.33. Audit log nên khó sửa
Nếu audit log dễ bị sửa/xóa bởi cùng người bị audit, giá trị giảm mạnh.
Tùy mức nghiêm túc, có thể:
- Ghi append-only.
- Hạn chế quyền xóa.
- Đưa sang log system riêng.
- Dùng storage có retention.
- Ghi hash chain nếu rất nghiêm ngặt.
Không phải hệ thống nào cũng cần mức cao.
Nhưng ít nhất:
Admin thường không nên sửa audit log một cách tùy tiện.
Audit log dùng để điều tra, nên phải đáng tin.
---
39.34. Error message không nên lộ quá nhiều
Lỗi trả về user không nên lộ:
- Stack trace.
- SQL query.
- Secret path.
- Internal service name nếu không cần.
- Token.
- Config.
- Chi tiết database.
Ví dụ xấu:
PostgreSQL error: relation users_passwords...
Traceback...
AWS_SECRET_ACCESS_KEY...
Production nên trả message gọn:
Something went wrong.
hoặc message domain an toàn.
Chi tiết lỗi đi vào log nội bộ.
Nhưng log cũng phải tránh secret.
---
39.35. Debug endpoint và admin endpoint
Debug endpoint rất nguy hiểm nếu lộ production.
Ví dụ:
/debug/metricskhông bảo vệ./adminmặc định./internal/retry-job/docspublic với API nhạy cảm.- Swagger có thể gọi API production.
Không nhất thiết phải tắt hết docs.
Nhưng phải kiểm soát:
- Auth.
- Network allowlist.
- Environment.
- Permission.
- Không expose secret/config.
Endpoint phục vụ vận hành cũng là attack surface.
---
39.36. Dependency security
App dùng rất nhiều thư viện.
Thư viện có thể có lỗ hổng.
Cần:
- Cập nhật dependency định kỳ.
- Theo dõi security advisory.
- Dùng lockfile.
- Scan dependency trong CI nếu có.
- Không cài package lạ không kiểm tra.
- Gỡ dependency không dùng.
Supply chain security là mảng lớn.
Ở mức thực dụng:
> Đừng để app production chạy mãi với dependency cũ có CVE nghiêm trọng.
---
39.37. Secrets trong frontend
Frontend code được gửi tới browser.
Vì vậy mọi thứ trong frontend đều có thể bị user xem.
Không được đặt secret thật trong frontend.
Ví dụ không được:
AI provider secret key.
Database password.
Private API key.
JWT signing key.
Payment secret key.
Một số key public có thể nằm frontend nếu provider thiết kế như vậy.
Ví dụ:
Publishable key.
Client id.
Public analytics key.
Nhưng phải hiểu nó là public.
Nếu key cho phép thao tác nhạy cảm, nó không được nằm ở frontend.
---
39.38. AI product có thêm rủi ro gì?
Với AI Judge, ngoài web security bình thường còn có:
- Prompt injection nếu AI đọc nội dung user.
- Data leakage qua prompt/log.
- Gửi dữ liệu nhạy cảm sang provider.
- Chi phí API bị spam.
- Worker chạy quá nhiều job.
- Upload code/file nguy hiểm.
- Output AI chứa nội dung không phù hợp.
- Teacher/student data bị đưa vào analytics/log quá mức.
Không cần xử lý tất cả trong một chương.
Nhưng cần nhớ:
> AI API cũng là external service, input user vẫn không tin cậy, và chi phí cũng là tài nguyên cần bảo vệ.
Rate limit, quota, audit, data minimization, và worker isolation đều quan trọng.
---
39.39. Security không chỉ là chặn attacker
Bảo mật cũng là giảm hậu quả khi có lỗi.
Ví dụ:
- Least privilege.
- Backup.
- Audit log.
- Rate limit.
- Secret rotation.
- Network segmentation.
- Token ngắn hạn.
- Permission check ở backend.
- Không log secret.
Nếu một lớp hỏng, lớp khác vẫn giảm thiệt hại.
Đây gọi là defense in depth.
Không có lớp nào hoàn hảo.
Nhưng nhiều lớp đúng chỗ làm hệ thống khó bị phá hơn nhiều.
---
39.40. Checklist trước khi production
Trước khi đưa web app lên production, hãy hỏi:
- Toàn bộ app đã dùng HTTPS chưa?
- Cookie có Secure/HttpOnly/SameSite phù hợp chưa?
- CORS production có allowlist rõ chưa?
- Nếu dùng cookie auth, đã chống CSRF chưa?
- User input có được validate không?
- Output user content có escape/sanitize không?
- Query có dùng parameterized query/ORM đúng không?
- File upload có size/type/scan/sandbox/lifecycle không?
- Endpoint fetch URL có chống SSRF không?
- API có rate limit cho login/reset/upload/job không?
- Permission có kiểm tra ở backend không?
- Object-level permission đã test chưa?
- Tenant boundary có scope query không?
- Secret có nằm ngoài source code không?
- Token/secret có bị log không?
- Admin/debug endpoint có được bảo vệ không?
- Audit log có cho action nhạy cảm không?
- Dependency có được cập nhật/scan không?
- Error production có lộ stack trace không?
Checklist này không bảo đảm tuyệt đối.
Nhưng nó chặn rất nhiều lỗi thực tế.
---
39.41. Bảng chọn nhanh
| Rủi ro | Cách phòng chính | |---|---| | Nghe lén/sửa traffic | HTTPS everywhere | | Website khác gọi API bằng cookie user | SameSite + CSRF token + Origin check | | Script độc chạy trong trang | Escape/sanitize + tránh innerHTML + CSP | | SQL injection | Parameterized query/ORM đúng + validate dynamic fields | | SSRF | Allowlist URL + chặn IP nội bộ + timeout/size limit | | Upload file độc | Limit size/type + scan + sandbox + storage private | | Brute force/spam | Rate limit theo IP/user/tenant/key | | Abuse AI cost | Quota + rate limit + usage audit | | Hở dữ liệu object | Object-level permission | | Hở tenant | Tenant scoping backend | | Secret lộ | Secret manager + không log + rotation | | Hành động nhạy cảm không truy vết | Audit log | | Error lộ nội bộ | Generic error cho user, log nội bộ an toàn |
---
39.42. Tóm tắt bằng AI Judge
Với AI Judge, web security cần chú ý:
HTTPS:
bảo vệ login, submission, feedback, token.
CORS:
chỉ allow frontend/admin domain thật.
CSRF:
nếu dùng cookie session, chống CSRF cho action thay đổi dữ liệu.
XSS:
feedback, assignment title, comment, markdown phải escape/sanitize.
SQL injection:
search/filter/sort/report query phải parameterized/allowlist.
SSRF:
nếu import URL hoặc preview link, chặn URL nội bộ.
File upload:
submission zip, avatar, document phải limit/validate/sandbox.
Rate limit/quota:
login, submit, retry grading, AI job, export report.
Audit:
override score, export student data, admin access, permission change.
Điểm quan trọng:
AI Judge giữ dữ liệu học sinh, bài nộp, feedback, điểm, và có thể tiêu tiền qua AI API.
Vì vậy security không phải phần phụ.
---
39.43. Kết luận của chương
Web security thực dụng không bắt đầu từ việc học thuộc mọi lỗ hổng.
Nó bắt đầu từ vài nguyên tắc:
Không tin input.
Không tin frontend.
Không tin internal network tuyệt đối.
Không cấp quyền rộng hơn cần thiết.
Không lưu/lộ secret.
Không để hành động nhạy cảm không có log.
Không để tài nguyên bị gọi vô hạn.
HTTPS, CORS, CSRF, XSS, SQL injection, SSRF, file upload, rate limiting, audit log đều là những lớp bảo vệ quanh cùng một mục tiêu:
Dữ liệu đúng người, đúng quyền, đúng ngữ cảnh, và hệ thống chịu được lỗi/abuse.
Ở chương tiếp theo, ta sẽ bước sang phần vận hành, deploy và hạ tầng: production khác localhost như thế nào, vì sao "chạy được trên máy mình" chưa đủ, và những vấn đề thật sẽ xuất hiện khi có nhiều người dùng cùng lúc.