CVE-2024-31453: Insufficient Information (NVD-CWE-noinfo)
value: PsiTransfer is an open source, self-hosted file sharing solution. Prior to version 2.2.0, the absence of restrictions on the endpoint, which allows users to create a path for uploading a file in a file distribution, allows an attacker to add arbitrary files to the distribution. The vulnerability allows an attacker to influence those users who come to the file distribution after them and slip the victim files with a malicious or phishing signature. Version 2.2.0 contains a patch for the issue.

CVE-2024-31453 allows users to violate the integrity of a file bucket and upload new files there, while the vulnerability with the number CVE-2024-31454 allows users to violate the integrity of a single file that is uploaded by another user by writing data there and not allows you to upload new files to the bucket. Thus, vulnerabilities are reproduced differently, require different security recommendations and affect different objects of the application’s business logic.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-09T18:15Z
Repository: https://github.com/psi-4ward/psitransfer
Language: JavaScript
Files changed: lib/endpoints.js
Commit date: 2024-03-17 18:40:50 +0100
Commit message: Restrict upload to a file which upload completed already

Diff: diff --git a/lib/endpoints.js b/lib/endpoints.js
index 945cbc4..93fb921 100644
--- a/lib/endpoints.js
+++ b/lib/endpoints.js
@@ -27,10 +27,11 @@ const uploadPage = pug.compileFile(path.join(__dirname, '../public/pug/upload.pu
 const downloadPage = pug.compileFile(path.join(__dirname, '../public/pug/download.pug'), { pretty: true });
 
 const store = new Store(config.uploadDir);
 const Db = require('./db');
 const { createGzip } = require("zlib");
+const httpErrors = require("http-errors");
 const db = new Db(config.uploadDir, store);
 db.init();
 const app = express();
 
 app.disable('x-powered-by');
@@ -313,11 +314,11 @@ app.get(`${ config.baseUrl }files/:fid`, async (req, res, next) => {
 });
 
 
 // Upload file
 app.use(`${ config.uploadAppPath }files`,
-  function(req, res, next) {
+  async function(req, res, next) {
     // Upload password protection
     if (config.uploadPass) {
       const bfTimeout = 500;
       if (!req.get('x-passwd')) {
         setTimeout(() => res.status(401).send('Unauthorized'), bfTimeout);
@@ -329,10 +330,26 @@ app.use(`${ config.uploadAppPath }files`,
       }
     }
 
     if (req.method === 'GET') return res.status(405).end();
 
+    // Restrict upload to a file which upload completed already
+    if(['POST', 'PATCH'].includes(req.method)) {
+      try {
+        const fid = req.url.substring(1);
+        const info = await store.info(fid);
+        if(!info.isPartial) {
+          return res.status(400).end('Upload already completed');
+        }
+      } catch(e) {
+        if(! e instanceof httpErrors.NotFound) {
+          console.error(e);
+          return;
+        }
+      }
+    }
+
     if (req.method === 'POST') {
       // validate meta-data
       // !! tusMeta.encode supports only strings !!
       const meta = tusMeta.decode(req.get('Upload-Metadata'));
 
@@ -385,11 +402,10 @@ app.use(`${ config.uploadAppPath }files`,
     getKey: req => req.FID,
     maxUploadLength: config.maxFileSize || Infinity,
     afterComplete: (req, upload, fid) => {
       db.add(upload.metadata.sid, upload.metadata.key, upload);
       debug(`Completed upload ${ fid }, size=${ upload.size } name=${ upload.metadata.name }`);
-
       eventBus.emit('fileUploaded', upload);
     },
   })
 );
 

Vulnerable code:   function(req, res, next) {

Fixed code: const httpErrors = require("http-errors");
  async function(req, res, next) {
    // Restrict upload to a file which upload completed already
    if(['POST', 'PATCH'].includes(req.method)) {
      try {
        const fid = req.url.substring(1);
        const info = await store.info(fid);
        if(!info.isPartial) {
          return res.status(400).end('Upload already completed');
        }
      } catch(e) {
        if(! e instanceof httpErrors.NotFound) {
          console.error(e);
          return;
        }
      }
    }