Skip to content

Commit 3a3a5ee

Browse files
acinaderdavimacedo
andauthored
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 <[email protected]>
1 parent bde8ab6 commit 3a3a5ee

File tree

3 files changed

+212
-3
lines changed

3 files changed

+212
-3
lines changed

spec/RegexVulnerabilities.spec.js

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
const request = require('../lib/request');
2+
3+
const serverURL = 'http://localhost:8378/1';
4+
const headers = {
5+
'Content-Type': 'application/json',
6+
};
7+
const keys = {
8+
_ApplicationId: 'test',
9+
_JavaScriptKey: 'test',
10+
};
11+
const emailAdapter = {
12+
sendVerificationEmail: () => Promise.resolve(),
13+
sendPasswordResetEmail: () => Promise.resolve(),
14+
sendMail: () => {},
15+
};
16+
const appName = 'test';
17+
const publicServerURL = 'http://localhost:8378/1';
18+
19+
describe('Regex Vulnerabilities', function() {
20+
beforeEach(async function() {
21+
await reconfigureServer({
22+
verifyUserEmails: true,
23+
emailAdapter,
24+
appName,
25+
publicServerURL,
26+
});
27+
28+
const signUpResponse = await request({
29+
url: `${serverURL}/users`,
30+
method: 'POST',
31+
headers,
32+
body: JSON.stringify({
33+
...keys,
34+
_method: 'POST',
35+
username: '[email protected]',
36+
password: 'somepassword',
37+
38+
}),
39+
});
40+
this.objectId = signUpResponse.data.objectId;
41+
this.sessionToken = signUpResponse.data.sessionToken;
42+
this.partialSessionToken = this.sessionToken.slice(0, 3);
43+
});
44+
45+
describe('on session token', function() {
46+
it('should not work with regex', async function() {
47+
try {
48+
await request({
49+
url: `${serverURL}/users/me`,
50+
method: 'POST',
51+
headers,
52+
body: JSON.stringify({
53+
...keys,
54+
_SessionToken: {
55+
$regex: this.partialSessionToken,
56+
},
57+
_method: 'GET',
58+
}),
59+
});
60+
fail('should not work');
61+
} catch (e) {
62+
expect(e.data.code).toEqual(209);
63+
expect(e.data.error).toEqual('Invalid session token');
64+
}
65+
});
66+
67+
it('should work with plain token', async function() {
68+
const meResponse = await request({
69+
url: `${serverURL}/users/me`,
70+
method: 'POST',
71+
headers,
72+
body: JSON.stringify({
73+
...keys,
74+
_SessionToken: this.sessionToken,
75+
_method: 'GET',
76+
}),
77+
});
78+
expect(meResponse.data.objectId).toEqual(this.objectId);
79+
expect(meResponse.data.sessionToken).toEqual(this.sessionToken);
80+
});
81+
});
82+
83+
describe('on verify e-mail', function() {
84+
beforeEach(async function() {
85+
const userQuery = new Parse.Query(Parse.User);
86+
this.user = await userQuery.get(this.objectId, { useMasterKey: true });
87+
});
88+
89+
it('should not work with regex', async function() {
90+
expect(this.user.get('emailVerified')).toEqual(false);
91+
await request({
92+
url: `${serverURL}/apps/test/[email protected]&token[$regex]=`,
93+
method: 'GET',
94+
});
95+
await this.user.fetch({ useMasterKey: true });
96+
expect(this.user.get('emailVerified')).toEqual(false);
97+
});
98+
99+
it('should work with plain token', async function() {
100+
expect(this.user.get('emailVerified')).toEqual(false);
101+
// It should work
102+
await request({
103+
url: `${serverURL}/apps/test/[email protected]&token=${this.user.get(
104+
'_email_verify_token'
105+
)}`,
106+
method: 'GET',
107+
});
108+
await this.user.fetch({ useMasterKey: true });
109+
expect(this.user.get('emailVerified')).toEqual(true);
110+
});
111+
});
112+
113+
describe('on password reset', function() {
114+
beforeEach(async function() {
115+
this.user = await Parse.User.logIn(
116+
117+
'somepassword'
118+
);
119+
});
120+
121+
it('should not work with regex', async function() {
122+
expect(this.user.id).toEqual(this.objectId);
123+
await request({
124+
url: `${serverURL}/requestPasswordReset`,
125+
method: 'POST',
126+
headers,
127+
body: JSON.stringify({
128+
...keys,
129+
_method: 'POST',
130+
131+
}),
132+
});
133+
await this.user.fetch({ useMasterKey: true });
134+
const passwordResetResponse = await request({
135+
url: `${serverURL}/apps/test/[email protected]&token[$regex]=`,
136+
method: 'GET',
137+
});
138+
expect(passwordResetResponse.status).toEqual(302);
139+
expect(passwordResetResponse.headers.location).toMatch(
140+
`\\/invalid\\_link\\.html`
141+
);
142+
await request({
143+
url: `${serverURL}/apps/test/request_password_reset`,
144+
method: 'POST',
145+
body: {
146+
token: { $regex: '' },
147+
username: '[email protected]',
148+
new_password: 'newpassword',
149+
},
150+
});
151+
try {
152+
await Parse.User.logIn('[email protected]', 'newpassword');
153+
fail('should not work');
154+
} catch (e) {
155+
expect(e.code).toEqual(101);
156+
expect(e.message).toEqual('Invalid username/password.');
157+
}
158+
});
159+
160+
it('should work with plain token', async function() {
161+
expect(this.user.id).toEqual(this.objectId);
162+
await request({
163+
url: `${serverURL}/requestPasswordReset`,
164+
method: 'POST',
165+
headers,
166+
body: JSON.stringify({
167+
...keys,
168+
_method: 'POST',
169+
170+
}),
171+
});
172+
await this.user.fetch({ useMasterKey: true });
173+
const token = this.user.get('_perishable_token');
174+
const passwordResetResponse = await request({
175+
url: `${serverURL}/apps/test/[email protected]&token=${token}`,
176+
method: 'GET',
177+
});
178+
expect(passwordResetResponse.status).toEqual(302);
179+
expect(passwordResetResponse.headers.location).toMatch(
180+
`\\/choose\\_password\\?token\\=${token}\\&`
181+
);
182+
await request({
183+
url: `${serverURL}/apps/test/request_password_reset`,
184+
method: 'POST',
185+
body: {
186+
token,
187+
username: '[email protected]',
188+
new_password: 'newpassword',
189+
},
190+
});
191+
const userAgain = await Parse.User.logIn(
192+
193+
'newpassword'
194+
);
195+
expect(userAgain.id).toEqual(this.objectId);
196+
});
197+
});
198+
});

src/Routers/PublicAPIRouter.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ const views = path.resolve(__dirname, '../../views');
1111

1212
export class PublicAPIRouter extends PromiseRouter {
1313
verifyEmail(req) {
14-
const { token, username } = req.query;
14+
const { username, token: rawToken } = req.query;
15+
const token =
16+
rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
17+
1518
const appId = req.params.appId;
1619
const config = Config.get(appId);
1720

@@ -122,7 +125,9 @@ export class PublicAPIRouter extends PromiseRouter {
122125
return this.missingPublicServerURL();
123126
}
124127

125-
const { username, token } = req.query;
128+
const { username, token: rawToken } = req.query;
129+
const token =
130+
rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
126131

127132
if (!username || !token) {
128133
return this.invalidLink(req);
@@ -158,7 +163,9 @@ export class PublicAPIRouter extends PromiseRouter {
158163
return this.missingPublicServerURL();
159164
}
160165

161-
const { username, token, new_password } = req.body;
166+
const { username, new_password, token: rawToken } = req.body;
167+
const token =
168+
rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
162169

163170
if ((!username || !token || !new_password) && req.xhr === false) {
164171
return this.invalidLink(req);

src/middlewares.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ export function handleParseHeaders(req, res, next) {
105105
}
106106
}
107107

108+
if (info.sessionToken && typeof info.sessionToken !== 'string') {
109+
info.sessionToken = info.sessionToken.toString();
110+
}
111+
108112
if (info.clientVersion) {
109113
info.clientSDK = ClientSDK.fromString(info.clientVersion);
110114
}

0 commit comments

Comments
 (0)