GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-5RQ4-664W-9X2C

Vulnerability from github – Published: 2026-02-25 22:34 – Updated: 2026-02-25 22:34
VLAI
Summary
Basic FTP has Path Traversal Vulnerability in its downloadToDir() method
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.

Source-to-Sink Flow

1. SOURCE: FTP server sends LIST response
└─> "-rw-r--r-- 1 user group 1024 Jan 20 12:00 ../../../etc/passwd"

2. PARSER: parseListUnix.ts:100 extracts filename
└─> file.name = "../../../etc/passwd"

3. VALIDATION: parseListUnix.ts:101 checks
└─> if (name === "." || name === "..") ❌ (only filters exact matches)
└─> "../../../etc/passwd" !== "." && !== ".." ✅ PASSES

4. SINK: Client.ts:707 uses filename directly
└─> const localPath = join(localDirPath, file.name)
└─> join("/safe/download", "../../../etc/passwd")
└─> Result: "/safe/download/../../../etc/passwd" → resolves to "/etc/passwd"

5. FILE WRITE: Client.ts:512 opens file
└─> fsOpen(localPath, "w") → writes to /etc/passwd (outside intended directory)

Vulnerable Code

File: src/Client.ts:707

protected async _downloadFromWorkingDir(localDirPath: string): Promise<void> {
await ensureLocalDirectory(localDirPath)
for (const file of await this.list()) {
const localPath = join(localDirPath, file.name) // ⚠️ VULNERABLE
// file.name comes from untrusted FTP server, no sanitization
await this.downloadTo(localPath, file.name)
}
}

Root Cause: - Parser validation (parseListUnix.ts:101) only filters exact . or .. entries - No sanitization of ../ sequences in filenames - path.join() doesn't prevent traversal, fs.open() resolves paths

Impact

A malicious FTP server can: - Write files to arbitrary locations on the client filesystem - Overwrite critical system files (if user has write access) - Potentially achieve remote code execution

Affected Versions

  • Tested: v5.1.0
  • Likely: All versions (code pattern exists since initial implementation)

Mitigation

Workaround: Do not use downloadToDir() with untrusted FTP servers.

Fix: Sanitize filenames before use:

import { basename } from 'path'

// In _downloadFromWorkingDir:
const sanitizedName = basename(file.name) // Strip path components
const localPath = join(localDirPath, sanitizedName)
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "basic-ftp"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "5.2.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-27699"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-02-25T22:34:26Z",
    "nvd_published_at": "2026-02-25T15:20:53Z",
    "severity": "CRITICAL"
  },
  "details": "The `basic-ftp`\u00a0library contains a path traversal vulnerability in the `downloadToDir()`\u00a0method. 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\u2514\u2500\u003e \"-rw-r--r-- 1 user group 1024 Jan 20 12:00 ../../../etc/passwd\"\n\n2. PARSER: parseListUnix.ts:100 extracts filename\n\u2514\u2500\u003e file.name = \"../../../etc/passwd\"\n\n3. VALIDATION: parseListUnix.ts:101 checks\n\u2514\u2500\u003e if (name === \".\" || name === \"..\") \u274c (only filters exact matches)\n\u2514\u2500\u003e \"../../../etc/passwd\" !== \".\" \u0026\u0026 !== \"..\" \u2705 PASSES\n\n4. SINK: Client.ts:707 uses filename directly\n\u2514\u2500\u003e const localPath = join(localDirPath, file.name)\n\u2514\u2500\u003e join(\"/safe/download\", \"../../../etc/passwd\")\n\u2514\u2500\u003e Result: \"/safe/download/../../../etc/passwd\" \u2192 resolves to \"/etc/passwd\"\n\n5. FILE WRITE: Client.ts:512 opens file\n\u2514\u2500\u003e fsOpen(localPath, \"w\") \u2192 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\u003cvoid\u003e\u00a0{\nawait ensureLocalDirectory(localDirPath)\nfor\u00a0(const file of await\u00a0this.list()) {\nconst localPath = join(localDirPath, file.name) // \u26a0\ufe0f 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-\u00a0Parser validation (`parseListUnix.ts:101`) only filters exact `.`\u00a0or `..`\u00a0entries\n-\u00a0No sanitization of `../`\u00a0sequences in filenames\n- `path.join()`\u00a0doesn\u0027t prevent traversal, `fs.open()`\u00a0resolves paths\n\n\n# Impact\n\nA malicious FTP server can:\n-\u00a0Write files to arbitrary locations on the client filesystem\n-\u00a0Overwrite critical system files (if user has write access)\n-\u00a0Potentially 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()`\u00a0with untrusted FTP servers.\n\n**Fix**: Sanitize filenames before use:\n\n```typescript\nimport\u00a0{ basename\u00a0} from \u0027path\u0027\n\n// In _downloadFromWorkingDir:\nconst sanitizedName = basename(file.name) // Strip path components\nconst localPath = join(localDirPath, sanitizedName)\n```",
  "id": "GHSA-5rq4-664w-9x2c",
  "modified": "2026-02-25T22:34:26Z",
  "published": "2026-02-25T22:34:26Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/patrickjuchli/basic-ftp/security/advisories/GHSA-5rq4-664w-9x2c"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27699"
    },
    {
      "type": "WEB",
      "url": "https://github.com/patrickjuchli/basic-ftp/commit/2a2a0e6514357b9eda07c2f8afbd3f04727a7cd9"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/patrickjuchli/basic-ftp"
    },
    {
      "type": "WEB",
      "url": "https://github.com/patrickjuchli/basic-ftp/releases/tag/v5.2.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Basic FTP has Path Traversal Vulnerability in its downloadToDir()\u00a0method"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…