Skip to content

Commit af82f0a

Browse files
mtrezzadblythy
authored andcommitted
fix: The client IP address may be determined incorrectly in some cases; this fixes a security vulnerability in which the Parse Server option masterKeyIps may be circumvented, see [GHSA-vm5r-c87r-pf6x](GHSA-vm5r-c87r-pf6x) (parse-community#8372)
BREAKING CHANGE: The mechanism to determine the client IP address has been rewritten; to correctly determine the IP address it is now required to set the Parse Server option `trustProxy` accordingly if Parse Server runs behind a proxy server, see the express framework's [trust proxy](https://expressjs.com/en/guide/behind-proxies.html) setting (parse-community#8372)
1 parent e402ff6 commit af82f0a

File tree

2 files changed

+4
-109
lines changed

2 files changed

+4
-109
lines changed

spec/Middlewares.spec.js

Lines changed: 3 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -173,72 +173,6 @@ describe('middlewares', () => {
173173
expect(fakeReq.auth.isMaster).toBe(true);
174174
});
175175

176-
it('should not succeed if the connection.remoteAddress does not belong to masterKeyIps list', async () => {
177-
AppCache.put(fakeReq.body._ApplicationId, {
178-
masterKey: 'masterKey',
179-
masterKeyIps: ['10.0.0.1', '10.0.0.2'],
180-
});
181-
fakeReq.connection = { remoteAddress: '127.0.0.1' };
182-
fakeReq.headers['x-parse-master-key'] = 'masterKey';
183-
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
184-
expect(fakeReq.auth.isMaster).toBe(false);
185-
});
186-
187-
it('should succeed if the connection.remoteAddress does belong to masterKeyIps list', async () => {
188-
AppCache.put(fakeReq.body._ApplicationId, {
189-
masterKey: 'masterKey',
190-
masterKeyIps: ['10.0.0.1', '10.0.0.2'],
191-
});
192-
fakeReq.connection = { remoteAddress: '10.0.0.1' };
193-
fakeReq.headers['x-parse-master-key'] = 'masterKey';
194-
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
195-
expect(fakeReq.auth.isMaster).toBe(true);
196-
});
197-
198-
it('should not succeed if the socket.remoteAddress does not belong to masterKeyIps list', async () => {
199-
AppCache.put(fakeReq.body._ApplicationId, {
200-
masterKey: 'masterKey',
201-
masterKeyIps: ['10.0.0.1', '10.0.0.2'],
202-
});
203-
fakeReq.socket = { remoteAddress: '127.0.0.1' };
204-
fakeReq.headers['x-parse-master-key'] = 'masterKey';
205-
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
206-
expect(fakeReq.auth.isMaster).toBe(false);
207-
});
208-
209-
it('should succeed if the socket.remoteAddress does belong to masterKeyIps list', async () => {
210-
AppCache.put(fakeReq.body._ApplicationId, {
211-
masterKey: 'masterKey',
212-
masterKeyIps: ['10.0.0.1', '10.0.0.2'],
213-
});
214-
fakeReq.socket = { remoteAddress: '10.0.0.1' };
215-
fakeReq.headers['x-parse-master-key'] = 'masterKey';
216-
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
217-
expect(fakeReq.auth.isMaster).toBe(true);
218-
});
219-
220-
it('should not succeed if the connection.socket.remoteAddress does not belong to masterKeyIps list', async () => {
221-
AppCache.put(fakeReq.body._ApplicationId, {
222-
masterKey: 'masterKey',
223-
masterKeyIps: ['10.0.0.1', '10.0.0.2'],
224-
});
225-
fakeReq.connection = { socket: { remoteAddress: 'ip3' } };
226-
fakeReq.headers['x-parse-master-key'] = 'masterKey';
227-
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
228-
expect(fakeReq.auth.isMaster).toBe(false);
229-
});
230-
231-
it('should succeed if the connection.socket.remoteAddress does belong to masterKeyIps list', async () => {
232-
AppCache.put(fakeReq.body._ApplicationId, {
233-
masterKey: 'masterKey',
234-
masterKeyIps: ['10.0.0.1', '10.0.0.2'],
235-
});
236-
fakeReq.connection = { socket: { remoteAddress: '10.0.0.1' } };
237-
fakeReq.headers['x-parse-master-key'] = 'masterKey';
238-
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
239-
expect(fakeReq.auth.isMaster).toBe(true);
240-
});
241-
242176
it('should allow any ip to use masterKey if masterKeyIps is empty', async () => {
243177
AppCache.put(fakeReq.body._ApplicationId, {
244178
masterKey: 'masterKey',
@@ -250,48 +184,9 @@ describe('middlewares', () => {
250184
expect(fakeReq.auth.isMaster).toBe(true);
251185
});
252186

253-
it('should succeed if xff header does belong to masterKeyIps', async () => {
254-
AppCache.put(fakeReq.body._ApplicationId, {
255-
masterKey: 'masterKey',
256-
masterKeyIps: ['10.0.0.1'],
257-
});
258-
fakeReq.headers['x-parse-master-key'] = 'masterKey';
259-
fakeReq.headers['x-forwarded-for'] = '10.0.0.1, 10.0.0.2, ip3';
260-
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
261-
expect(fakeReq.auth.isMaster).toBe(true);
262-
});
263-
264-
it('should succeed if xff header with one ip does belong to masterKeyIps', async () => {
265-
AppCache.put(fakeReq.body._ApplicationId, {
266-
masterKey: 'masterKey',
267-
masterKeyIps: ['10.0.0.1'],
268-
});
269-
fakeReq.headers['x-parse-master-key'] = 'masterKey';
270-
fakeReq.headers['x-forwarded-for'] = '10.0.0.1';
271-
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
272-
expect(fakeReq.auth.isMaster).toBe(true);
273-
});
274-
275-
it('should not succeed if xff header does not belong to masterKeyIps', async () => {
276-
AppCache.put(fakeReq.body._ApplicationId, {
277-
masterKey: 'masterKey',
278-
masterKeyIps: ['ip4'],
279-
});
280-
fakeReq.headers['x-parse-master-key'] = 'masterKey';
281-
fakeReq.headers['x-forwarded-for'] = '10.0.0.1, 10.0.0.2, ip3';
282-
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
283-
expect(fakeReq.auth.isMaster).toBe(false);
284-
});
285-
286-
it('should not succeed if xff header is empty and masterKeyIps is set', async () => {
287-
AppCache.put(fakeReq.body._ApplicationId, {
288-
masterKey: 'masterKey',
289-
masterKeyIps: ['10.0.0.1'],
290-
});
291-
fakeReq.headers['x-parse-master-key'] = 'masterKey';
292-
fakeReq.headers['x-forwarded-for'] = '';
293-
await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve));
294-
expect(fakeReq.auth.isMaster).toBe(false);
187+
it('can set trust proxy', async () => {
188+
const server = await reconfigureServer({ trustProxy: 1 });
189+
expect(server.app.parent.settings['trust proxy']).toBe(1);
295190
});
296191

297192
it('should properly expose the headers', () => {

src/cloud-code/Parse.Cloud.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ module.exports = ParseCloud;
722722
* @property {Boolean} isChallenge If true, means the current request is originally triggered by an auth challenge.
723723
* @property {Parse.User} user If set, the user that made the request.
724724
* @property {Parse.Object} object The object triggering the hook.
725-
* @property {String} ip The IP address of the client making the request.
725+
* @property {String} ip The IP address of the client making the request. To ensure retrieving the correct IP address, set the Parse Server option `trustProxy: true` if Parse Server runs behind a proxy server, for example behind a load balancer.
726726
* @property {Object} headers The original HTTP headers for the request.
727727
* @property {String} triggerName The name of the trigger (`beforeSave`, `afterSave`, ...)
728728
* @property {Object} log The current logger inside Parse Server.

0 commit comments

Comments
 (0)