Skip to content

#6589 catch JSON.parse and return 403 properly #6614

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 30 additions & 17 deletions spec/Middlewares.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('middlewares', () => {
_ApplicationId: 'FakeAppId',
},
headers: {},
get: key => {
get: (key) => {
return fakeReq.headers[key.toLowerCase()];
},
};
Expand All @@ -24,7 +24,7 @@ describe('middlewares', () => {
AppCache.del(fakeReq.body._ApplicationId);
});

it('should use _ContentType if provided', done => {
it('should use _ContentType if provided', (done) => {
expect(fakeReq.headers['content-type']).toEqual(undefined);
const contentType = 'image/jpeg';
fakeReq.body._ContentType = contentType;
Expand Down Expand Up @@ -64,7 +64,7 @@ describe('middlewares', () => {
expect(fakeRes.status).toHaveBeenCalledWith(403);
});

it('should succeed when any one of the configured keys supplied', done => {
it('should succeed when any one of the configured keys supplied', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
clientKey: 'clientKey',
masterKey: 'masterKey',
Expand All @@ -77,7 +77,7 @@ describe('middlewares', () => {
});
});

it('should succeed when client key supplied but empty', done => {
it('should succeed when client key supplied but empty', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
clientKey: '',
masterKey: 'masterKey',
Expand All @@ -90,7 +90,7 @@ describe('middlewares', () => {
});
});

it('should succeed when no keys are configured and none supplied', done => {
it('should succeed when no keys are configured and none supplied', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
});
Expand All @@ -110,22 +110,22 @@ describe('middlewares', () => {

const BodyKeys = Object.keys(BodyParams);

BodyKeys.forEach(infoKey => {
BodyKeys.forEach((infoKey) => {
const bodyKey = BodyParams[infoKey];
const keyValue = 'Fake' + bodyKey;
// javascriptKey is the only one that gets defaulted,
const otherKeys = BodyKeys.filter(
otherKey => otherKey !== infoKey && otherKey !== 'javascriptKey'
(otherKey) => otherKey !== infoKey && otherKey !== 'javascriptKey'
);

it(`it should pull ${bodyKey} into req.info`, done => {
it(`it should pull ${bodyKey} into req.info`, (done) => {
fakeReq.body[bodyKey] = keyValue;

middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeReq.body[bodyKey]).toEqual(undefined);
expect(fakeReq.info[infoKey]).toEqual(keyValue);

otherKeys.forEach(otherKey => {
otherKeys.forEach((otherKey) => {
expect(fakeReq.info[otherKey]).toEqual(undefined);
});

Expand All @@ -145,7 +145,7 @@ describe('middlewares', () => {
expect(fakeRes.status).toHaveBeenCalledWith(403);
});

it('should succeed if the ip does belong to masterKeyIps list', done => {
it('should succeed if the ip does belong to masterKeyIps list', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1', 'ip2'],
Expand All @@ -169,7 +169,7 @@ describe('middlewares', () => {
expect(fakeRes.status).toHaveBeenCalledWith(403);
});

it('should succeed if the connection.remoteAddress does belong to masterKeyIps list', done => {
it('should succeed if the connection.remoteAddress does belong to masterKeyIps list', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1', 'ip2'],
Expand All @@ -193,7 +193,7 @@ describe('middlewares', () => {
expect(fakeRes.status).toHaveBeenCalledWith(403);
});

it('should succeed if the socket.remoteAddress does belong to masterKeyIps list', done => {
it('should succeed if the socket.remoteAddress does belong to masterKeyIps list', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1', 'ip2'],
Expand All @@ -217,7 +217,7 @@ describe('middlewares', () => {
expect(fakeRes.status).toHaveBeenCalledWith(403);
});

it('should succeed if the connection.socket.remoteAddress does belong to masterKeyIps list', done => {
it('should succeed if the connection.socket.remoteAddress does belong to masterKeyIps list', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1', 'ip2'],
Expand All @@ -230,7 +230,7 @@ describe('middlewares', () => {
});
});

it('should allow any ip to use masterKey if masterKeyIps is empty', done => {
it('should allow any ip to use masterKey if masterKeyIps is empty', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: [],
Expand All @@ -243,7 +243,7 @@ describe('middlewares', () => {
});
});

it('should succeed if xff header does belong to masterKeyIps', done => {
it('should succeed if xff header does belong to masterKeyIps', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1'],
Expand All @@ -256,7 +256,7 @@ describe('middlewares', () => {
});
});

it('should succeed if xff header with one ip does belong to masterKeyIps', done => {
it('should succeed if xff header with one ip does belong to masterKeyIps', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1'],
Expand Down Expand Up @@ -357,7 +357,7 @@ describe('middlewares', () => {
);
});

it('should use user provided on field userFromJWT', done => {
it('should use user provided on field userFromJWT', (done) => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
});
Expand All @@ -367,4 +367,17 @@ describe('middlewares', () => {
done();
});
});

it('should give invalid response when upload file without x-parse-application-id in header', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
});
fakeReq.body = Buffer.from('fake-file');
console.log('fakeReq.body.constructor');
console.log(fakeReq.body.constructor);
console.log('fakeReq.body instanceof Buffer');
console.log(fakeReq.body instanceof Buffer);
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
});
19 changes: 13 additions & 6 deletions src/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import defaultLogger from './logger';
export const DEFAULT_ALLOWED_HEADERS =
'X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, Content-Type, Pragma, Cache-Control';

const getMountForRequest = function(req) {
const getMountForRequest = function (req) {
const mountPathLength = req.originalUrl.length - req.url.length;
const mountPath = req.originalUrl.slice(0, mountPathLength);
return req.protocol + '://' + req.get('host') + mountPath;
Expand Down Expand Up @@ -59,7 +59,14 @@ export function handleParseHeaders(req, res, next) {
if (req.body instanceof Buffer) {
// The only chance to find the app id is if this is a file
// upload that actually is a JSON body. So try to parse it.
req.body = JSON.parse(req.body);
// https://github.com/parse-community/parse-server/issues/6589
// It is also possible that the client is trying to upload a file but forgot
// to provide x-parse-app-id in header and parse a binary file will fail
try {
req.body = JSON.parse(req.body);
} catch (e) {
return invalidRequest(req, res);
}
fileViaJSON = true;
}

Expand Down Expand Up @@ -168,10 +175,10 @@ export function handleParseHeaders(req, res, next) {
// Client keys are not required in parse-server, but if any have been configured in the server, validate them
// to preserve original behavior.
const keys = ['clientKey', 'javascriptKey', 'dotNetKey', 'restAPIKey'];
const oneKeyConfigured = keys.some(function(key) {
const oneKeyConfigured = keys.some(function (key) {
return req.config[key] !== undefined;
});
const oneKeyMatches = keys.some(function(key) {
const oneKeyMatches = keys.some(function (key) {
return req.config[key] !== undefined && info[key] === req.config[key];
});

Expand Down Expand Up @@ -225,13 +232,13 @@ export function handleParseHeaders(req, res, next) {
});
}
})
.then(auth => {
.then((auth) => {
if (auth) {
req.auth = auth;
next();
}
})
.catch(error => {
.catch((error) => {
if (error instanceof Parse.Error) {
next(error);
return;
Expand Down