CVE-2022-31089: Unchecked Return Value (CWE-252)
value: Parse Server is an open source backend that can be deployed to any infrastructure that can run Node.js. In affected versions certain types of invalid files requests are not handled properly and can crash the server. If you are running multiple Parse Server instances in a cluster, the availability impact may be low; if you are running Parse Server as single instance without redundancy, the availability impact may be high. This issue has been addressed in versions 4.10.12 and 5.2.3. Users are advised to upgrade. There are no known workarounds for this issue.
CWE description: The product does not check the return value from a method or function, which can prevent it from detecting unexpected states and conditions.
CVSS v3 base score: 7.5
Published: 2022-06-27T21:15Z
Repository: https://github.com/parse-community/parse-server
Language: JavaScript
Files changed: spec/ParseFile.spec.js, src/Routers/FilesRouter.js
Commit date: 2022-06-18 01:33:19 +0200
Commit message: fix: invalid file request not properly handled; this fixes a security vulnerability in which an invalid file request can crash the server ([GHSA-xw6g-jjvf-wwf9](https://github.com/parse-community/parse-server/security/advisories/GHSA-xw6g-jjvf-wwf9)) (#8060)


Diff: diff --git a/spec/ParseFile.spec.js b/spec/ParseFile.spec.js
index af1de35ee..281ce45cc 100644
--- a/spec/ParseFile.spec.js
+++ b/spec/ParseFile.spec.js
@@ -652,10 +652,48 @@ describe('Parse.File testing', () => {
         done();
       });
     });
   });
 
+  describe('getting files', () => {
+    it('does not crash on file request with invalid app ID', async () => {
+      const res1 = await request({
+        url: 'http://localhost:8378/1/files/invalid-id/invalid-file.txt',
+      }).catch(e => e);
+      expect(res1.status).toBe(403);
+      expect(res1.data).toEqual({ code: 119, error: 'Invalid application ID.' });
+      // Ensure server did not crash
+      const res2 = await request({ url: 'http://localhost:8378/1/health' });
+      expect(res2.status).toEqual(200);
+      expect(res2.data).toEqual({ status: 'ok' });
+    });
+
+    it('does not crash on file request with invalid path', async () => {
+      const res1 = await request({
+        url: 'http://localhost:8378/1/files/invalid-id//invalid-path/%20/invalid-file.txt',
+      }).catch(e => e);
+      expect(res1.status).toBe(403);
+      expect(res1.data).toEqual({ error: 'unauthorized' });
+      // Ensure server did not crash
+      const res2 = await request({ url: 'http://localhost:8378/1/health' });
+      expect(res2.status).toEqual(200);
+      expect(res2.data).toEqual({ status: 'ok' });
+    });
+
+    it('does not crash on file metadata request with invalid app ID', async () => {
+      const res1 = await request({
+        url: `http://localhost:8378/1/files/invalid-id/metadata/invalid-file.txt`,
+      });
+      expect(res1.status).toBe(200);
+      expect(res1.data).toEqual({});
+      // Ensure server did not crash
+      const res2 = await request({ url: 'http://localhost:8378/1/health' });
+      expect(res2.status).toEqual(200);
+      expect(res2.data).toEqual({ status: 'ok' });
+    });
+  });
+
   xdescribe('Gridstore Range tests', () => {
     it('supports range requests', done => {
       const headers = {
         'Content-Type': 'application/octet-stream',
         'X-Parse-Application-Id': 'test',
diff --git a/src/Routers/FilesRouter.js b/src/Routers/FilesRouter.js
index c0c7e00f1..bdd86ae59 100644
--- a/src/Routers/FilesRouter.js
+++ b/src/Routers/FilesRouter.js
@@ -64,10 +64,16 @@ export class FilesRouter {
     return router;
   }
 
   getHandler(req, res) {
     const config = Config.get(req.params.appId);
+    if (!config) {
+      res.status(403);
+      const err = new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid application ID.');
+      res.json({ code: err.code, error: err.message });
+      return;
+    }
     const filesController = config.filesController;
     const filename = req.params.filename;
     const contentType = mime.getType(filename);
     if (isFileStreamable(req, filesController)) {
       filesController.handleFileStream(config, filename, req, res, contentType).catch(() => {
@@ -248,14 +254,14 @@ export class FilesRouter {
       next(error);
     }
   }
 
   async metadataHandler(req, res) {
-    const config = Config.get(req.params.appId);
-    const { filesController } = config;
-    const { filename } = req.params;
     try {
+      const config = Config.get(req.params.appId);
+      const { filesController } = config;
+      const { filename } = req.params;
       const data = await filesController.getMetadata(filename);
       res.status(200);
       res.json(data);
     } catch (e) {
       res.status(200);

Vulnerable code:     const config = Config.get(req.params.appId);
    const { filesController } = config;
    const { filename } = req.params;
Fixed code:   describe('getting files', () => {
    it('does not crash on file request with invalid app ID', async () => {
      const res1 = await request({
        url: 'http://localhost:8378/1/files/invalid-id/invalid-file.txt',
      }).catch(e => e);
      expect(res1.status).toBe(403);
      expect(res1.data).toEqual({ code: 119, error: 'Invalid application ID.' });
      // Ensure server did not crash
      const res2 = await request({ url: 'http://localhost:8378/1/health' });
      expect(res2.status).toEqual(200);
      expect(res2.data).toEqual({ status: 'ok' });
    });

    it('does not crash on file request with invalid path', async () => {
      const res1 = await request({
        url: 'http://localhost:8378/1/files/invalid-id//invalid-path/%20/invalid-file.txt',
      }).catch(e => e);
      expect(res1.status).toBe(403);
      expect(res1.data).toEqual({ error: 'unauthorized' });
      // Ensure server did not crash
      const res2 = await request({ url: 'http://localhost:8378/1/health' });
      expect(res2.status).toEqual(200);
      expect(res2.data).toEqual({ status: 'ok' });
    });

    it('does not crash on file metadata request with invalid app ID', async () => {
      const res1 = await request({
        url: `http://localhost:8378/1/files/invalid-id/metadata/invalid-file.txt`,
      });
      expect(res1.status).toBe(200);
      expect(res1.data).toEqual({});
      // Ensure server did not crash
      const res2 = await request({ url: 'http://localhost:8378/1/health' });
      expect(res2.status).toEqual(200);
      expect(res2.data).toEqual({ status: 'ok' });
    });
  });

    if (!config) {
      res.status(403);
      const err = new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid application ID.');
      res.json({ code: err.code, error: err.message });
      return;
    }
      const config = Config.get(req.params.appId);
      const { filesController } = config;
      const { filename } = req.params;