CORS & CSRF

- Published on
- /10 mins read/
Một trong những ngộ nhận tai hại nhất của các kỹ sư phần mềm là nghĩ rằng CORS (Cross-Origin Resource Sharing) là một bức tường lửa (Firewall) bảo vệ Server khỏi các cuộc tấn công từ bên ngoài. Thực tế hoàn toàn ngược lại: CORS là cơ chế do Trình duyệt (Browser) thực thi để NỚI LỎNG chính sách Same-Origin Policy (SOP). Các công cụ như cURL, Postman, Mobile Apps hay Script tấn công của Hacker hoàn toàn phớt lờ CORS. Coi CORS là công cụ xác thực (Authentication) cho API là một sai lầm chết người về mặt an ninh thông tin.
Trong kiến trúc hiện đại, khi Frontend (Single Page Applications - React/Vue/Next.js) được deploy trên một tên miền (https://app.company.com) và Backend REST/GraphQL APIs nằm trên một tên miền hoặc cổng khác (https://api.company.com), lỗi No 'Access-Control-Allow-Origin' header is present on the requested resource là rào cản đầu tiên mọi lập trình viên đều gặp phải.
Tuy nhiên, thay vì hiểu đúng bản chất, nhiều đội ngũ chọn giải pháp nguy hiểm: Cấu hình Access-Control-Allow-Origin: * hoặc phản chiếu động (Reflect) bất kỳ Origin nào gửi lên. Bài viết này phân tích cơ chế bảo mật trình duyệt từ gốc rễ và cung cấp Bản thiết kế Hardening CORS cấp doanh nghiệp tại tầng API Gateway.
# nền tảng: same-origin policy (sop) & nghịch lý trình duyệt
Mọi trình duyệt hiện đại (Chrome, Safari, Firefox) đều bị chi phối bởi quy tắc an ninh nền tảng nhất: Same-Origin Policy (SOP) được phát minh bởi Netscape vào năm 1995.
Hai URL được coi là Cùng Nguồn (Same-Origin) khi và chỉ khi cả 3 yếu tố sau hoàn toàn trùng khớp 100%:
- Giao thức (Protocol/Scheme):
httpvshttps→ Khác nguồn. - Tên miền máy chủ (Host/Domain):
company.comvsapi.company.com→ Khác nguồn. - Cổng mạng (Port):
localhost:3000vslocalhost:8080→ Khác nguồn.
# tại sao sop lại quan trọng?
Nếu không có SOP:
- Bạn đăng nhập vào trang web ngân hàng
my-bank.com, ngân hàng lưu session cookie vào trình duyệt. - Bạn mở một tab khác vào trang web độc hại
evil-site.com. - Đoạn mã JavaScript trên
evil-site.comcó thể tự do gửi Ajax request sangmy-bank.com/api/transfer-money, trình duyệt tự động đính kèm cookie đăng nhập của bạn, và tiền của bạn biến mất!
CORS sinh ra để giải quyết nhu cầu chính đáng: Cho phép my-bank-frontend.com được phép truy cập hợp pháp vào tài nguyên của my-bank-api.com dưới sự đồng thuận minh bạch của máy chủ.
# giải mã yêu cầu thăm dò: preflight request (HTTP options)
Không phải mọi request cross-origin đều gửi thẳng tới backend. Trình duyệt chia các request thành 2 nhóm: Simple Requests và Preflighted Requests.
# điều kiện kích hoạt preflight options
Request sẽ bị ép phải chạy Preflight nếu vi phạm bất kỳ điều kiện nào sau đây:
- Sử dụng các HTTP Method khác ngoài:
GET,HEAD,POST. - Sử dụng
Content-Typekhác ngoài:application/x-www-form-urlencoded,multipart/form-data,text/plain. (Lưu ý: Mọi request dùngapplication/jsonđều BẮT BUỘC phải Preflight!) - Có chứa bất kỳ Custom Headers nào (ví dụ:
Authorization,X-Trace-Id,X-Tenant-Id).
# chi phí ẩn về hiệu năng: double round-trip time (2x rtt)
Mỗi lần frontend gọi một API POST/PUT JSON, trình duyệt phải thực hiện 2 lần bắt tay mạng liên tiếp:
OPTIONSround-trip (~50ms - 200ms).POST/PUTpayload round-trip (~50ms - 200ms). → Làm tăng gấp đôi P99 Latency của các tương tác người dùng!
Tối ưu hóa của Architect: Luôn trả về header
Access-Control-Max-Age: 86400(Cache kết quả Preflight trong 24 giờ). Trình duyệt sẽ lưu lại quyền truy cập và bỏ qua bước gửiOPTIONScho toàn bộ các request tiếp theo tới cùng endpoint.
# các lỗ hổng bảo mật nguy hiểm khi cấu hình sai CORS
# cạm bẫy wildcard * đi kèm credentials
Rất nhiều lập trình viên thử cấu hình:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: trueTrình duyệt sẽ chặn đứng cấu hình này ngay lập tức! Theo tiêu chuẩn bảo mật W3C/WHATWG, nếu máy chủ cho phép gửi thông tin định danh nhạy cảm (credentials: true — cookies, HTTP auth), thì Access-Control-Allow-Origin bắt buộc phải là một domain cụ thể, tuyệt đối không được dùng dấu sao *.
# lỗ hổng dynamic origin reflection (phản xạ mù quáng)
Để "lách" quy tắc trên, nhiều backend developer viết đoạn code tai hại:
// LỖ HỔNG AN NINH NGHIÊM TRỌNG:
String origin = request.getHeader("Origin");
response.setHeader("Access-Control-Allow-Origin", origin); // Phản chiếu bất kỳ ai gửi lên!
response.setHeader("Access-Control-Allow-Credentials", "true");- Kịch bản khai thác: Hacker tạo trang
https://attacker.com. Khi người dùng truy cập trang này, script của hacker gửi request sang API của bạn vớiOrigin: https://attacker.com. - Server của bạn ngây thơ đọc header và trả về
Access-Control-Allow-Origin: https://attacker.comkèmcredentials: true. - Trình duyệt cho phép JavaScript của hacker đọc toàn bộ response nhạy cảm (thông tin số dư tài khoản, email, token)!
# phân biệt rạch ròi: CORS vs CSRF
| Tiêu chuẩn | CORS (Cross-Origin Resource Sharing) | CSRF (Cross-Site Request Forgery) |
|---|---|---|
| Bản chất | Cơ chế cho phép Frontend ĐỌC KẾT QUẢ (Read Access) từ domain khác | Kẻ tấn công lợi dụng cookie để THỰC THI HÀNH ĐỘNG (Write/State Mutation) |
| Hành vi trình duyệt | Vẫn gửi request lên server, nhưng chặn không cho JS đọc response nếu thiếu header CORS | Gửi request và server thực thi bình thường! (Hacker không cần đọc kết quả, chỉ cần tiền bị trừ) |
| Biện pháp phòng thủ | Cấu hình đúng Whitelist Origin | Sử dụng SameSite=Strict/Lax Cookies hoặc Anti-CSRF Tokens |
# kiến trúc tập trung hóa: CORS hardening tại API gateway
Trong kiến trúc Microservices, việc phân tán cấu hình CORS bằng các annotation @CrossOrigin rải rác trên từng Controller của từng service là một thảm họa quản trị: Dễ bỏ sót, khó audit và không thể cập nhật tập trung.
Chuẩn mực Enterprise: Chặn và xử lý 100% Preflight và CORS tại tầng API Gateway (Spring Cloud Gateway / Envoy / NGINX) trước khi request chạm tới các microservices nghiệp vụ bên trong.
# cấu hình Spring cloud gateway chuẩn doanh nghiệp
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
# Whitelist nghiêm ngặt các domain frontend tin cậy
allowed-origin-patterns:
- 'https://tungdadev.com'
- 'https://*.tungdadev.com'
- 'http://localhost:[3000,5173]' # Chỉ mở cho local dev
allowed-methods:
- GET
- POST
- PUT
- DELETE
- PATCH
- OPTIONS
allowed-headers:
- Authorization
- Content-Type
- X-Requested-With
- X-Correlation-Id
exposed-headers:
- Content-Disposition
- X-Total-Count
allow-credentials: true
# Cache preflight trong 24 giờ để giảm thiểu latency cho người dùng
max-age: 86400# xử lý tại tầng Spring security 6+ trong service đơn lẻ
Nếu không có Gateway, cấu hình chặt chẽ trong SecurityFilterChain:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
// Bật CSRF protection nếu dùng Session Cookies; tắt nếu dùng stateless JWT Authorization header
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // Luôn cho phép preflight đi qua
.anyRequest().authenticated()
)
.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(List.of("https://*.tungdadev.com", "http://localhost:3000"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type", "X-Requested-With"));
config.setAllowCredentials(true);
config.setMaxAge(Duration.ofHours(24));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}# bảng kiểm tra an ninh vận hành (security architect's checklist)
- 1. Không Dùng Wildcard Với Credentials: Đảm bảo không tồn tại đồng thời
Access-Control-Allow-Origin: *vàAccess-Control-Allow-Credentials: true. - 2. Không Phản Chiếu Mù Quáng (No Blind Reflection): Mã nguồn tuyệt đối không lấy trực tiếp giá trị header
request.getHeader("Origin")để gán vàoAllow-Originmà không qua whitelist kiểm tra. - 3. Cấu Hình Preflight Cache (
max-age): Thiết lậpAccess-Control-Max-Agetối thiểu 3.600 giây (khuyến nghị 86.400 giây) để loại bỏ gánh nặng latency của các lệnh OPTIONS lặp lại. - 4. Chặn Preflight Tại Gateway: Đảm bảo các request
OPTIONSđược API Gateway phản hồi ngay lập tức (204 No Content), không được chuyển tiếp sâu vào các microservices nội bộ làm tốn tài nguyên worker threads. - 5. Phòng Thủ CSRF Toàn Diện: Nếu ứng dụng dùng Cookies để xác thực, phải kích hoạt cờ
SameSite=LaxhoặcSameSite=Stricttrên toàn bộ cookies phiên, kết hợp kiểm traOrigin/Refererheaders ở các API làm biến đổi dữ liệu.
# lời kết
Bảo mật trình duyệt là phòng tuyến đầu tiên bảo vệ người dùng và tài nguyên doanh nghiệp trước các mối đe dọa không gian mạng. Hiểu đúng Same-Origin Policy, nắm vững cơ chế Preflight OPTIONS và thiết lập một cấu hình CORS Whitelist nghiêm ngặt tại API Gateway chính là nền tảng cốt lõi để xây dựng những hệ thống web hiện đại: vừa an toàn trước các cuộc tấn công đánh cắp dữ liệu, vừa mang lại trải nghiệm tương tác mượt mà và tối ưu nhất cho người dùng cuối.
Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè & đồng nghiệp nhé!
Happy coding 😎 👍🏻 🚀 🔥.
On this page
- # nền tảng: same-origin policy (sop) & nghịch lý trình duyệt
- # tại sao sop lại quan trọng?
- # giải mã yêu cầu thăm dò: preflight request (HTTP options)
- # điều kiện kích hoạt preflight options
- # chi phí ẩn về hiệu năng: double round-trip time (2x rtt)
- # các lỗ hổng bảo mật nguy hiểm khi cấu hình sai CORS
- # cạm bẫy wildcard * đi kèm credentials
- # lỗ hổng dynamic origin reflection (phản xạ mù quáng)
- # phân biệt rạch ròi: CORS vs CSRF
- # kiến trúc tập trung hóa: CORS hardening tại API gateway
- # cấu hình Spring cloud gateway chuẩn doanh nghiệp
- # xử lý tại tầng Spring security 6+ trong service đơn lẻ
- # bảng kiểm tra an ninh vận hành (security architect's checklist)
- # lời kết