acc-check-ssrf
SSRF (Server-Side Request Forgery) Security Check
Analyze PHP code for SSRF vulnerabilities (OWASP A10:2021).
Detection Patterns
1. User-Controlled URLs
// 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);
2. Cloud Metadata Endpoint Access
// 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 */ }
3. Internal Network Access
// 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';
4. URL Parsing Bypass
// 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/';
5. Protocol Attacks
// 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';
6. PDF/Image Generation SSRF
// 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");
7. Webhook/Callback SSRF
// 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);
8. SVG/XML External References
// 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
Grep Patterns
# 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"
Validation Patterns
URL Allowlist
// 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);
}
}
Block Internal Networks
// 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.');
}
}
Protocol Allowlist
// 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;
}
}
Disable Redirects
// 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');
}
},
],
]);
Severity Classification
| 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 |
Output Format
### 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:
More from dykyi-roman/awesome-claude-code
psr-overview-knowledge
PHP Standards Recommendations (PSR) overview knowledge base. Provides comprehensive reference for all accepted PSRs including PSR-1,3,4,6,7,11,12,13,14,15,16,17,18,20. Use for PSR selection decisions and compliance audits.
22detect-code-smells
Detects code smells in PHP codebases. Identifies God Class, Feature Envy, Data Clumps, Long Parameter List, Long Method, Primitive Obsession, Message Chains, Inappropriate Intimacy. Generates actionable reports with refactoring recommendations.
15clean-arch-knowledge
Clean Architecture knowledge base. Provides patterns, antipatterns, and PHP-specific guidelines for Clean Architecture and Hexagonal Architecture audits.
15ddd-knowledge
DDD architecture knowledge base. Provides patterns, antipatterns, and PHP-specific guidelines for Domain-Driven Design audits.
14testing-knowledge
Testing knowledge base for PHP 8.4 projects. Provides testing pyramid, AAA pattern, naming conventions, isolation principles, DDD testing guidelines, and PHPUnit patterns.
12bug-root-cause-finder
Root cause analysis methods for PHP bugs. Provides 5 Whys technique, fault tree analysis, git bisect guidance, and stack trace parsing.
12