VYPR
High severity7.5OSV Advisory· Published Nov 7, 2025· Updated Apr 15, 2026

CVE-2025-64430

CVE-2025-64430

Description

Parse Server is an open source backend that can be deployed to any infrastructure that can run Node.js. In versions 4.2.0 through 7.5.3, and 8.0.0 through 8.3.1-alpha.1, there is a Server-Side Request Forgery (SSRF) vulnerability in the file upload functionality when trying to upload a Parse.File with uri parameter, allowing execution of an arbitrary URI. The vulnerability stems from a file upload feature in which Parse Server retrieves the file data from a URI that is provided in the request. A request to the provided URI is executed, but the response is not stored in Parse Server's file storage as the server crashes upon receiving the response. This issue is fixed in versions 7.5.4 and 8.4.0-alpha.1.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
parse-servernpm
>= 4.2.0, < 7.5.47.5.4
parse-servernpm
>= 8.0.0, < 8.4.0-alpha.28.4.0-alpha.2

Affected products

1

Patches

2
8bbe3efbcf4a

fix: Uploading a file by providing an origin URL allows for Server-Side Request Forgery (SSRF); fixes vulnerability [GHSA-x4qj-2f4q-r4rx](https://github.com/parse-community/parse-server/security/advisories/GHSA-x4qj-2f4q-r4rx) (#9904)

2 files changed · +74 28
  • spec/ParseFile.spec.js+74 0 modified
    @@ -633,6 +633,80 @@ describe('Parse.File testing', () => {
             done();
           });
         });
    +
    +    describe('URI-backed file upload is disabled to prevent SSRF attack', () => {
    +      const express = require('express');
    +      let testServer;
    +      let testServerPort;
    +      let requestsMade;
    +
    +      beforeEach(async () => {
    +        requestsMade = [];
    +        const app = express();
    +        app.use((req, res) => {
    +          requestsMade.push({ url: req.url, method: req.method });
    +          res.status(200).send('test file content');
    +        });
    +        testServer = app.listen(0);
    +        testServerPort = testServer.address().port;
    +      });
    +
    +      afterEach(async () => {
    +        if (testServer) {
    +          await new Promise(resolve => testServer.close(resolve));
    +        }
    +        Parse.Cloud._removeAllHooks();
    +      });
    +
    +      it('does not access URI when file upload attempted over REST', async () => {
    +        const response = await request({
    +          method: 'POST',
    +          url: 'http://localhost:8378/1/classes/TestClass',
    +          headers: {
    +            'Content-Type': 'application/json',
    +            'X-Parse-Application-Id': 'test',
    +            'X-Parse-REST-API-Key': 'rest',
    +          },
    +          body: {
    +            file: {
    +              __type: 'File',
    +              name: 'test.txt',
    +              _source: {
    +                format: 'uri',
    +                uri: `http://127.0.0.1:${testServerPort}/secret-file.txt`,
    +              },
    +            },
    +          },
    +        });
    +        expect(response.status).toBe(201);
    +        // Verify no HTTP request was made to the URI
    +        expect(requestsMade.length).toBe(0);
    +      });
    +
    +      it('does not access URI when file created in beforeSave trigger', async () => {
    +        Parse.Cloud.beforeSave(Parse.File, () => {
    +          return new Parse.File('trigger-file.txt', {
    +            uri: `http://127.0.0.1:${testServerPort}/secret-file.txt`,
    +          });
    +        });
    +        await expectAsync(
    +          request({
    +            method: 'POST',
    +            headers: {
    +              'Content-Type': 'application/octet-stream',
    +              'X-Parse-Application-Id': 'test',
    +              'X-Parse-REST-API-Key': 'rest',
    +            },
    +            url: 'http://localhost:8378/1/files/test.txt',
    +            body: 'test content',
    +          })
    +        ).toBeRejectedWith(jasmine.objectContaining({
    +          status: 400
    +        }));
    +        // Verify no HTTP request was made to the URI
    +        expect(requestsMade.length).toBe(0);
    +      });
    +    });
       });
     
       describe('deleting files', () => {
    
  • src/Routers/FilesRouter.js+0 28 modified
    @@ -5,34 +5,8 @@ import Parse from 'parse/node';
     import Config from '../Config';
     import logger from '../logger';
     const triggers = require('../triggers');
    -const http = require('http');
     const Utils = require('../Utils');
     
    -const downloadFileFromURI = uri => {
    -  return new Promise((res, rej) => {
    -    http
    -      .get(uri, response => {
    -        response.setDefaultEncoding('base64');
    -        let body = `data:${response.headers['content-type']};base64,`;
    -        response.on('data', data => (body += data));
    -        response.on('end', () => res(body));
    -      })
    -      .on('error', e => {
    -        rej(`Error downloading file from ${uri}: ${e.message}`);
    -      });
    -  });
    -};
    -
    -const addFileDataIfNeeded = async file => {
    -  if (file._source.format === 'uri') {
    -    const base64 = await downloadFileFromURI(file._source.uri);
    -    file._previousSave = file;
    -    file._data = base64;
    -    file._requestTask = null;
    -  }
    -  return file;
    -};
    -
     export class FilesRouter {
       expressRouter({ maxUploadSize = '20Mb' } = {}) {
         var router = express.Router();
    @@ -210,8 +184,6 @@ export class FilesRouter {
           }
           // if the file returned by the trigger has already been saved skip saving anything
           if (!saveResult) {
    -        // if the ParseFile returned is type uri, download the file before saving it
    -        await addFileDataIfNeeded(fileObject.file);
             // update fileSize
             const bufferData = Buffer.from(fileObject.file._data, 'base64');
             fileObject.fileSize = Buffer.byteLength(bufferData);
    
97763863b726

fix: Uploading a file by providing an origin URL allows for Server-Side Request Forgery (SSRF); fixes vulnerability [GHSA-x4qj-2f4q-r4rx](https://github.com/parse-community/parse-server/security/advisories/GHSA-x4qj-2f4q-r4rx) (#9903)

2 files changed · +74 28
  • spec/ParseFile.spec.js+74 0 modified
    @@ -653,6 +653,80 @@ describe('Parse.File testing', () => {
             done();
           });
         });
    +
    +    describe('URI-backed file upload is disabled to prevent SSRF attack', () => {
    +      const express = require('express');
    +      let testServer;
    +      let testServerPort;
    +      let requestsMade;
    +
    +      beforeEach(async () => {
    +        requestsMade = [];
    +        const app = express();
    +        app.use((req, res) => {
    +          requestsMade.push({ url: req.url, method: req.method });
    +          res.status(200).send('test file content');
    +        });
    +        testServer = app.listen(0);
    +        testServerPort = testServer.address().port;
    +      });
    +
    +      afterEach(async () => {
    +        if (testServer) {
    +          await new Promise(resolve => testServer.close(resolve));
    +        }
    +        Parse.Cloud._removeAllHooks();
    +      });
    +
    +      it('does not access URI when file upload attempted over REST', async () => {
    +        const response = await request({
    +          method: 'POST',
    +          url: 'http://localhost:8378/1/classes/TestClass',
    +          headers: {
    +            'Content-Type': 'application/json',
    +            'X-Parse-Application-Id': 'test',
    +            'X-Parse-REST-API-Key': 'rest',
    +          },
    +          body: {
    +            file: {
    +              __type: 'File',
    +              name: 'test.txt',
    +              _source: {
    +                format: 'uri',
    +                uri: `http://127.0.0.1:${testServerPort}/secret-file.txt`,
    +              },
    +            },
    +          },
    +        });
    +        expect(response.status).toBe(201);
    +        // Verify no HTTP request was made to the URI
    +        expect(requestsMade.length).toBe(0);
    +      });
    +
    +      it('does not access URI when file created in beforeSave trigger', async () => {
    +        Parse.Cloud.beforeSave(Parse.File, () => {
    +          return new Parse.File('trigger-file.txt', {
    +            uri: `http://127.0.0.1:${testServerPort}/secret-file.txt`,
    +          });
    +        });
    +        await expectAsync(
    +          request({
    +            method: 'POST',
    +            headers: {
    +              'Content-Type': 'application/octet-stream',
    +              'X-Parse-Application-Id': 'test',
    +              'X-Parse-REST-API-Key': 'rest',
    +            },
    +            url: 'http://localhost:8378/1/files/test.txt',
    +            body: 'test content',
    +          })
    +        ).toBeRejectedWith(jasmine.objectContaining({
    +          status: 400
    +        }));
    +        // Verify no HTTP request was made to the URI
    +        expect(requestsMade.length).toBe(0);
    +      });
    +    });
       });
     
       describe('deleting files', () => {
    
  • src/Routers/FilesRouter.js+0 28 modified
    @@ -4,34 +4,8 @@ import Parse from 'parse/node';
     import Config from '../Config';
     import logger from '../logger';
     const triggers = require('../triggers');
    -const http = require('http');
     const Utils = require('../Utils');
     
    -const downloadFileFromURI = uri => {
    -  return new Promise((res, rej) => {
    -    http
    -      .get(uri, response => {
    -        response.setDefaultEncoding('base64');
    -        let body = `data:${response.headers['content-type']};base64,`;
    -        response.on('data', data => (body += data));
    -        response.on('end', () => res(body));
    -      })
    -      .on('error', e => {
    -        rej(`Error downloading file from ${uri}: ${e.message}`);
    -      });
    -  });
    -};
    -
    -const addFileDataIfNeeded = async file => {
    -  if (file._source.format === 'uri') {
    -    const base64 = await downloadFileFromURI(file._source.uri);
    -    file._previousSave = file;
    -    file._data = base64;
    -    file._requestTask = null;
    -  }
    -  return file;
    -};
    -
     export class FilesRouter {
       expressRouter({ maxUploadSize = '20Mb' } = {}) {
         var router = express.Router();
    @@ -247,8 +221,6 @@ export class FilesRouter {
           }
           // if the file returned by the trigger has already been saved skip saving anything
           if (!saveResult) {
    -        // if the ParseFile returned is type uri, download the file before saving it
    -        await addFileDataIfNeeded(fileObject.file);
             // update fileSize
             const bufferData = Buffer.from(fileObject.file._data, 'base64');
             fileObject.fileSize = Buffer.byteLength(bufferData);
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.