25Advanced

Modern Security

Modern Security โ€” Instruction 25

Coverage

Trojan Source, Unicode attacks, HTTP/2 attacks, Sec-Fetch, postMessage, Reverse Tabnapping Session puzzling, Web Cache Poisoning, iFrame security, Autocomplete


Trojan Source (CVE-2021-42574)

1. Unicode Bidirectional Characters in Code

// ๐Ÿ”ด CRITICAL โ€” Invisible characters that make code look different to humans vs compiler
// What you see:
if (user.role == "admin") { grantAccess() }
// What compiler executes:
if (user.role == "adminโ€ฎ โฆ// Check if adminโฉ โฆ") {  // always true!

// These Unicode characters (U+202E, U+2066-U+2069) are INVISIBLE in most editors

// ๐ŸŸข Scan for dangerous Unicode in all source files:

Run:

grep -rP "[\x{202A}-\x{202E}\x{2066}-\x{2069}\x{200F}\x{200E}]" --include="*.{js,ts,py,php,go,rb,java}" .

Flag ANY file containing these characters as CRITICAL.


Unicode Normalization Attacks

2. Normalize Before Validation

// ๐Ÿ”ด "๏ฝdmin" (full-width chars) โ‰  "admin" (ASCII) to validator
// but after Unicode normalization they become identical

// Attacker registers: "๏ฝdmin@gmail.com" (full-width 'a')
// After normalization: "admin@gmail.com"
// โ†’ Account takeover if admin account exists!

// ๐ŸŸข Always normalize BEFORE validation and comparison
const normalizedEmail = email.normalize('NFKC').toLowerCase().trim()
const normalizedUsername = username.normalize('NFKC').toLowerCase().trim()

// Apply to: usernames, emails, paths, URLs, any string comparison

Reverse Tabnapping

3. target="_blank" Without noopener

<!-- ๐Ÿ”ด The new page can access window.opener and redirect parent! -->
<a href="https://external.com" target="_blank">Click here</a>

<!-- ๐ŸŸข Always add rel="noopener noreferrer" -->
<a href="https://external.com" target="_blank" rel="noopener noreferrer">Click here</a>

<!-- CSP can also help: -->
<!-- Scan all HTML files and templates for target="_blank" without rel="noopener" -->

postMessage Security

4. Always Validate Origin

// ๐Ÿ”ด Accepts messages from ANY origin
window.addEventListener('message', (event) => {
  doSomething(event.data)  // no origin check!
})

// ๐Ÿ”ด Sending to wildcard
window.parent.postMessage(sensitiveData, '*')  // goes to any origin!

// ๐ŸŸข Strict origin validation
const TRUSTED_ORIGINS = ['https://app.yourdomain.com', 'https://admin.yourdomain.com']

window.addEventListener('message', (event) => {
  if (!TRUSTED_ORIGINS.includes(event.origin)) {
    console.warn('Rejected message from:', event.origin)
    return
  }
  // Validate message structure
  if (!event.data?.type || !VALID_MESSAGE_TYPES.includes(event.data.type)) return
  doSomething(event.data)
})

// ๐ŸŸข Always specify exact target origin when sending
window.parent.postMessage(data, 'https://parent.yourdomain.com')

HTTP/2 Attacks

5. RST Flood (CVE-2023-44487) โ€” Rapid Reset Attack

// ๐Ÿ”ด Node.js < 18.19.1 / 20.x < 20.8.1 vulnerable to HTTP/2 RST flood DDoS
// Check: node --version

// ๐ŸŸข Ensure Node.js >= 20.8.1 or >= 18.19.1
// All current LTS versions are patched

// Also mitigated by: Cloudflare, Nginx 1.25.3+, reverse proxy in front of Node

6. HPACK Bomb

// Highly compressed HTTP/2 headers that expand to huge size on decompression
// Handled by HTTP/2 server implementation โ€” ensure server is updated
// Nginx, Node.js, Caddy all have protections in recent versions

Web Cache Poisoning

7. Unkeyed Headers

// ๐Ÿ”ด Response cached based on URL, but content varies by unkeyed header
// Attacker sends: X-Forwarded-Host: evil.com
// Response cached with evil.com in content โ†’ served to other users!

// ๐ŸŸข Configure cache to key on all relevant headers
// Vercel/CDN: ensure Vary header is set for all varying dimensions
res.setHeader('Vary', 'Accept-Encoding, Accept-Language')
// Never: cache different content for same URL without Vary header

8. Fat GET Request Poisoning

// ๐Ÿ”ด GET request with body that affects response
// GET /api/products body: {"category": "poison"}
// If response is cached โ†’ all users get poisoned response

// ๐ŸŸข GET requests must never use body for response logic
// Use query parameters instead (they're part of the cache key)

iFrame Security

9. Sandbox External iFrames

<!-- ๐Ÿ”ด Unsandboxed third-party iframe has full capabilities -->
<iframe src="https://third-party.com/widget"></iframe>

<!-- ๐ŸŸข Sandbox with minimal permissions -->
<iframe
  src="https://third-party.com/widget"
  sandbox="allow-scripts allow-same-origin"
  allow="camera 'none'; microphone 'none'; geolocation 'none'; payment 'none'"
  referrerpolicy="no-referrer"
  loading="lazy">
</iframe>

<!-- Only add sandbox permissions actually needed:
  allow-scripts: JS execution
  allow-same-origin: access to own origin
  allow-forms: form submission
  allow-popups: opening new windows
-->

Session Puzzling

10. Single Semantics per Session Variable

// ๐Ÿ”ด Session variable used for different purposes
req.session.userId = user.id         // after login
req.session.userId = resetToken      // during password reset
// If attacker completes password reset โ†’ userId is set โ†’ might be logged in!

// ๐ŸŸข Use separate session variables for each purpose
req.session.authenticatedUserId = user.id
req.session.passwordResetToken = token
req.session.passwordResetUserId = user.id

HTTP Method Override

11. Disable Method Override in Production

// ๐Ÿ”ด X-HTTP-Method-Override header changes DELETE to GET or vice versa
// Attacker bypasses WAF rules that block DELETE

// ๐ŸŸข Disable method override middleware
// Don't use: methodOverride() middleware in production
// Express: don't include 'method-override' package unless required

Autocomplete on Sensitive Fields

12. Autocomplete Attributes

<!-- ๐Ÿ”ด Browser autofill may pre-fill sensitive fields unexpectedly -->
<input type="text" name="creditCard">

<!-- ๐ŸŸข Use specific autocomplete values -->
<input type="text" name="name" autocomplete="name">
<input type="email" name="email" autocomplete="email">
<input type="password" name="currentPassword" autocomplete="current-password">
<input type="password" name="newPassword" autocomplete="new-password">

<!-- ๐Ÿ”ด For OTP/2FA: turn off to prevent autofill -->
<input type="text" name="otp" autocomplete="off" inputmode="numeric">

<!-- For payment: use specific autocomplete tokens -->
<input type="text" name="cc-number" autocomplete="cc-number">

Content-Type Validation

13. Validate Incoming Content-Type

// ๐Ÿ”ด Server parses unexpected content types
// Attacker sends XML instead of JSON โ†’ may trigger different parser

// ๐ŸŸข Validate Content-Type for all API endpoints
app.use('/api', (req, res, next) => {
  if (req.method !== 'GET' && req.method !== 'DELETE') {
    if (!req.is('application/json')) {
      return res.status(415).json({ error: 'Unsupported Media Type. Expected application/json' })
    }
  }
  next()
})

// For file upload endpoints: accept only expected MIME types