Skip to content

Commit

Permalink
Merge pull request from GHSA-h4mf-75hf-67w4
Browse files Browse the repository at this point in the history
* Fix session token issue

* verify email problem

* Fix password reset problem

* Change test file name

* Split tests

* Refetch user

* Replaces lets to consts

* Refactor unit test

What you have is just finee, but wanted
to show you what I meant with my comment

Use jasmine's this to set stuff in beforeEach's

Not that all functions need to be `function ()` instead of
`() =>` so `this` is preserved.

see: https://jasmine.github.io/tutorials/your_first_suite#section-The_%3Ccode%3Ethis%3C/code%3E_keyword

Co-authored-by: Antonio Davi Macedo Coelho de Castro <adavimacedo@gmail.com>
  • Loading branch information
acinader and davimacedo authored Mar 2, 2020
1 parent bde8ab6 commit 3a3a5ee
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 3 deletions.
198 changes: 198 additions & 0 deletions spec/RegexVulnerabilities.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
const request = require('../lib/request');

const serverURL = 'http://localhost:8378/1';
const headers = {
'Content-Type': 'application/json',
};
const keys = {
_ApplicationId: 'test',
_JavaScriptKey: 'test',
};
const emailAdapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {},
};
const appName = 'test';
const publicServerURL = 'http://localhost:8378/1';

describe('Regex Vulnerabilities', function() {
beforeEach(async function() {
await reconfigureServer({
verifyUserEmails: true,
emailAdapter,
appName,
publicServerURL,
});

const signUpResponse = await request({
url: `${serverURL}/users`,
method: 'POST',
headers,
body: JSON.stringify({
...keys,
_method: 'POST',
username: 'someemail@somedomain.com',
password: 'somepassword',
email: 'someemail@somedomain.com',
}),
});
this.objectId = signUpResponse.data.objectId;
this.sessionToken = signUpResponse.data.sessionToken;
this.partialSessionToken = this.sessionToken.slice(0, 3);
});

describe('on session token', function() {
it('should not work with regex', async function() {
try {
await request({
url: `${serverURL}/users/me`,
method: 'POST',
headers,
body: JSON.stringify({
...keys,
_SessionToken: {
$regex: this.partialSessionToken,
},
_method: 'GET',
}),
});
fail('should not work');
} catch (e) {
expect(e.data.code).toEqual(209);
expect(e.data.error).toEqual('Invalid session token');
}
});

it('should work with plain token', async function() {
const meResponse = await request({
url: `${serverURL}/users/me`,
method: 'POST',
headers,
body: JSON.stringify({
...keys,
_SessionToken: this.sessionToken,
_method: 'GET',
}),
});
expect(meResponse.data.objectId).toEqual(this.objectId);
expect(meResponse.data.sessionToken).toEqual(this.sessionToken);
});
});

describe('on verify e-mail', function() {
beforeEach(async function() {
const userQuery = new Parse.Query(Parse.User);
this.user = await userQuery.get(this.objectId, { useMasterKey: true });
});

it('should not work with regex', async function() {
expect(this.user.get('emailVerified')).toEqual(false);
await request({
url: `${serverURL}/apps/test/verify_email?username=someemail@somedomain.com&token[$regex]=`,
method: 'GET',
});
await this.user.fetch({ useMasterKey: true });
expect(this.user.get('emailVerified')).toEqual(false);
});

it('should work with plain token', async function() {
expect(this.user.get('emailVerified')).toEqual(false);
// It should work
await request({
url: `${serverURL}/apps/test/verify_email?username=someemail@somedomain.com&token=${this.user.get(
'_email_verify_token'
)}`,
method: 'GET',
});
await this.user.fetch({ useMasterKey: true });
expect(this.user.get('emailVerified')).toEqual(true);
});
});

describe('on password reset', function() {
beforeEach(async function() {
this.user = await Parse.User.logIn(
'someemail@somedomain.com',
'somepassword'
);
});

it('should not work with regex', async function() {
expect(this.user.id).toEqual(this.objectId);
await request({
url: `${serverURL}/requestPasswordReset`,
method: 'POST',
headers,
body: JSON.stringify({
...keys,
_method: 'POST',
email: 'someemail@somedomain.com',
}),
});
await this.user.fetch({ useMasterKey: true });
const passwordResetResponse = await request({
url: `${serverURL}/apps/test/request_password_reset?username=someemail@somedomain.com&token[$regex]=`,
method: 'GET',
});
expect(passwordResetResponse.status).toEqual(302);
expect(passwordResetResponse.headers.location).toMatch(
`\\/invalid\\_link\\.html`
);
await request({
url: `${serverURL}/apps/test/request_password_reset`,
method: 'POST',
body: {
token: { $regex: '' },
username: 'someemail@somedomain.com',
new_password: 'newpassword',
},
});
try {
await Parse.User.logIn('someemail@somedomain.com', 'newpassword');
fail('should not work');
} catch (e) {
expect(e.code).toEqual(101);
expect(e.message).toEqual('Invalid username/password.');
}
});

it('should work with plain token', async function() {
expect(this.user.id).toEqual(this.objectId);
await request({
url: `${serverURL}/requestPasswordReset`,
method: 'POST',
headers,
body: JSON.stringify({
...keys,
_method: 'POST',
email: 'someemail@somedomain.com',
}),
});
await this.user.fetch({ useMasterKey: true });
const token = this.user.get('_perishable_token');
const passwordResetResponse = await request({
url: `${serverURL}/apps/test/request_password_reset?username=someemail@somedomain.com&token=${token}`,
method: 'GET',
});
expect(passwordResetResponse.status).toEqual(302);
expect(passwordResetResponse.headers.location).toMatch(
`\\/choose\\_password\\?token\\=${token}\\&`
);
await request({
url: `${serverURL}/apps/test/request_password_reset`,
method: 'POST',
body: {
token,
username: 'someemail@somedomain.com',
new_password: 'newpassword',
},
});
const userAgain = await Parse.User.logIn(
'someemail@somedomain.com',
'newpassword'
);
expect(userAgain.id).toEqual(this.objectId);
});
});
});
13 changes: 10 additions & 3 deletions src/Routers/PublicAPIRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ const views = path.resolve(__dirname, '../../views');

export class PublicAPIRouter extends PromiseRouter {
verifyEmail(req) {
const { token, username } = req.query;
const { username, token: rawToken } = req.query;
const token =
rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;

const appId = req.params.appId;
const config = Config.get(appId);

Expand Down Expand Up @@ -122,7 +125,9 @@ export class PublicAPIRouter extends PromiseRouter {
return this.missingPublicServerURL();
}

const { username, token } = req.query;
const { username, token: rawToken } = req.query;
const token =
rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;

if (!username || !token) {
return this.invalidLink(req);
Expand Down Expand Up @@ -158,7 +163,9 @@ export class PublicAPIRouter extends PromiseRouter {
return this.missingPublicServerURL();
}

const { username, token, new_password } = req.body;
const { username, new_password, token: rawToken } = req.body;
const token =
rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;

if ((!username || !token || !new_password) && req.xhr === false) {
return this.invalidLink(req);
Expand Down
4 changes: 4 additions & 0 deletions src/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ export function handleParseHeaders(req, res, next) {
}
}

if (info.sessionToken && typeof info.sessionToken !== 'string') {
info.sessionToken = info.sessionToken.toString();
}

if (info.clientVersion) {
info.clientSDK = ClientSDK.fromString(info.clientVersion);
}
Expand Down

0 comments on commit 3a3a5ee

Please sign in to comment.