11Advanced

Advanced Attacks

Advanced Attacks — Instruction 11

Coverage

SSRF, SSTI, Prototype Pollution, Zip Slip, Open Redirect, XXE, Mass Assignment, IDOR CWE-918, CWE-94, CWE-1321, CWE-22, CWE-601


SSRF (Server-Side Request Forgery)

1. Validate URLs Before Fetching

// 🔴 CRITICAL — Server fetches any URL the user provides
app.post('/fetch', async (req, res) => {
  const data = await fetch(req.body.url)  // SSRF!
  // Attacker: url = "http://169.254.169.254/latest/meta-data/iam/security-credentials"
  // → AWS metadata with credentials!
})

// 🟢 Allowlist approach
const ALLOWED_DOMAINS = ['api.trusted.com', 'cdn.partner.com']
function isAllowedUrl(url) {
  const parsed = new URL(url)
  if (!['http:', 'https:'].includes(parsed.protocol)) return false
  if (!ALLOWED_DOMAINS.includes(parsed.hostname)) return false
  return true
}

// 🟢 Block private/internal IPs
const BLOCKED_RANGES = ['127.', '10.', '172.16.', '192.168.', '169.254.', '::1', 'localhost']
function isPrivateIp(host) {
  return BLOCKED_RANGES.some(range => host.startsWith(range))
}

SSTI (Server-Side Template Injection)

2. Never Use User Input in Templates

// 🔴 CRITICAL — Template injection
// Pug
res.render('template', { name: req.query.name })
// If name = "#{root.process.mainModule.require('child_process').execSync('id').toString()}"
// → RCE!

// 🔴 Handlebars - prototype pollution via template
// {{constructor.prototype.toString}}

// 🟢 Escape user input before template rendering
// Use template-specific escaping functions
// Never pass raw user input as template variables for unsafe contexts

Scan for: res.render(, template.render(, Jinja2, Twig, Smarty with user input


Prototype Pollution

3. Object Operations with User Input

// 🔴 CRITICAL — Pollutes Object.prototype
function merge(target, source) {
  for (const key in source) {
    target[key] = source[key]  // if key is "__proto__"...
  }
}
merge({}, JSON.parse('{"__proto__": {"isAdmin": true}}'))
// Now: ({}).isAdmin === true for ALL objects!

// 🔴 Lodash merge before patch
_.merge({}, userInput)

// 🟢 Protection approaches
// Option 1: Validate keys before merge
function safeMerge(target, source) {
  for (const key of Object.keys(source)) {
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue
    target[key] = source[key]
  }
}
// Option 2: Create object without prototype
const safe = Object.create(null)
// Option 3: Use JSON.parse with schema validation (Zod, Yup)

Scan for: _.merge, Object.assign, deep-merge patterns, [key] assignment in loops


Zip Slip

4. Archive Extraction Path Validation

// 🔴 CRITICAL — Malicious zip contains "../../etc/cron.d/evil"
// Extraction overwrites system files!
const zip = new AdmZip(uploadedFile)
zip.extractAllTo('/uploads/')  // 🔴 No path validation!

// 🟢 Validate every entry path
const zip = new AdmZip(uploadedFile)
const entries = zip.getEntries()
for (const entry of entries) {
  const entryPath = path.resolve('/uploads/', entry.entryName)
  if (!entryPath.startsWith(path.resolve('/uploads/'))) {
    throw new Error('Zip Slip attack detected')
  }
}
zip.extractAllTo('/uploads/')
# Python
import zipfile, os
def safe_extract(zf, target_dir):
    for member in zf.namelist():
        member_path = os.path.abspath(os.path.join(target_dir, member))
        if not member_path.startswith(os.path.abspath(target_dir)):
            raise ValueError(f"Zip Slip: {member}")
    zf.extractall(target_dir)

Open Redirect

5. Validate Redirect URLs

// 🔴 Open redirect — phishing attacks
const redirect = req.query.next
res.redirect(redirect)

// 🔴 Insufficient check (bypasses)
if (redirect.startsWith('/')) res.redirect(redirect)
// Bypass: //evil.com or /\evil.com

// 🟢 Strict allowlist
const ALLOWED_PATHS = ['/dashboard', '/profile', '/settings', '/home']
const redirect = req.query.next
if (!ALLOWED_PATHS.includes(redirect)) return res.redirect('/dashboard')
res.redirect(redirect)

XXE (XML External Entity)

6. Disable External Entities in XML Parsing

// 🔴 Default XML parsers may process external entities
// Attack: <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>

// Node.js - xml2js (safe by default, but verify)
const xml2js = require('xml2js')
const parser = new xml2js.Parser({ 
  // xml2js is safe by default
})

// Java - disable XXE
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance()
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)

// Python - use defusedxml
import defusedxml.ElementTree as ET
ET.parse(xmlfile)  // safe

Scan for: xml.etree, lxml, libxml2, DOMParser, XML parsing without entity restrictions


XML Bomb (Billion Laughs)

7. Limit XML Entity Expansion

// 🔴 Exponential entity expansion crashes server
// <!ENTITY a "aaa"> <!ENTITY b "&a;&a;&a;..."> ...
// Solution: defusedxml (Python), disable DTD processing (Java)

// Node.js — limit with sax parser options
const sax = require('sax')
// sax doesn't expand entities by default — safe

Mass Assignment

8. Never Pass req.body Directly to DB

// 🔴 CRITICAL — User can set any field
await User.create(req.body)
await user.update(req.body)

// 🟢 Explicitly pick allowed fields
const allowed = ['name', 'email', 'bio']
const data = Object.fromEntries(
  Object.entries(req.body).filter(([key]) => allowed.includes(key))
)
await User.create(data)

// 🟢 With Zod validation
const UpdateSchema = z.object({ name: z.string(), email: z.string().email() })
const data = UpdateSchema.parse(req.body)  // throws if extra fields

IDOR (Insecure Direct Object Reference)

9. Always Verify Ownership

// 🔴 CRITICAL — User can access any record by changing ID
app.get('/api/invoices/:id', auth, async (req, res) => {
  const invoice = await Invoice.findById(req.params.id)
  res.json(invoice)  // No ownership check!
})

// 🟢 Always verify ownership
app.get('/api/invoices/:id', auth, async (req, res) => {
  const invoice = await Invoice.findOne({
    _id: req.params.id,
    userId: req.user.id  // ← Must match authenticated user
  })
  if (!invoice) return res.status(404).json({ error: 'Not found' })
  res.json(invoice)
})

Forced Browsing (CWE-425)

10. All Routes Protected by Auth Middleware

// 🔴 Admin route accessible without auth
app.get('/admin/users', getAllUsers)
app.get('/api/export', exportAllData)

// 🟢 Auth middleware on ALL protected routes
const requireAuth = (req, res, next) => {
  if (!req.user) return res.status(401).json({ error: 'Unauthorized' })
  next()
}
const requireAdmin = (req, res, next) => {
  if (req.user?.role !== 'admin') return res.status(403).json({ error: 'Forbidden' })
  next()
}
app.get('/admin/users', requireAuth, requireAdmin, getAllUsers)