← Backend← Backend
BackendBackend19 Th7, 2026Jul 19, 202627 phút đọc22 min read

Bảo mật WebWeb Security

Thuộc bộ kiến thức Backend Roadmap.

Tổng quan

Bảo mật web không phải là một tính năng bạn gắn thêm vào lúc cuối; nó là thuộc tính của mọi quyết định bạn đưa ra khi xây dựng backend — cách bạn truy vấn database, cách bạn render output, cách bạn lưu secrets, cách bạn cấu hình headers, và cách bạn tin (hoặc từ chối tin) dữ liệu đi qua ranh giới hệ thống. Chỉ một câu query không parameterized hoặc một header bị thiếu cũng đủ biến một service vững chắc thành một vụ rò rỉ dữ liệu.

Bài viết này mang tính phòng thủ (defensive). Mục tiêu là nhận diện được hình dạng của từng loại lỗ hổng để phòng ngừagiảm thiểu — không phải để khai thác. Với mỗi rủi ro, chúng ta mô tả cách nó xảy ra rồi đến biện pháp kiểm soát cụ thể để vô hiệu hóa nó.

Chúng ta xây dựng mô hình tư duy theo từng lớp:

Authentication, session management, và password hashing được trình bày chi tiết trong Authentication & Authorization; các vấn đề riêng của API trong APIs; và cơ chế HTTP/TLS nền tảng trong Internet & Web Fundamentals. Bài này liên kết đến cả ba thay vì lặp lại.

Kiến thức nền tảng

Tư duy bảo mật

Hầu hết các quy tắc bảo mật cụ thể đều bắt nguồn từ bốn nguyên tắc. Nắm vững chúng, bạn có thể suy luận về những lỗ hổng mà bạn chưa từng thấy trong bất kỳ checklist nào.

Hai ý tưởng bổ trợ hoàn thiện bức tranh:

Bộ ba CIA và trust boundaries

Các biện pháp kiểm soát bảo mật tồn tại để giữ ba thuộc tính: Confidentiality (chỉ bên được phép mới đọc được dữ liệu), Integrity (dữ liệu không thể bị sửa đổi mà không bị phát hiện), và Availability (hệ thống vẫn dùng được). Mỗi lỗ hổng phá vỡ một hoặc nhiều thuộc tính này — một SQL injection dump bảng users phá Confidentiality; một CSRF đổi email phá Integrity; một endpoint không giới hạn bị dội bom phá Availability.

Một trust boundary là bất kỳ đường ranh nào nơi dữ liệu hoặc quyền điều khiển đi qua giữa các vùng tin cậy khác nhau — browser → server, server → database, service của bạn → third-party API. Lỗ hổng tập trung ở các ranh giới này vì đó là nơi các giả định (“client đã validate rồi”) bị vi phạm. Hãy vẽ ranh giới một cách rõ ràng và đặt một biện pháp kiểm soát ở mỗi điểm giao cắt.

Khái niệm chính

OWASP Top 10 (2021)

OWASP Top 10 là tài liệu nâng cao nhận thức được tham chiếu rộng rãi nhất về rủi ro của ứng dụng web. Hãy học nó như một checklist “mình đã nghĩ đến cái này chưa?”. Mỗi dòng dưới đây ghép rủi ro với biện pháp phòng thủ chính của nó.

#Rủi roLà gìCâu phòng thủ
A01Broken Access ControlNgười dùng hành động ngoài quyền được cấp (IDOR, thiếu kiểm tra, leo thang đặc quyền)Deny by default; enforce authorization ở server trên mọi request theo user đã xác thực
A02Cryptographic FailuresDữ liệu nhạy cảm lộ ra do crypto yếu/thiếu (plaintext, cipher yếu, không TLS)Mã hóa in transit (TLS) và at rest; dùng thuật toán mạnh, chuẩn; đừng tự viết crypto
A03InjectionInput không tin cậy bị diễn giải thành code/lệnh (SQL, NoSQL, OS, LDAP)Parameterized queries / prepared statements; validate và encode input
A04Insecure DesignThiếu hoặc sai sót các biện pháp bảo mật ở mức thiết kếThreat-model sớm; dùng secure design pattern và kiến trúc tham chiếu
A05Security MisconfigurationDefault creds, error chi tiết, cloud bucket mở, tính năng thừaCấu hình hardened, lặp lại được; least functionality; tắt defaults; tự động hóa
A06Vulnerable & Outdated ComponentsThư viện/framework/OS package có lỗ hổng đã biếtKiểm kê dependencies; vá kịp thời; quét (SCA) trong CI
A07Identification & Authentication FailuresXử lý login, session, hoặc credential yếuAuth mạnh, MFA, session management an toàn — xem ./07
A08Software & Data Integrity FailuresUpdate không xác thực, deserialization không an toàn, CI/CD bị xâm phạmXác minh chữ ký/integrity; bảo vệ pipeline; tránh deserialization không an toàn
A09Security Logging & Monitoring FailuresTấn công không bị phát hiện do thiếu log/alertLog security event; tập trung hóa, alert, và lưu trữ; đừng log secrets
A10Server-Side Request Forgery (SSRF)Server bị lừa gửi request đến đích ngoài ý muốnValidate/allow-list URL đi ra; chặn dải nội bộ và metadata endpoint

Injection (SQL / NoSQL / command)

Injection xảy ra khi input không tin cậy bị nối vào lệnh của một interpreter — một câu SQL, một shell command, một NoSQL filter — khiến input có thể thay đổi cấu trúc của lệnh thay vì chỉ được coi là dữ liệu thuần túy. Cách khắc phục luôn là cùng một ý tưởng: tách biệt code và data.

SQL injection — không an toàn (đừng bao giờ làm thế này):

# Nối chuỗi khiến input trở thành SQL. Input kiểu  ' OR '1'='1
# biến mệnh đề WHERE thành thứ luôn khớp.
username = request.args["username"]
query = "SELECT * FROM users WHERE username = '" + username + "'"
cursor.execute(query)   # DỄ BỊ TẤN CÔNG

SQL injection — an toàn (parameterized query / prepared statement):

# Driver gửi cấu trúc query và giá trị một cách riêng biệt.
# Giá trị không bao giờ bị parse thành SQL — nó luôn chỉ là data.
username = request.args["username"]
cursor.execute(
    "SELECT * FROM users WHERE username = %s",   # placeholder
    (username,),                                  # tham số được bind
)

Tương tự ở các hệ sinh thái khác:

// Node.js (pg) — parameterized
await client.query("SELECT * FROM users WHERE username = $1", [username]);
// Java (JDBC) — PreparedStatement
PreparedStatement ps = conn.prepareStatement(
    "SELECT * FROM users WHERE username = ?");
ps.setString(1, username);

Quy tắc phòng thủ injection:

Cross-Site Scripting (XSS)

XSS là injection vào browser: dữ liệu do kẻ tấn công kiểm soát lọt vào trang dưới dạng script thực thi được, chạy trong session của nạn nhân với cookie và quyền của họ. Có ba biến thể:

LoạiPayload nằm ở đâuVí dụ vector
StoredLưu ở server, phục vụ cho nhiều userComment độc lưu vào DB, hiển thị cho mọi người xem
ReflectedBật lại từ server theo requestPayload trong URL/search param được echo vào trang
DOM-basedKhông tới server; JS ở client ghi rainnerHTML = location.hash trong code front-end

Các biện pháp phòng thủ, theo lớp:

{# Jinja2 autoescape BẬT (mặc định): an toàn theo thiết kế #}
<p>{{ user_comment }}</p>            {# < > & được escape #}

{# NGUY HIỂM — chỉ dùng sau khi đã sanitize user_comment ở server #}
<div>{{ user_comment | safe }}</div>

Cross-Site Request Forgery (CSRF)

CSRF lợi dụng thói quen của browser là tự động đính kèm cookie vào mọi request đến domain của bạn. Nếu một user đang đăng nhập vào site của bạn và ghé thăm trang của kẻ tấn công, trang đó có thể âm thầm submit một form hoặc bắn một request đến app của bạn, và browser gửi kèm session cookie — nên hành động chạy dưới danh nghĩa nạn nhân. CSRF nhắm vào các request thay đổi trạng thái (POST/PUT/DELETE), không phải các thao tác đọc.

Các biện pháp phòng thủ:

Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/

Broken authentication & session management

Các luồng login yếu, session ID dễ đoán, token không bao giờ hết hạn, và credential trong URL cho phép kẻ tấn công trở thành người dùng khác. Vì đây là một chủ đề lớn, phần trình bày đầy đủ — password hashing (bcrypt/argon2), MFA, vòng đời session, các cạm bẫy JWT, OAuth/OIDC — nằm trong Authentication & Authorization. Các quy tắc một dòng: dùng một auth library đã được kiểm chứng, hash password bằng hàm adaptive chậm, xoay vòng và cho hết hạn session, và enforce MFA trên các tài khoản nhạy cảm.

Broken access control & IDOR

Access control quyết định một user đã xác thực được phép làm gì. Nó vỡ khi server tin tưởng client tự enforce giới hạn. Dạng kinh điển là IDOR (Insecure Direct Object Reference): một endpoint kiểu GET /api/invoices/1043 trả về hóa đơn có ID đó mà không kiểm tra nó thuộc về người gọi, nên đổi số thành 1044 sẽ lộ dữ liệu của người khác.

Các biện pháp phòng thủ:

Server-Side Request Forgery (SSRF)

SSRF xảy ra khi server của bạn fetch một URL do người dùng cung cấp (trực tiếp hoặc gián tiếp) — import ảnh, webhook, PDF renderer, link preview — và kẻ tấn công trỏ nó vào trong: đến http://169.254.169.254/ (cloud metadata, thường chứa credential), đến các admin panel nội bộ, hoặc đến các service localhost vốn tin tưởng lưu lượng local.

Các biện pháp phòng thủ:

Security misconfiguration

Nhóm phổ biến nhất trong thực tế: default admin password, bật directory listing, stack trace chi tiết trong production, các service thừa đang chạy, cloud storage quá lỏng (public S3 bucket), thiếu security header, và dev tooling bị lộ. Biện pháp phòng thủ: một cấu hình baseline hardened, được quản lý version; cùng một config trên các môi trường (trừ secrets); tắt mọi thứ bạn không cần; error page chung chung trong production; và tự động scan để phát hiện drift.

Sensitive data exposure / cryptographic failures

Dữ liệu rò rỉ qua lưu trữ plaintext, mã hóa yếu hoặc thiếu, secrets trong log hoặc source control, và API response quá rộng. Biện pháp phòng thủ:

CORS (Cross-Origin Resource Sharing)

Same-Origin Policy (SOP) của browser chặn JavaScript ở https://a.com đọc response từ https://b.com. CORS là cơ chế có kiểm soát, do server điều khiển để nới lỏng điều đó — nó cho phép API của bạn báo cho browser biết origin nào khác được phép đọc response của nó. CORS là một lớp enforcement ở browser; nó không bảo vệ server khỏi các client không phải browser (curl, các server), và không thay thế cho authentication hay authorization.

Cách hoạt động. Với các request “simple”, browser gửi request kèm header Origin và kiểm tra Access-Control-Allow-Origin của response trước khi lộ nó cho JS. Với các request có thể thay đổi trạng thái hoặc dùng custom header, browser trước tiên gửi một request preflight OPTIONS để xin phép:

# Preflight request (browser -> API của bạn)
OPTIONS /api/orders HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

# Preflight response (API của bạn -> browser)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 600

Cấu hình an toàn:

# Flask-CORS — allow-list rõ ràng, không phải "*"
CORS(app,
     origins=["https://app.example.com"],
     methods=["GET", "POST", "PUT", "DELETE"],
     allow_headers=["Content-Type", "Authorization"],
     supports_credentials=True)

Content-Security-Policy (CSP)

CSP là một HTTP response header báo cho browser biết những nguồn content nào (script, style, image, frame, connection) là đáng tin cậy. Lợi ích nổi bật của nó: là một tuyến phòng thủ thứ hai mạnh mẽ chống XSS — ngay cả khi kẻ tấn công inject được một <script>, một CSP tốt sẽ ngăn nó thực thi (không cho inline script, chỉ cho origin trong allow-list). Nó cũng giảm thiểu data exfiltration và clickjacking.

Một policy khởi đầu hợp lý:

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self';
  img-src 'self' data:;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';
  upgrade-insecure-requests;

Lưu ý:

Transport security: HTTPS / TLS và HSTS

Mọi lưu lượng nên được mã hóa bằng TLS (HTTPS), luôn luôn, ở mọi nơi — không chỉ trên trang login. Không có nó, bất kỳ ai trên đường đi của mạng cũng có thể đọc hoặc sửa đổi lưu lượng, đánh cắp session cookie, và inject content. Xem Internet & Web Fundamentals để hiểu cơ chế handshake.

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Triển khai cẩn thận: một khi browser thấy HSTS, nó sẽ từ chối HTTP thuần trong max-age giây; preload (gửi vào danh sách preload của browser) thực chất là vĩnh viễn, nên chỉ bật khi bạn chắc chắn mọi subdomain đều hỗ trợ HTTPS.

Security response headers

Một tập nhỏ các header giúp hardening mọi response. Gửi chúng toàn cục (ở reverse proxy hoặc app middleware).

HeaderMục đích
Strict-Transport-SecurityBuộc HTTPS cho các lần truy cập sau (HSTS)
Content-Security-PolicyGiới hạn nguồn content; giảm thiểu XSS/clickjacking
X-Content-Type-Options: nosniffNgăn browser MIME-sniff response thành kiểu ngoài ý muốn
X-Frame-Options: DENYNgăn framing (clickjacking); frame-ancestors là kế thừa của CSP
Referrer-Policy: no-referrer (hoặc strict-origin-when-cross-origin)Giới hạn lượng thông tin URL rò rỉ trong header Referer
Permissions-PolicyTắt các tính năng browser mạnh (camera, geolocation) bạn không dùng
Cache-Control: no-store (trên response nhạy cảm)Ngăn cache dữ liệu riêng tư
Set-Cookie: ... HttpOnly; Secure; SameSiteBảo vệ session cookie (xem CSRF/XSS)

Đừng để lộ banner phiên bản Server/X-Powered-By — chúng giúp kẻ tấn công fingerprint các phiên bản có lỗ hổng đã biết.

Application & server hardening

Hashing: integrity vs. password

Cả hai đều dùng hàm hash, nhưng yêu cầu lại ngược nhau, và nhầm lẫn chúng là một lỗ hổng thực sự.

Trường hợp dùngMục tiêuNên dùngKHÔNG dùng
Integrity / checksum (fingerprint file, cache key, khử trùng lặp, ETag)Nhanh, tất địnhSHA-256 (nhanh là tốt và mong muốn)
Message authentication (xác minh payload không bị sửa, dùng shared key)Nhanh + có keyHMAC-SHA256SHA thuần (không key)
Lưu trữ passwordChậm, có salt, memory-hardargon2id, bcrypt, scrypt (hoặc PBKDF2)MD5, SHA-1, SHA-256 (quá nhanh → dễ brute-force)

Điểm mấu chốt: với integrity bạn muốn một hash nhanh. Với password bạn muốn một hàm cố tình chậm, có salt, memory-hard để việc brute-force offline là bất khả thi — dùng một hash nhanh đa dụng (kể cả SHA-256) cho password là một sai lầm nghiêm trọng. MD5 và SHA-1 cũng đã bị phá cho cả integrity nhạy cảm về bảo mật (collision) và nên tránh cho chữ ký/certificate. Hướng dẫn đầy đủ về password hashing cùng tham số nằm trong Authentication & Authorization.

Best Practices

Security checklist

Lĩnh vựcKiểm tra
InputMọi input ngoài được validate ở server (allow-list)
InjectionParameterized query / prepared statement ở mọi nơi
XSSOutput được encode; autoescape bật; HTML người dùng được sanitize; CSP enforce
CSRFSameSite cookie + anti-CSRF token trên request thay đổi trạng thái
AuthLibrary đã kiểm chứng, MFA, session an toàn (xem ./07)
Access controlAuthz ở server trên mọi request; deny by default; kiểm tra sở hữu
SSRFURL đi ra được allow-list; chặn dải nội bộ & metadata
TransportTLS 1.2+ mọi nơi; redirect HTTP→HTTPS; HSTS
HeadersCSP, HSTS, nosniff, X-Frame-Options/frame-ancestors, Referrer-Policy
CORSAllow-list origin rõ ràng; không * + credential
SecretsNgoài source control; trong secrets manager; xoay vòng; scan rò rỉ
DependenciesĐược kiểm kê, pin, SCA-scan, vá kịp thời
CookiesĐặt HttpOnly, Secure, SameSite
Passwordsargon2id/bcrypt/scrypt có salt (không bao giờ MD5/SHA)
Rate limitingÁp cho các endpoint auth và tốn kém
ConfigBaseline hardened; tắt defaults; error production chung chung
LoggingLog security event; không secrets trong log; monitor/alert

Tài liệu tham khảo

Part of the Backend Roadmap knowledge base.

Overview

Web security is not a feature you bolt on at the end; it is a property of every decision you make while building a backend — how you query the database, how you render output, how you store secrets, how you configure headers, and how you trust (or refuse to trust) the data crossing your boundary. A single unparameterized query or one missing header can turn an otherwise solid service into a data breach.

This note is defensive. The goal is to recognize the shape of each class of vulnerability so you can prevent and mitigate it — not to exploit it. For every risk we describe the failure mode and then the concrete control that neutralizes it.

We build up the mental model in layers:

Authentication, session management, and password hashing are covered in depth in Authentication & Authorization; API-specific concerns in APIs; and the underlying HTTP/TLS mechanics in Internet & Web Fundamentals. This note cross-links to all three rather than repeating them.

Fundamentals

The security mindset

Most concrete security rules fall out of four principles. Internalize these and you can reason about vulnerabilities you have never seen a checklist item for.

Two supporting ideas complete the picture:

The CIA triad and trust boundaries

Security controls exist to preserve three properties: Confidentiality (only authorized parties can read the data), Integrity (data cannot be tampered with undetectably), and Availability (the system stays usable). Every vulnerability breaks one or more of these — an SQL injection that dumps the users table breaks confidentiality; a CSRF that changes an email breaks integrity; an unthrottled endpoint that gets hammered breaks availability.

A trust boundary is any line where data or control passes between zones of different trust — browser → server, server → database, your service → a third-party API. Vulnerabilities cluster at these boundaries because that’s where assumptions (“the client already validated this”) get violated. Draw the boundaries explicitly and put a control on every crossing.

Key Concepts

OWASP Top 10 (2021)

The OWASP Top 10 is the most widely referenced awareness document for web application risk. Learn it as a checklist of “have I thought about this?” Each row below pairs the risk with its primary defense.

#RiskWhat it isOne-line defense
A01Broken Access ControlUsers act outside their intended permissions (IDOR, missing checks, privilege escalation)Deny by default; enforce authorization server-side on every request against the authenticated user
A02Cryptographic FailuresSensitive data exposed via weak/absent crypto (plaintext, weak ciphers, no TLS)Encrypt in transit (TLS) and at rest; use strong, standard algorithms; don’t roll your own crypto
A03InjectionUntrusted input interpreted as code/commands (SQL, NoSQL, OS, LDAP)Parameterized queries / prepared statements; validate and encode input
A04Insecure DesignMissing or flawed security controls at the design levelThreat-model early; use secure design patterns and reference architectures
A05Security MisconfigurationDefault creds, verbose errors, open cloud buckets, unnecessary featuresHardened, repeatable config; least functionality; disable defaults; automate
A06Vulnerable & Outdated ComponentsKnown-vulnerable libraries/frameworks/OS packagesInventory dependencies; patch promptly; scan (SCA) in CI
A07Identification & Authentication FailuresWeak login, session, or credential handlingStrong auth, MFA, secure session management — see ./07
A08Software & Data Integrity FailuresUnverified updates, insecure deserialization, compromised CI/CDVerify signatures/integrity; secure the pipeline; avoid unsafe deserialization
A09Security Logging & Monitoring FailuresAttacks go undetected due to missing logs/alertsLog security events; centralize, alert, and retain; don’t log secrets
A10Server-Side Request Forgery (SSRF)Server is tricked into making requests to unintended destinationsValidate/allow-list outbound URLs; block internal ranges and metadata endpoints

Injection (SQL / NoSQL / command)

Injection happens whenever untrusted input is concatenated into an interpreter’s instruction — a SQL query, a shell command, a NoSQL filter — so the input can change the structure of the command instead of being treated as pure data. The fix is always the same idea: keep code and data separate.

SQL injection — unsafe (never do this):

# String concatenation lets input become SQL. Input like  ' OR '1'='1
# turns the WHERE clause into something that always matches.
username = request.args["username"]
query = "SELECT * FROM users WHERE username = '" + username + "'"
cursor.execute(query)   # VULNERABLE

SQL injection — safe (parameterized query / prepared statement):

# The driver sends the query structure and the value separately.
# The value can never be parsed as SQL — it is always just data.
username = request.args["username"]
cursor.execute(
    "SELECT * FROM users WHERE username = %s",   # placeholder
    (username,),                                  # bound parameter
)

The same in other ecosystems:

// Node.js (pg) — parameterized
await client.query("SELECT * FROM users WHERE username = $1", [username]);
// Java (JDBC) — PreparedStatement
PreparedStatement ps = conn.prepareStatement(
    "SELECT * FROM users WHERE username = ?");
ps.setString(1, username);

Rules for injection defense:

Cross-Site Scripting (XSS)

XSS is injection into the browser: attacker-controlled data ends up in a page as executable script, running in the victim’s session with their cookies and permissions. Three variants:

TypeWhere the payload livesExample vector
StoredPersisted server-side, served to many usersMalicious comment saved to DB, shown to every viewer
ReflectedBounced off the server from the requestPayload in a URL/search param echoed into the page
DOM-basedNever reaches the server; client JS writes itinnerHTML = location.hash in front-end code

Defenses, layered:

{# Jinja2 autoescape ON (the default): safe by construction #}
<p>{{ user_comment }}</p>            {# < > & are escaped #}

{# DANGEROUS — only after sanitizing user_comment server-side #}
<div>{{ user_comment | safe }}</div>

Cross-Site Request Forgery (CSRF)

CSRF abuses the browser’s habit of automatically attaching cookies to any request to your domain. If a user is logged in to your site and visits an attacker’s page, that page can silently submit a form or fire a request to your app, and the browser sends the session cookie along — so the action runs as the victim. CSRF targets state-changing requests (POST/PUT/DELETE), not reads.

Defenses:

Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/

Broken authentication & session management

Weak login flows, guessable session IDs, tokens that never expire, and credentials in URLs let attackers become other users. Because this is a large topic, the full treatment — password hashing (bcrypt/argon2), MFA, session lifecycle, JWT pitfalls, OAuth/OIDC — lives in Authentication & Authorization. The one-line rules: use a vetted auth library, hash passwords with a slow adaptive function, rotate and expire sessions, and enforce MFA on sensitive accounts.

Broken access control & IDOR

Access control decides what an authenticated user is allowed to do. It breaks when the server trusts the client to enforce limits. The classic form is IDOR (Insecure Direct Object Reference): an endpoint like GET /api/invoices/1043 returns the invoice with that ID without checking it belongs to the caller, so changing the number to 1044 reveals someone else’s data.

Defenses:

Server-Side Request Forgery (SSRF)

SSRF happens when your server fetches a URL supplied (directly or indirectly) by the user — image imports, webhooks, PDF renderers, link previews — and the attacker points it inward: at http://169.254.169.254/ (cloud metadata, often holding credentials), at internal admin panels, or at localhost services that trust local traffic.

Defenses:

Security misconfiguration

The most common category in practice: default admin passwords, directory listing enabled, verbose stack traces in production, unnecessary services running, overly permissive cloud storage (public S3 buckets), missing security headers, and dev tooling exposed. Defenses: a hardened, version-controlled baseline configuration; the same config across environments (minus secrets); turning off everything you don’t need; generic error pages in production; and automated scanning for drift.

Sensitive data exposure / cryptographic failures

Data leaks through plaintext storage, weak or absent encryption, secrets in logs or source control, and over-broad API responses. Defenses:

CORS (Cross-Origin Resource Sharing)

The browser’s Same-Origin Policy (SOP) blocks JavaScript on https://a.com from reading responses from https://b.com. CORS is the controlled, server-driven mechanism to relax that — it lets your API tell the browser which other origins are allowed to read its responses. CORS is a browser enforcement layer; it does not protect your server from non-browser clients (curl, servers), and it is not a substitute for authentication or authorization.

How it works. For “simple” requests the browser sends the request with an Origin header and checks the response’s Access-Control-Allow-Origin before revealing it to JS. For requests that can change state or use custom headers, the browser first sends a preflight OPTIONS request asking permission:

# Preflight request (browser -> your API)
OPTIONS /api/orders HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

# Preflight response (your API -> browser)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 600

Configure it safely:

# Flask-CORS — explicit allow-list, not "*"
CORS(app,
     origins=["https://app.example.com"],
     methods=["GET", "POST", "PUT", "DELETE"],
     allow_headers=["Content-Type", "Authorization"],
     supports_credentials=True)

Content-Security-Policy (CSP)

CSP is an HTTP response header that tells the browser which sources of content (scripts, styles, images, frames, connections) are trustworthy. Its headline benefit: it is a strong second line of defense against XSS — even if an attacker injects a <script>, a good CSP prevents it from executing (no inline scripts, only allow-listed origins). It also mitigates data exfiltration and clickjacking.

A reasonable starting policy:

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self';
  img-src 'self' data:;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';
  upgrade-insecure-requests;

Notes:

Transport security: HTTPS / TLS and HSTS

All traffic should be encrypted with TLS (HTTPS), always, everywhere — not just on the login page. Without it, anyone on the network path can read or modify traffic, steal session cookies, and inject content. See Internet & Web Fundamentals for the handshake mechanics.

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Roll out carefully: once a browser sees HSTS it will refuse plain HTTP for max-age seconds; preload (submitting to the browser preload list) is effectively permanent, so only enable it when you are certain every subdomain supports HTTPS.

Security response headers

A small set of headers hardens every response. Send them globally (at the reverse proxy or app middleware).

HeaderPurpose
Strict-Transport-SecurityForce HTTPS for future visits (HSTS)
Content-Security-PolicyRestrict content sources; mitigate XSS/clickjacking
X-Content-Type-Options: nosniffStop the browser from MIME-sniffing responses into an unintended type
X-Frame-Options: DENYPrevent framing (clickjacking); frame-ancestors is the CSP successor
Referrer-Policy: no-referrer (or strict-origin-when-cross-origin)Limit how much URL info leaks in the Referer header
Permissions-PolicyDisable powerful browser features (camera, geolocation) you don’t use
Cache-Control: no-store (on sensitive responses)Prevent caching of private data
Set-Cookie: ... HttpOnly; Secure; SameSiteProtect session cookies (see CSRF/XSS)

Do not expose Server/X-Powered-By version banners — they help attackers fingerprint known-vulnerable versions.

Application & server hardening

Hashing: integrity vs. passwords

Both use hash functions, but the requirements are opposite, and mixing them up is a real vulnerability.

Use caseGoalUseDo NOT use
Integrity / checksums (file fingerprints, cache keys, deduplication, ETags)Fast, deterministicSHA-256 (fast is fine and desirable)
Message authentication (verify a payload wasn’t tampered with, using a shared key)Fast + keyedHMAC-SHA256plain SHA (no key)
Password storageSlow, salted, memory-hardargon2id, bcrypt, scrypt (or PBKDF2)MD5, SHA-1, SHA-256 (far too fast → brute-forceable)

The key insight: for integrity you want a fast hash. For passwords you want a deliberately slow, salted, memory-hard function so that offline brute-forcing is infeasible — using a fast general-purpose hash (even SHA-256) for passwords is a serious mistake. MD5 and SHA-1 are broken for security-sensitive integrity too (collisions) and should be avoided for signatures/certificates. Full password-hashing guidance and parameters are in Authentication & Authorization.

Best Practices

Security checklist

AreaCheck
InputAll external input validated server-side (allow-list)
InjectionParameterized queries / prepared statements everywhere
XSSOutput encoded; autoescape on; user HTML sanitized; CSP enforced
CSRFSameSite cookies + anti-CSRF tokens on state-changing requests
AuthVetted library, MFA, secure sessions (see ./07)
Access controlServer-side authz on every request; deny by default; ownership checked
SSRFOutbound URLs allow-listed; internal ranges & metadata blocked
TransportTLS 1.2+ everywhere; HTTP→HTTPS redirect; HSTS
HeadersCSP, HSTS, nosniff, X-Frame-Options/frame-ancestors, Referrer-Policy
CORSExplicit origin allow-list; no * + credentials
SecretsOut of source control; in a secrets manager; rotated; leak-scanned
DependenciesInventoried, pinned, SCA-scanned, patched promptly
CookiesHttpOnly, Secure, SameSite set
Passwordsargon2id/bcrypt/scrypt with salt (never MD5/SHA)
Rate limitingApplied to auth and expensive endpoints
ConfigHardened baseline; defaults off; generic prod errors
LoggingSecurity events logged; no secrets in logs; monitored/alerted

References