fix: Cross-Site Scripting (XSS) via HTML pages for password reset and email verification [GHSA-jhgf-2h8h-ggxv](https://github.com/parse-community/parse-server/security/advisories/GHSA-jhgf-2h8h-ggxv) (#9985)
This commit is contained in:
@@ -14,9 +14,9 @@
|
|||||||
<body>
|
<body>
|
||||||
<h1>{{appName}}</h1>
|
<h1>{{appName}}</h1>
|
||||||
<h1>Expired verification link!</h1>
|
<h1>Expired verification link!</h1>
|
||||||
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
|
<form method="POST" action="{{publicServerUrl}}/apps/{{appId}}/resend_verification_email">
|
||||||
<input name="token" type="hidden" value="{{{token}}}">
|
<input name="token" type="hidden" value="{{token}}">
|
||||||
<input name="locale" type="hidden" value="{{{locale}}}">
|
<input name="locale" type="hidden" value="{{locale}}">
|
||||||
<button type="submit">Resend Link</button>
|
<button type="submit">Resend Link</button>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -23,11 +23,11 @@
|
|||||||
<p>You can set a new Password for your account: {{username}}</p>
|
<p>You can set a new Password for your account: {{username}}</p>
|
||||||
<br />
|
<br />
|
||||||
<p>{{error}}</p>
|
<p>{{error}}</p>
|
||||||
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
|
<form id='form' action='{{publicServerUrl}}/apps/{{appId}}/request_password_reset' method='POST'>
|
||||||
<input name='utf-8' type='hidden' value='✓' />
|
<input name='utf-8' type='hidden' value='✓' />
|
||||||
<input name="username" type="hidden" id="username" value="{{{username}}}" />
|
<input name="username" type="hidden" id="username" value="{{username}}" />
|
||||||
<input name="token" type="hidden" id="token" value="{{{token}}}" />
|
<input name="token" type="hidden" id="token" value="{{token}}" />
|
||||||
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
|
<input name="locale" type="hidden" id="locale" value="{{locale}}" />
|
||||||
|
|
||||||
<p>New Password</p>
|
<p>New Password</p>
|
||||||
<input name="new_password" type="password" id="password" />
|
<input name="new_password" type="password" id="password" />
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
<body>
|
<body>
|
||||||
<h1>{{appName}}</h1>
|
<h1>{{appName}}</h1>
|
||||||
<h1>Expired verification link!</h1>
|
<h1>Expired verification link!</h1>
|
||||||
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
|
<form method="POST" action="{{publicServerUrl}}/apps/{{appId}}/resend_verification_email">
|
||||||
<input name="token" type="hidden" value="{{{token}}}">
|
<input name="token" type="hidden" value="{{token}}">
|
||||||
<input name="locale" type="hidden" value="{{{locale}}}">
|
<input name="locale" type="hidden" value="{{locale}}">
|
||||||
<button type="submit">Resend Link</button>
|
<button type="submit">Resend Link</button>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -23,11 +23,11 @@
|
|||||||
<p>You can set a new Password for your account: {{username}}</p>
|
<p>You can set a new Password for your account: {{username}}</p>
|
||||||
<br />
|
<br />
|
||||||
<p>{{error}}</p>
|
<p>{{error}}</p>
|
||||||
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
|
<form id='form' action='{{publicServerUrl}}/apps/{{appId}}/request_password_reset' method='POST'>
|
||||||
<input name='utf-8' type='hidden' value='✓' />
|
<input name='utf-8' type='hidden' value='✓' />
|
||||||
<input name="username" type="hidden" id="username" value="{{{username}}}" />
|
<input name="username" type="hidden" id="username" value="{{username}}" />
|
||||||
<input name="token" type="hidden" id="token" value="{{{token}}}" />
|
<input name="token" type="hidden" id="token" value="{{token}}" />
|
||||||
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
|
<input name="locale" type="hidden" id="locale" value="{{locale}}" />
|
||||||
|
|
||||||
<p>New Password</p>
|
<p>New Password</p>
|
||||||
<input name="new_password" type="password" id="password" />
|
<input name="new_password" type="password" id="password" />
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
<body>
|
<body>
|
||||||
<h1>{{appName}}</h1>
|
<h1>{{appName}}</h1>
|
||||||
<h1>Expired verification link!</h1>
|
<h1>Expired verification link!</h1>
|
||||||
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
|
<form method="POST" action="{{publicServerUrl}}/apps/{{appId}}/resend_verification_email">
|
||||||
<input name="token" type="hidden" value="{{{token}}}">
|
<input name="token" type="hidden" value="{{token}}">
|
||||||
<input name="locale" type="hidden" value="{{{locale}}}">
|
<input name="locale" type="hidden" value="{{locale}}">
|
||||||
<button type="submit">Resend Link</button>
|
<button type="submit">Resend Link</button>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -23,11 +23,11 @@
|
|||||||
<p>You can set a new Password for your account: {{username}}</p>
|
<p>You can set a new Password for your account: {{username}}</p>
|
||||||
<br />
|
<br />
|
||||||
<p>{{error}}</p>
|
<p>{{error}}</p>
|
||||||
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
|
<form id='form' action='{{publicServerUrl}}/apps/{{appId}}/request_password_reset' method='POST'>
|
||||||
<input name='utf-8' type='hidden' value='✓' />
|
<input name='utf-8' type='hidden' value='✓' />
|
||||||
<input name="username" type="hidden" id="username" value="{{{username}}}" />
|
<input name="username" type="hidden" id="username" value="{{username}}" />
|
||||||
<input name="token" type="hidden" id="token" value="{{{token}}}" />
|
<input name="token" type="hidden" id="token" value="{{token}}" />
|
||||||
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
|
<input name="locale" type="hidden" id="locale" value="{{locale}}" />
|
||||||
|
|
||||||
<p>New Password</p>
|
<p>New Password</p>
|
||||||
<input name="new_password" type="password" id="password" />
|
<input name="new_password" type="password" id="password" />
|
||||||
|
|||||||
@@ -1180,4 +1180,72 @@ describe('Pages Router', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('XSS Protection', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await reconfigureServer({
|
||||||
|
appId: 'test',
|
||||||
|
appName: 'exampleAppname',
|
||||||
|
publicServerURL: 'http://localhost:8378/1',
|
||||||
|
pages: { enableRouter: true },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should escape XSS payloads in token parameter', async () => {
|
||||||
|
const xssPayload = '"><script>alert("XSS")</script>';
|
||||||
|
const response = await request({
|
||||||
|
url: `http://localhost:8378/1/apps/choose_password?token=${encodeURIComponent(xssPayload)}&username=test&appId=test`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.text).not.toContain('<script>alert("XSS")</script>');
|
||||||
|
expect(response.text).toContain('"><script>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should escape XSS in username parameter', async () => {
|
||||||
|
const xssUsername = '<img src=x onerror=alert(1)>';
|
||||||
|
const response = await request({
|
||||||
|
url: `http://localhost:8378/1/apps/choose_password?username=${encodeURIComponent(xssUsername)}&appId=test`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.text).not.toContain('<img src=x onerror=alert(1)>');
|
||||||
|
expect(response.text).toContain('<img');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should escape XSS in locale parameter', async () => {
|
||||||
|
const xssLocale = '"><svg/onload=alert(1)>';
|
||||||
|
const response = await request({
|
||||||
|
url: `http://localhost:8378/1/apps/choose_password?locale=${encodeURIComponent(xssLocale)}&appId=test`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.text).not.toContain('<svg/onload=alert(1)>');
|
||||||
|
expect(response.text).toContain('"><svg');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle legitimate usernames with quotes correctly', async () => {
|
||||||
|
const username = "O'Brien";
|
||||||
|
const response = await request({
|
||||||
|
url: `http://localhost:8378/1/apps/choose_password?username=${encodeURIComponent(username)}&appId=test`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
// Should be properly escaped as HTML entity
|
||||||
|
expect(response.text).toContain('O'Brien');
|
||||||
|
// Should NOT contain unescaped quote that breaks HTML
|
||||||
|
expect(response.text).not.toContain('value="O\'Brien"');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle legitimate usernames with ampersands correctly', async () => {
|
||||||
|
const username = 'Smith & Co';
|
||||||
|
const response = await request({
|
||||||
|
url: `http://localhost:8378/1/apps/choose_password?username=${encodeURIComponent(username)}&appId=test`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
// Should be properly escaped
|
||||||
|
expect(response.text).toContain('Smith & Co');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user