SSRF: When Your Server Makes Requests for Attackers
Attackers use your server to access internal systems they can't reach directly
Quick Answer
SSRF (Server-Side Request Forgery) lets attackers make your server send requests to internal systems they can't reach directly. Next.js had critical SSRF vulnerabilities in 2024-2025. Always validate and allowlist URLs before your server fetches them.
What is SSRF?
SSRF (Server-Side Request Forgery) happens when attackers trick your server into making HTTP requests on their behalf. Your server has access to internal systems - localhost services, cloud metadata endpoints, private APIs - that attackers can't reach directly from the internet. By controlling a URL your server fetches, they gain access to these protected resources.
Think of it like convincing a delivery driver who has access to a secure building to pick up a package for you from inside. The driver (your server) has legitimate access; the attacker just manipulates what they retrieve.
SSRF is serious enough to have its own dedicated category in the OWASP Top 10 (A10:2021). The consequences include accessing internal APIs, stealing cloud credentials, and scanning internal networks - all from a simple URL parameter.
Real Next.js SSRF Vulnerabilities
This isn't theoretical - Next.js had critical SSRF vulnerabilities that affected real applications:
Affected: Next.js 13.4.0 - 14.1.0 (Server Actions)
Condition: Self-hosted deployments using Server Actions with redirect()
Attack: Manipulating the Host header to trigger SSRF via the redirect function
Fixed: Next.js 14.1.1+
View AdvisoryAffected: Next.js < 14.2.32 and < 15.4.7
Condition: Middleware using next() function
Fixed: Next.js 14.2.32+ and 15.4.7+
View AdvisoryIf you're a vibe coder using Next.js, check your package.json version immediately. Run npm show next version to see available updates.
Common SSRF Attack Patterns
Attackers have many targets once they can control server-side requests:
Internal Service Access
http://localhost:3000/admin Access admin panels and internal APIs running on the same server.
Cloud Metadata Theft
http://169.254.169.254/latest/meta-data/ AWS, GCP, and Azure expose IAM credentials at this endpoint. SSRF can steal them.
File Protocol
file:///etc/passwd Read local files if the HTTP library supports file:// protocol.
Internal Network Scanning
http://192.168.1.1:22/ Port scan internal hosts to discover services.
DNS Rebinding
http://attacker-domain.com/ External domain that resolves to internal IP after initial check.
For detailed exploitation techniques, see the PortSwigger SSRF Guide.
Why AI Tools Generate SSRF-Vulnerable Code
When you ask AI tools to add features like "preview a URL" or "fetch data from a webhook," they generate the simplest working code without URL validation. This is classic vibe coding risk - the AI gives you functional code that's insecure by default.
Features commonly vibe coded with SSRF vulnerabilities:
- URL preview/unfurling - "Add link previews to my chat app"
- Image proxy - "Resize images from any URL"
- Webhook handlers - "Add webhook support"
- Import from URL - "Let users import data from a URL"
- PDF generation - "Generate PDFs from URLs"
AI-generated code for these features almost never includes URL validation because you didn't explicitly ask for it.
Vulnerable Code Examples
Pattern 1: Vulnerable URL Fetch (AI Default)
// AI generates this for "fetch URL" features
app.get('/preview', async (req, res) => {
const url = req.query.url
const response = await fetch(url) // DANGEROUS!
const data = await response.text()
res.send(data)
})
// Attacker uses: /preview?url=http://localhost:3000/admin
// Or: /preview?url=http://169.254.169.254/latest/meta-data/Any URL accepted, including internal services and cloud metadata endpoints. The attacker steals IAM credentials from AWS.
Pattern 2: Vulnerable Image Proxy
// Image proxy without validation
app.get('/image-proxy', async (req, res) => {
const imageUrl = req.query.src
const response = await fetch(imageUrl)
const buffer = await response.arrayBuffer()
res.set('Content-Type', response.headers.get('content-type'))
res.send(Buffer.from(buffer))
})
// Attacker: /image-proxy?src=file:///etc/passwdImage proxies are common SSRF vectors. No protocol or domain validation allows file:// and internal URL access.
How to Fix SSRF
Secure Pattern: Complete URL Validation
const dns = require('dns').promises
const ALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com']
const ALLOWED_PROTOCOLS = ['https:']
// Block internal IP ranges
const BLOCKED_IP_PATTERNS = [
/^localhost$/i,
/^127\./,
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
/^192\.168\./,
/^169\.254\./, // AWS/Azure metadata
/^0\.0\.0\.0$/,
]
async function validateUrl(urlString) {
// Parse URL safely
let parsed
try {
parsed = new URL(urlString)
} catch {
throw new Error('Invalid URL')
}
// Check protocol
if (!ALLOWED_PROTOCOLS.includes(parsed.protocol)) {
throw new Error('Only HTTPS allowed')
}
// Check domain allowlist
if (!ALLOWED_DOMAINS.includes(parsed.hostname)) {
throw new Error('Domain not allowed')
}
// Check hostname isn't internal
if (BLOCKED_IP_PATTERNS.some(p => p.test(parsed.hostname))) {
throw new Error('Internal address blocked')
}
// DNS resolution check - prevents rebinding
const ips = await dns.resolve4(parsed.hostname)
for (const ip of ips) {
if (BLOCKED_IP_PATTERNS.some(p => p.test(ip))) {
throw new Error('Internal address blocked')
}
}
return parsed.href
}
app.get('/preview', async (req, res) => {
try {
const safeUrl = await validateUrl(req.query.url)
const response = await fetch(safeUrl)
const data = await response.text()
res.send(data)
} catch (err) {
res.status(403).json({ error: err.message })
}
})Complete validation: protocol allowlist, domain allowlist, IP blocking, and DNS resolution check to catch rebinding attacks.
Next.js Server Action Protection
// Secure Next.js Server Action
'use server'
import { redirect } from 'next/navigation'
const ALLOWED_PATHS = ['/dashboard', '/profile', '/settings']
export async function safeRedirect(path: string) {
// Block external redirects
if (path.startsWith('http://') || path.startsWith('https://')) {
throw new Error('External redirects not allowed')
}
// Block protocol-relative URLs
if (path.startsWith('//')) {
throw new Error('Protocol-relative URLs not allowed')
}
// Allowlist internal paths
if (!ALLOWED_PATHS.some(allowed => path.startsWith(allowed))) {
throw new Error('Invalid redirect path')
}
redirect(path)
}Server Actions should validate redirect paths strictly. This prevents CVE-2024-34351-style attacks.
Key Security Points
- Always use an allowlist - specify exactly which domains are allowed
- Block all internal IPs - 127.x, 10.x, 172.16-31.x, 192.168.x, 169.254.x
- Only allow HTTPS - block file://, gopher://, and other protocols
- Check DNS resolution - catch rebinding attacks where domain resolves to internal IP
- Don't trust redirects - an allowed domain could redirect to internal URL
AI Fix Prompt
Copy this prompt to your AI tool to scan your codebase for SSRF vulnerabilities:
You are a security auditor. Scan this codebase for SSRF (Server-Side Request Forgery) vulnerabilities (CWE-918).
## What to Search For
### 1. HTTP Requests with User-Controlled URLs
Search for fetch(), axios, got, node-fetch, or http/https modules with dynamic URLs:
- fetch(req.query.url)
- axios.get(userProvidedUrl)
- got(urlFromRequest)
- http.request(userInput)
### 2. Common SSRF-Vulnerable Features
Look for these features that commonly have SSRF:
- URL preview / link unfurling
- Image proxy / image resizer
- Webhook receivers and senders
- PDF generators that fetch URLs
- Import from URL functionality
- RSS feed readers
- OAuth callbacks
### 3. Next.js Specific Patterns
**Server Actions with redirects:**
```typescript
// CHECK: Does this validate the redirect target?
'use server'
import { redirect } from 'next/navigation'
export async function action(url: string) {
redirect(url) // SSRF if url not validated
}
```
**API routes fetching URLs:**
```typescript
// app/api/proxy/route.ts
export async function GET(request: NextRequest) {
const url = request.nextUrl.searchParams.get('url')
const response = await fetch(url) // VULNERABLE!
}
```
### 4. URL Validation to Verify
For any URL fetching, verify ALL of these exist:
```javascript
// 1. Parse URL safely
const parsed = new URL(userUrl)
// 2. Protocol allowlist
if (!['https:'].includes(parsed.protocol)) reject
// 3. Domain allowlist OR blocklist check
if (!ALLOWED_DOMAINS.includes(parsed.hostname)) reject
// 4. Block internal IPs (including cloud metadata)
const blocked = [
/^localhost$/i,
/^127\./,
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
/^192\.168\./,
/^169\.254\./, // AWS/Azure metadata
/^0\.0\.0\.0$/,
/^\[::1\]$/, // IPv6 localhost
]
if (blocked.some(p => p.test(parsed.hostname))) reject
// 5. DNS resolution check (prevents rebinding)
const ips = await dns.resolve4(parsed.hostname)
for (const ip of ips) {
if (blocked.some(p => p.test(ip))) reject
}
```
### 5. Bypass Attempts to Consider
Check if validation can be bypassed:
- IP address representations: 0177.0.0.1 (octal), 0x7f.0.0.1 (hex)
- URL encoding: %31%32%37.0.0.1
- DNS rebinding: external domain resolving to internal IP
- Redirects: allowed domain redirects to internal URL
- IPv6: [::1], [0:0:0:0:0:0:0:1]
- Decimal IP: 2130706433 (= 127.0.0.1)
## Framework Version Checks
For Next.js projects, verify version is patched:
- CVE-2024-34351: Fixed in 14.1.1+
- CVE-2025-57822: Fixed in 14.2.32+ and 15.4.7+
Check package.json for Next.js version.
## Report Format
For each finding, report:
1. File and line number
2. The vulnerable code pattern
3. What user input controls the URL
4. What validation exists (if any)
5. What's missing (protocol check, domain allowlist, IP block, DNS check)
6. Severity (Critical if no validation, High if partial)
7. Specific fix with codeFrequently Asked Questions
What is SSRF vulnerability?
SSRF (Server-Side Request Forgery) is a vulnerability where attackers trick your server into making HTTP requests to internal systems they cannot reach directly. By controlling a URL your server fetches, attackers can access internal APIs, cloud metadata endpoints, or scan internal networks. SSRF has its own category in OWASP Top 10 (A10:2021).
How do attackers exploit SSRF?
Attackers find features where your server fetches URLs - like image previews, URL metadata extraction, or webhooks. They submit malicious URLs pointing to internal services (http://localhost:3000/admin) or cloud metadata (http://169.254.169.254). Your server, which has network access to these resources, fetches the data and returns it to the attacker.
Is my Next.js app vulnerable to SSRF?
Possibly. Next.js had critical SSRF vulnerabilities: CVE-2024-34351 (versions 13.4.0-14.1.0) in Server Actions and CVE-2025-57822 in middleware. Update to Next.js 14.1.1+ or 15.4.7+ and audit any code that fetches user-supplied URLs. Self-hosted deployments were more affected than Vercel-hosted apps.
How do I prevent SSRF in Node.js?
Implement URL allowlisting: only allow requests to approved domains. Block internal IP ranges (127.x.x.x, 10.x.x.x, 172.16-31.x.x, 192.168.x.x, 169.254.x.x). Only allow https:// protocol. Resolve DNS and check the resolved IP is not internal. Never pass user input directly to fetch() or HTTP libraries.
What can attackers access with SSRF?
Attackers can access: internal APIs and admin panels (localhost services), cloud metadata endpoints to steal IAM credentials (AWS 169.254.169.254), internal databases, configuration services, and can port-scan your internal network. On cloud platforms, SSRF can lead to complete account takeover via stolen credentials.
External Resources
- CWE-918: Server-Side Request Forgery - Official vulnerability definition
- OWASP SSRF (A10:2021) - Top 10 category explanation
- PortSwigger SSRF Guide - Detailed exploitation techniques
- OWASP SSRF Prevention Cheat Sheet - Comprehensive prevention guide
- Next.js CVE-2024-34351 Advisory - Server Actions SSRF
Find SSRF Vulnerabilities in Your Code
VibeShip Scanner automatically detects SSRF patterns in your codebase, including unvalidated URL fetching and missing internal IP blocks.
Scan Your Code Free