mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
fix: Validate body field types in request middleware (#10209)
This commit is contained in:
@@ -430,6 +430,103 @@ describe('middlewares', () => {
|
||||
expect(middlewares.checkIp(localhostV62, ['127.0.0.1'], new Map())).toBe(true);
|
||||
});
|
||||
|
||||
describe('body field type validation', () => {
|
||||
beforeEach(() => {
|
||||
AppCachePut(fakeReq.body._ApplicationId, {
|
||||
masterKeyIps: ['0.0.0.0/0'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject non-string _SessionToken in body', async () => {
|
||||
fakeReq.body._SessionToken = { toString: 'evil' };
|
||||
await middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should reject non-string _ClientVersion in body', async () => {
|
||||
fakeReq.body._ClientVersion = { toLowerCase: 'evil' };
|
||||
await middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should reject non-string _InstallationId in body', async () => {
|
||||
fakeReq.body._InstallationId = { toString: 'evil' };
|
||||
await middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should reject non-string _ContentType in body', async () => {
|
||||
fakeReq.body._ContentType = { toString: 'evil' };
|
||||
await middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should reject non-string base64 in file-via-JSON upload', async () => {
|
||||
fakeReq.body = Buffer.from(
|
||||
JSON.stringify({
|
||||
_ApplicationId: 'FakeAppId',
|
||||
base64: { toString: 'evil' },
|
||||
})
|
||||
);
|
||||
await middlewares.handleParseHeaders(fakeReq, fakeRes);
|
||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||
});
|
||||
|
||||
it('should not crash the server process on non-string body fields', async () => {
|
||||
// Verify that type confusion in body fields does not crash the Node.js process.
|
||||
// Each request should be handled independently without affecting server stability.
|
||||
const payloads = [
|
||||
{ _SessionToken: { toString: 'evil' } },
|
||||
{ _ClientVersion: { toLowerCase: 'evil' } },
|
||||
{ _InstallationId: [1, 2, 3] },
|
||||
{ _ContentType: { toString: 'evil' } },
|
||||
];
|
||||
for (const payload of payloads) {
|
||||
const req = {
|
||||
ip: '127.0.0.1',
|
||||
originalUrl: 'http://example.com/parse/',
|
||||
url: 'http://example.com/',
|
||||
body: { _ApplicationId: 'FakeAppId', ...payload },
|
||||
headers: {},
|
||||
get: key => req.headers[key.toLowerCase()],
|
||||
};
|
||||
const res = jasmine.createSpyObj('res', ['end', 'status']);
|
||||
await middlewares.handleParseHeaders(req, res);
|
||||
expect(res.status).toHaveBeenCalledWith(403);
|
||||
}
|
||||
// Server process is still alive — a subsequent valid request works
|
||||
const validReq = {
|
||||
ip: '127.0.0.1',
|
||||
originalUrl: 'http://example.com/parse/',
|
||||
url: 'http://example.com/',
|
||||
body: { _ApplicationId: 'FakeAppId' },
|
||||
headers: {},
|
||||
get: key => validReq.headers[key.toLowerCase()],
|
||||
};
|
||||
const validRes = jasmine.createSpyObj('validRes', ['end', 'status']);
|
||||
let nextCalled = false;
|
||||
await middlewares.handleParseHeaders(validReq, validRes, () => {
|
||||
nextCalled = true;
|
||||
});
|
||||
expect(nextCalled).toBe(true);
|
||||
expect(validRes.status).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should still accept valid string body fields', done => {
|
||||
fakeReq.body._SessionToken = 'r:validtoken';
|
||||
fakeReq.body._ClientVersion = 'js1.0.0';
|
||||
fakeReq.body._InstallationId = 'install123';
|
||||
fakeReq.body._ContentType = 'application/json';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeReq.info.sessionToken).toEqual('r:validtoken');
|
||||
expect(fakeReq.info.clientVersion).toEqual('js1.0.0');
|
||||
expect(fakeReq.info.installationId).toEqual('install123');
|
||||
expect(fakeReq.headers['content-type']).toEqual('application/json');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should match address with cache', () => {
|
||||
const ipv6 = '2001:0db8:85a3:0000:0000:8a2e:0370:7334';
|
||||
const cache1 = new Map();
|
||||
|
||||
@@ -65,8 +65,7 @@ describe('Regex Vulnerabilities', () => {
|
||||
});
|
||||
fail('should not work');
|
||||
} catch (e) {
|
||||
expect(e.data.code).toEqual(209);
|
||||
expect(e.data.error).toEqual('Invalid session token');
|
||||
expect(e.data.error).toEqual('unauthorized');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+20
-2
@@ -150,18 +150,30 @@ export async function handleParseHeaders(req, res, next) {
|
||||
// TODO: test that the REST API formats generated by the other
|
||||
// SDKs are handled ok
|
||||
if (req.body._ClientVersion) {
|
||||
if (typeof req.body._ClientVersion !== 'string') {
|
||||
return invalidRequest(req, res);
|
||||
}
|
||||
info.clientVersion = req.body._ClientVersion;
|
||||
delete req.body._ClientVersion;
|
||||
}
|
||||
if (req.body._InstallationId) {
|
||||
if (typeof req.body._InstallationId !== 'string') {
|
||||
return invalidRequest(req, res);
|
||||
}
|
||||
info.installationId = req.body._InstallationId;
|
||||
delete req.body._InstallationId;
|
||||
}
|
||||
if (req.body._SessionToken) {
|
||||
if (typeof req.body._SessionToken !== 'string') {
|
||||
return invalidRequest(req, res);
|
||||
}
|
||||
info.sessionToken = req.body._SessionToken;
|
||||
delete req.body._SessionToken;
|
||||
}
|
||||
if (req.body._MasterKey) {
|
||||
if (typeof req.body._MasterKey !== 'string') {
|
||||
return invalidRequest(req, res);
|
||||
}
|
||||
info.masterKey = req.body._MasterKey;
|
||||
delete req.body._MasterKey;
|
||||
}
|
||||
@@ -181,6 +193,9 @@ export async function handleParseHeaders(req, res, next) {
|
||||
delete req.body._context;
|
||||
}
|
||||
if (req.body._ContentType) {
|
||||
if (typeof req.body._ContentType !== 'string') {
|
||||
return invalidRequest(req, res);
|
||||
}
|
||||
req.headers['content-type'] = req.body._ContentType;
|
||||
delete req.body._ContentType;
|
||||
}
|
||||
@@ -190,14 +205,17 @@ export async function handleParseHeaders(req, res, next) {
|
||||
}
|
||||
|
||||
if (info.sessionToken && typeof info.sessionToken !== 'string') {
|
||||
info.sessionToken = info.sessionToken.toString();
|
||||
return invalidRequest(req, res);
|
||||
}
|
||||
|
||||
if (info.clientVersion) {
|
||||
if (info.clientVersion && typeof info.clientVersion === 'string') {
|
||||
info.clientSDK = ClientSDK.fromString(info.clientVersion);
|
||||
}
|
||||
|
||||
if (fileViaJSON && req.body) {
|
||||
if (req.body.base64 && typeof req.body.base64 !== 'string') {
|
||||
return invalidRequest(req, res);
|
||||
}
|
||||
req.fileData = req.body.fileData;
|
||||
// We need to repopulate req.body with a buffer
|
||||
var base64 = req.body.base64;
|
||||
|
||||
Reference in New Issue
Block a user