Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
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
97 changes: 97 additions & 0 deletions 97 spec/Middlewares.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
3 changes: 1 addition & 2 deletions 3 spec/RegexVulnerabilities.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
});

Expand Down
22 changes: 20 additions & 2 deletions 22 src/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.