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.
| Package | Affected versions | Patched versions |
|---|---|---|
parse-servernpm | >= 4.2.0, < 7.5.4 | 7.5.4 |
parse-servernpm | >= 8.0.0, < 8.4.0-alpha.2 | 8.4.0-alpha.2 |
Affected products
1- Range: 4.2.0, 4.3.0, 4.4.0, …
Patches
28bbe3efbcf4afix: 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);
97763863b726fix: 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- github.com/advisories/GHSA-x4qj-2f4q-r4rxghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-64430ghsaADVISORY
- github.com/parse-community/parse-server/commit/8bbe3efbcf4a3b66f4a8db9bfb18cd98c050db51nvdWEB
- github.com/parse-community/parse-server/commit/97763863b72689a29ad7a311dfb590c3e3c50585nvdWEB
- github.com/parse-community/parse-server/pull/9903nvdWEB
- github.com/parse-community/parse-server/pull/9904nvdWEB
- github.com/parse-community/parse-server/security/advisories/GHSA-x4qj-2f4q-r4rxnvdWEB
News mentions
0No linked articles in our index yet.