Merge pull request from GHSA-h4mf-75hf-67w4
* 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>
This commit is contained in:
198
spec/RegexVulnerabilities.spec.js
Normal file
198
spec/RegexVulnerabilities.spec.js
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user