Skip to content

Fix potential OS command injection in webserver.js#32

Open
DongZifan wants to merge 1 commit into
jsdf:pcejsfrom
DongZifan:pcejs
Open

Fix potential OS command injection in webserver.js#32
DongZifan wants to merge 1 commit into
jsdf:pcejsfrom
DongZifan:pcejs

Conversation

@DongZifan
Copy link
Copy Markdown

Command Injection Reproduction Notes for webserver.js

Summary

Hello,
I am writing to report potential Path Traversal and OS Command Injection vulnerabilities in the following file:
jsdf/pce/46c3ecb/example/webserver.js

The first issue appears when user-controlled request paths are used to construct filesystem paths without verifying that the final resolved path remains inside the intended serving directory. Under certain conditions, specially crafted request paths may allow files outside the configured web root to be accessed.

The second issue appears when file paths are used to construct shell command strings that are later executed via child_process.execSync(...). Under certain conditions, specially crafted filenames may allow unintended command execution on the host system. This could potentially lead to command injection risks, depending on how the input path and filename are validated and processed.

Root Cause

The path traversal issue is caused by unsafe filesystem path construction in the following code:

const reqPath = req.url.replace(/\?.*/, '').replace(/_cb.*/, '');
const reqPathFSPath = path.join(serveDir, reqPath);

Because the final path is not checked against serveDir, a request such as /../secret_outside.txt may escape the intended serving directory.

The command injection issue is caused by unsafe shell command construction in the following function:

function getMimeType(filepath) {
  if (!filesMimeTypesCache[filepath]) {
    switch (path.extname(filepath)) {
      case '.css':
        filesMimeTypesCache[filepath] = 'text/css';
        break;
      case '.js':
        filesMimeTypesCache[filepath] = 'application/javascript';
        break;
      case '.wasm':
        filesMimeTypesCache[filepath] = 'application/wasm';
        break;
      default:
        filesMimeTypesCache[filepath] = exec(
          `file --mime-type --brief ${filepath}`
        )
          .toString()
          .trim();
    }
  }
  return filesMimeTypesCache[filepath];
}

Reproduction Material

A minimal reproduction script is provided in: poc_webserver.js
poc_webserver.js

This Proof of Concept (PoC) is intended to demonstrate that external input can reach dangerous filesystem access and command execution logic through the vulnerable code path.

What the PoC Does

The PoC performs a minimal end-to-end trigger of the vulnerable code path:

  1. It creates a temporary web root directory.
  2. It creates a secret file outside the public serving directory.
  3. It creates a malicious filename containing shell metacharacters.
  4. It starts the original webserver.js.
  5. It sends a crafted request to /../secret_outside.txt.
  6. This causes the application to resolve a path outside the intended serving directory.
  7. It sends a second crafted request for a malicious filename such as poc&calc&rem.
  8. This causes the application to invoke getMimeType(filepath).
  9. The supplied filepath value is concatenated into a shell command and passed to execSync(...).

In the provided example, the injected filename is crafted so that, on Windows, successful command execution opens the Calculator application. This serves as a visible indicator that external input can reach the OS command execution sink without proper sanitization.

Example Payload

The PoC uses the following path traversal payload:

/../secret_outside.txt

The PoC uses the following command injection filename:

poc&calc&rem

How to Run

Run from the project root:

node poc_webserver.js

Expected Output

When the PoC runs against the vulnerable version, you should see output similar to:

[POC] starting vulnerable webserver.js ...

[POC-1] testing path traversal ...
[server stdout] 200 /../secret_outside.txt text/plain
[POC-1] response:
PWNED_PATH_TRAVERSAL_SECRET

[POC-1] SUCCESS: path traversal confirmed.

[POC-2] testing command injection through filename ...
[POC-2] requesting malicious filename: poc&calc&rem
[server stdout] 200 /poc&calc&rem text/plain
[POC-2] If Calculator opens, command injection is confirmed.

In the vulnerable version, after the crafted request is processed, the server can read a file outside the intended public directory. In addition, after the malicious filename is requested, the local machine will launch the Calculator application as a benign demonstration effect.

This shows that attacker-controlled request paths can influence filesystem access behavior, and attacker-controlled filenames can influence command execution behavior through the vulnerable getMimeType() path.

Patch Explanation

This branch also includes a patched version of webserver.js intended to mitigate the path traversal and command injection risks described above.

What the patch changes

The patch adds path validation before request paths are used for filesystem access.

In the vulnerable version, user-controlled request paths such as:

  • /../secret_outside.txt
  • encoded traversal variants
  • paths containing parent directory traversal segments

could reach filesystem APIs without validating whether the final path remained inside serveDir.

The patched version resolves the request path against serveDir and rejects paths that escape the intended serving directory.

The patch also changes MIME type detection so that file paths are no longer concatenated into a shell command string.

In the vulnerable version, attacker-controlled filenames such as:

  • poc&calc&rem
  • filenames containing shell metacharacters

could reach child_process.execSync(...) through string concatenation.

The patched version uses execFileSync(...) with an argument array so the file path is passed as a single argument instead of being interpreted by the shell.

Validation introduced by the patch

For request paths, the patch checks that:
the value is decoded safely
the final resolved filesystem path is inside serveDir
paths escaping the serving directory are rejected before filesystem access

For MIME type detection, the patch avoids shell command string construction by replacing:

exec(`file --mime-type --brief ${filepath}`)

with:

execFileSync('file', ['--mime-type', '--brief', filepath])

If validation fails, the request is rejected before it reaches file reading logic. If a filename contains shell metacharacters, those characters are treated as part of the filename argument rather than as shell syntax.# Command Injection Reproduction Notes for webserver.js

Summary

Hello,
I am writing to report potential Path Traversal and OS Command Injection vulnerabilities in the following file:
jsdf/pce/46c3ecb/example/webserver.js

The first issue appears when user-controlled request paths are used to construct filesystem paths without verifying that the final resolved path remains inside the intended serving directory. Under certain conditions, specially crafted request paths may allow files outside the configured web root to be accessed.

The second issue appears when file paths are used to construct shell command strings that are later executed via child_process.execSync(...). Under certain conditions, specially crafted filenames may allow unintended command execution on the host system. This could potentially lead to command injection risks, depending on how the input path and filename are validated and processed.

Root Cause

The path traversal issue is caused by unsafe filesystem path construction in the following code:

const reqPath = req.url.replace(/\?.*/, '').replace(/_cb.*/, '');
const reqPathFSPath = path.join(serveDir, reqPath);

Because the final path is not checked against serveDir, a request such as /../secret_outside.txt may escape the intended serving directory.

The command injection issue is caused by unsafe shell command construction in the following function:

function getMimeType(filepath) {
  if (!filesMimeTypesCache[filepath]) {
    switch (path.extname(filepath)) {
      case '.css':
        filesMimeTypesCache[filepath] = 'text/css';
        break;
      case '.js':
        filesMimeTypesCache[filepath] = 'application/javascript';
        break;
      case '.wasm':
        filesMimeTypesCache[filepath] = 'application/wasm';
        break;
      default:
        filesMimeTypesCache[filepath] = exec(
          `file --mime-type --brief ${filepath}`
        )
          .toString()
          .trim();
    }
  }
  return filesMimeTypesCache[filepath];
}

Reproduction Material

A minimal reproduction script is provided in: poc_webserver.js

This Proof of Concept (PoC) is intended to demonstrate that external input can reach dangerous filesystem access and command execution logic through the vulnerable code path.

What the PoC Does

The PoC performs a minimal end-to-end trigger of the vulnerable code path:

  1. It creates a temporary web root directory.
  2. It creates a secret file outside the public serving directory.
  3. It creates a malicious filename containing shell metacharacters.
  4. It starts the original webserver.js.
  5. It sends a crafted request to /../secret_outside.txt.
  6. This causes the application to resolve a path outside the intended serving directory.
  7. It sends a second crafted request for a malicious filename such as poc&calc&rem.
  8. This causes the application to invoke getMimeType(filepath).
  9. The supplied filepath value is concatenated into a shell command and passed to execSync(...).

In the provided example, the injected filename is crafted so that, on Windows, successful command execution opens the Calculator application. This serves as a visible indicator that external input can reach the OS command execution sink without proper sanitization.

Example Payload

The PoC uses the following path traversal payload:

/../secret_outside.txt

The PoC uses the following command injection filename:

poc&calc&rem

How to Run

Run from the project root:

node poc_webserver.js

Expected Output

When the PoC runs against the vulnerable version, you should see output similar to:

[POC] starting vulnerable webserver.js ...

[POC-1] testing path traversal ...
[server stdout] 200 /../secret_outside.txt text/plain
[POC-1] response:
PWNED_PATH_TRAVERSAL_SECRET

[POC-1] SUCCESS: path traversal confirmed.

[POC-2] testing command injection through filename ...
[POC-2] requesting malicious filename: poc&calc&rem
[server stdout] 200 /poc&calc&rem text/plain
[POC-2] If Calculator opens, command injection is confirmed.

In the vulnerable version, after the crafted request is processed, the server can read a file outside the intended public directory. In addition, after the malicious filename is requested, the local machine will launch the Calculator application as a benign demonstration effect.

This shows that attacker-controlled request paths can influence filesystem access behavior, and attacker-controlled filenames can influence command execution behavior through the vulnerable getMimeType() path.

Patch Explanation

This branch also includes a patched version of webserver.js intended to mitigate the path traversal and command injection risks described above.

What the patch changes

The patch adds path validation before request paths are used for filesystem access.

In the vulnerable version, user-controlled request paths such as:

  • /../secret_outside.txt
  • encoded traversal variants
  • paths containing parent directory traversal segments

could reach filesystem APIs without validating whether the final path remained inside serveDir.

The patched version resolves the request path against serveDir and rejects paths that escape the intended serving directory.

The patch also changes MIME type detection so that file paths are no longer concatenated into a shell command string.

In the vulnerable version, attacker-controlled filenames such as:

  • poc&calc&rem
  • filenames containing shell metacharacters

could reach child_process.execSync(...) through string concatenation.

The patched version uses execFileSync(...) with an argument array so the file path is passed as a single argument instead of being interpreted by the shell.

Validation introduced by the patch

For request paths, the patch checks that:
the value is decoded safely
the final resolved filesystem path is inside serveDir
paths escaping the serving directory are rejected before filesystem access

For MIME type detection, the patch avoids shell command string construction by replacing:

exec(`file --mime-type --brief ${filepath}`)

with:

execFileSync('file', ['--mime-type', '--brief', filepath])

If validation fails, the request is rejected before it reaches file reading logic. If a filename contains shell metacharacters, those characters are treated as part of the filename argument rather than as shell syntax.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant