Day 52: Web & Mobile Security: OWASP Top 10, XSS, CSRF, & SSL Pinning
Recognize and defend against the vulnerability classes most likely to show up in both a real security review and an interview.
Study
Concepts
XSS: attacker-controlled script execution in your page's context
Cross-Site Scripting happens when untrusted input is rendered as executable HTML/JS in your page. React's default text rendering (`{userInput}`) auto-escapes content, which is why React apps are relatively resistant BY DEFAULT — the real danger is `dangerouslySetInnerHTML` with unsanitized input, or building raw HTML strings on a server that get injected client-side. If you must render user-provided HTML (a rich-text comment), sanitize it through a library like DOMPurify immediately before rendering, never trust it as-is.
The consequence of a successful XSS is severe specifically because it runs WITH the user's full page privileges — it can read localStorage (including any tokens stored there, tying directly back to Day 51's storage guidance), make authenticated requests as the user, or exfiltrate form input in real time.
CSRF: tricking the browser into making a request it trusts
Cross-Site Request Forgery exploits the fact that browsers automatically attach cookies (including auth cookies) to ANY request to a domain, even one triggered by a malicious page the user happens to have open in another tab — a hidden auto-submitting form on `evil.com` can POST to `yourbank.com/transfer` and the browser will happily attach the user's valid session cookie. Defenses: `SameSite=Lax` or `Strict` cookies (the browser withholds the cookie on cross-site requests by default in modern browsers), CSRF tokens (a per-session random value the server checks on state-changing requests, unreadable/unforgeable by a third-party page), and requiring custom headers for state-changing requests (simple cross-site forms cannot set custom headers).
Note the asymmetry with token-based (Authorization header, non-cookie) auth: if your access token lives in memory and is sent via an `Authorization: Bearer` header rather than a cookie, CSRF does not apply to that request AT ALL, because a third-party page cannot set custom headers on a cross-origin form submission — this is one argument in favor of header-based auth for the access token even while using a cookie for the refresh token.
Mobile-specific: SSL/Certificate Pinning
Even over HTTPS, a device with a maliciously (or corporately) installed root certificate can man-in-the-middle traffic by presenting a certificate that the device trusts but that was not issued by the real server's CA. SSL Pinning hard-codes (pins) the expected certificate or public key inside the app itself, so the app rejects a connection even if the OS-level certificate store would otherwise trust the intercepting certificate — a meaningful extra layer for apps handling sensitive data (banking, health), at the operational cost of needing an app update if the pinned certificate/key ever needs to rotate.
See It
Visualizations
Visualization
XSS vs CSRF
| XSS | CSRF | |
|---|---|---|
| What runs | Attacker JS, inside YOUR page | A legitimate request, from a DIFFERENT page |
| Root cause | Unsanitized input rendered as HTML/JS | Browser auto-attaches cookies to any request |
| Primary defense | Escape by default, sanitize any raw HTML (DOMPurify) | SameSite cookies, CSRF tokens, custom-header requirement |
| React's default posture | Safer by default (auto-escaping) | Depends entirely on cookie config, not framework-specific |
Build It
Code Examples
Sanitizing user-provided HTML before rendering
import DOMPurify from 'dompurify';
function CommentBody({ rawHtml }) {
// NEVER: dangerouslySetInnerHTML={{ __html: rawHtml }} directly
const clean = DOMPurify.sanitize(rawHtml, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href'],
});
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}CSRF-hardened cookie config + custom header requirement
// Server: set the refresh-token cookie defensively
res.cookie('refresh_token', token, {
httpOnly: true, // JS cannot read it (mitigates XSS exfiltration of this cookie)
secure: true, // HTTPS only
sameSite: 'strict', // never sent on cross-site requests at all
});
// Also require a custom header on state-changing requests — a
// cross-site <form> submission cannot set custom headers, so this
// alone blocks the classic CSRF attack even without SameSite.
app.post('/api/transfer', requireHeader('X-Requested-With', 'XMLHttpRequest'), handleTransfer);Remember
Key Takeaways
- React auto-escapes rendered text by default — the real XSS risk is dangerouslySetInnerHTML or server-built raw HTML with unsanitized input.
- Sanitize any user-provided HTML with a library like DOMPurify immediately before rendering it — never trust it as-is.
- CSRF exploits automatic cookie attachment on cross-site requests — SameSite cookies + CSRF tokens + custom-header checks are the standard defenses.
- Header-based (Authorization: Bearer) auth is naturally immune to CSRF, since a cross-site form cannot set custom headers.
- SSL Pinning defends mobile apps against MITM even when the device's OS-level trust store has been compromised — at the cost of needing app updates to rotate pins.
Do It
Practice
- 1Deliberately render unsanitized user input via dangerouslySetInnerHTML in a sandboxed test app and confirm a <script> payload executes — then fix it with DOMPurify.
- 2Configure SameSite=strict on a test cookie and confirm (via a cross-origin test form) that it is withheld on the cross-site request.
- 3Read the OWASP Top 10 list and write one sentence per item connecting it to a concrete frontend/mobile scenario you could encounter.