From acc
Analyzes PHP code for SSRF vulnerabilities. Detects unvalidated URLs, internal network access, DNS rebinding, cloud metadata access, URL parsing bypasses. Use for PHP web app security audits.
How this skill is triggered — by the user, by Claude, or both
Slash command
/acc:check-ssrfThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Analyze PHP code for SSRF vulnerabilities (OWASP A10:2021).
Analyze PHP code for SSRF vulnerabilities (OWASP A10:2021).
// CRITICAL: Direct URL from user input
$url = $_GET['url'];
$content = file_get_contents($url);
// CRITICAL: Request URL from parameter
$response = $httpClient->get($request->input('callback'));
// CRITICAL: User input in cURL
$ch = curl_init($_POST['endpoint']);
curl_exec($ch);
// CRITICAL: Guzzle with user input
$client = new GuzzleHttp\Client();
$client->request('GET', $userProvidedUrl);
// CRITICAL: AWS metadata endpoint
$url = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/';
// User could redirect to this endpoint
// CRITICAL: GCP metadata
$url = 'http://metadata.google.internal/computeMetadata/v1/';
// CRITICAL: Azure metadata
$url = 'http://169.254.169.254/metadata/instance';
// Detection: URLs that could reach metadata
if (strpos($url, '169.254.') !== false) { /* Block */ }
// CRITICAL: Access to internal services
$response = file_get_contents("http://internal-api:8080/admin");
// CRITICAL: Localhost bypass
$url = $_GET['url'];
// User inputs: http://localhost/admin, http://127.0.0.1/admin
// Or: http://0.0.0.0/, http://[::1]/, http://127.1/
// CRITICAL: Private IP ranges
// 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
$url = 'http://10.0.0.1/internal-service';
// CRITICAL: Protocol confusion
$url = 'http://evil.com@internal-server/';
$url = 'http://internal-server#@evil.com/';
$url = 'http://internal-server\@evil.com/';
// CRITICAL: URL encoding bypass
$url = 'http://127.0.0.1%00.evil.com/';
$url = 'http://127。0。0。1/'; // Unicode dots
// CRITICAL: DNS rebinding - domain resolves to internal IP
$url = 'http://rebind.attacker.com/'; // First resolves to public, then to 127.0.0.1
// CRITICAL: Redirect chains
$url = 'http://allowed.com/redirect?to=http://internal/';
// CRITICAL: File protocol
$url = 'file:///etc/passwd';
$content = file_get_contents($url);
// CRITICAL: Gopher protocol (can access Redis, memcached)
$url = 'gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall';
// CRITICAL: Dict protocol
$url = 'dict://127.0.0.1:6379/info';
// CRITICAL: LDAP protocol
$url = 'ldap://evil.com/o=evil';
// CRITICAL: HTML to PDF with user content
$html = '<img src="' . $userUrl . '">';
$pdf->loadHtml($html);
// CRITICAL: Image URL in PDF
$pdf->image($request->input('logo_url'));
// CRITICAL: wkhtmltopdf with user HTML
shell_exec("wkhtmltopdf '$userHtml' output.pdf");
// CRITICAL: User-provided webhook URL
$webhookUrl = $request->input('webhook_url');
$httpClient->post($webhookUrl, ['json' => $data]);
// CRITICAL: OAuth callback
$callbackUrl = $request->input('redirect_uri');
return redirect($callbackUrl . '?code=' . $code);
// CRITICAL: Import from URL
$data = file_get_contents($request->input('import_url'));
$this->importData($data);
// CRITICAL: SVG with external references
// User uploads SVG containing:
// <image xlink:href="http://internal/secret" />
// <use xlink:href="http://internal/api" />
$svg = file_get_contents($uploadedFile);
// Rendering SVG may fetch external resources
# User URL in HTTP functions
Grep: "(file_get_contents|fopen|curl_init|readfile)\s*\([^)]*\\\$" --glob "**/*.php"
# HTTP client with variable
Grep: "(->get|->post|->request)\s*\([^)]*\\\$" --glob "**/*.php"
# Webhook/callback patterns
Grep: "(webhook|callback|redirect).*url.*\\\$" -i --glob "**/*.php"
# URL from request
Grep: "\\\$_(GET|POST|REQUEST)\[.*url" -i --glob "**/*.php"
// SECURE: Strict allowlist
final class UrlValidator
{
private const ALLOWED_HOSTS = [
'api.trusted-service.com',
'cdn.example.com',
];
public function validate(string $url): bool
{
$parsed = parse_url($url);
if ($parsed === false || !isset($parsed['host'])) {
return false;
}
return in_array($parsed['host'], self::ALLOWED_HOSTS, true);
}
}
// SECURE: Block private/internal IPs
final class SafeUrlFetcher
{
public function fetch(string $url): string
{
$parsed = parse_url($url);
$ip = gethostbyname($parsed['host']);
if ($this->isPrivateIp($ip) || $this->isMetadataIp($ip)) {
throw new SecurityException('Internal URL not allowed');
}
return file_get_contents($url);
}
private function isPrivateIp(string $ip): bool
{
return filter_var(
$ip,
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
) === false;
}
private function isMetadataIp(string $ip): bool
{
return str_starts_with($ip, '169.254.');
}
}
// SECURE: Only allow HTTPS
final class SecureUrlValidator
{
public function validate(string $url): bool
{
$parsed = parse_url($url);
// Only HTTPS allowed
if (($parsed['scheme'] ?? '') !== 'https') {
return false;
}
// No credentials in URL
if (isset($parsed['user']) || isset($parsed['pass'])) {
return false;
}
return true;
}
}
// SECURE: Prevent redirect-based SSRF
$client = new GuzzleHttp\Client([
'allow_redirects' => false,
// Or limit redirects and verify each
'allow_redirects' => [
'max' => 3,
'on_redirect' => function ($request, $response, $uri) {
if (!$this->isAllowedHost($uri->getHost())) {
throw new SecurityException('Redirect to disallowed host');
}
},
],
]);
| Pattern | Severity | OWASP |
|---|---|---|
| Cloud metadata access | 🔴 Critical | A10 |
| Internal network access | 🔴 Critical | A10 |
| User URL without validation | 🔴 Critical | A10 |
| File/gopher protocol | 🔴 Critical | A10 |
| Webhook URL unvalidated | 🟠 Major | A10 |
| Missing redirect validation | 🟠 Major | A10 |
| Protocol not restricted | 🟡 Minor | A10 |
### SSRF: [Description]
**Severity:** 🔴 Critical
**Location:** `file.php:line`
**CWE:** CWE-918 (Server-Side Request Forgery)
**Issue:**
User-controlled URL is fetched without validation, allowing access to internal services.
**Attack Vector:**
1. Attacker provides URL: `http://169.254.169.254/latest/meta-data/`
2. Server fetches AWS credentials from metadata service
3. Attacker receives IAM credentials
**Code:**
```php
// Vulnerable
$data = file_get_contents($_GET['url']);
Fix:
// Secure: Validate URL before fetching
if (!$this->urlValidator->isAllowed($url)) {
throw new SecurityException('URL not allowed');
}
$data = file_get_contents($url);
References:
npx claudepluginhub dykyi-roman/awesome-claude-code --plugin accDetects Server-Side Request Forgery (SSRF) vulnerabilities where user-controlled URLs access internal services, cloud metadata, or bypass networks in JS/TS, Python, Go, Ruby code. Audits webhooks, URL previews, imports.
Detects SSRF vulnerabilities in HTTP requests to user-controlled URLs. Invoke when implementing webhook receivers, URL preview features, or proxying user-supplied URLs.
Audits Python code for SSRF (CWE-918) vulnerabilities in HTTP clients (requests, httpx, urllib, aiohttp), webhooks, proxies, file/model downloads, and SVG/XML external resources.