Skip to content

Commit b4a4cb9

Browse files
committed
fix
1 parent 9b44d25 commit b4a4cb9

File tree

5 files changed

+93
-6
lines changed

5 files changed

+93
-6
lines changed

spec/vulnerabilities.spec.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,92 @@ describe('(GHSA-v5hf-f4c3-m5rv) Stored XSS via .svgz, .xht, .xml, .xsl, .xslt fi
12891289
});
12901290
});
12911291

1292+
describe('(GHSA-42ph-pf9q-cr72) Stored XSS filter bypass via parameterized Content-Type and additional XML extensions', () => {
1293+
const headers = {
1294+
'X-Parse-Application-Id': 'test',
1295+
'X-Parse-REST-API-Key': 'rest',
1296+
};
1297+
1298+
beforeEach(async () => {
1299+
await reconfigureServer({
1300+
fileUpload: {
1301+
enableForPublic: true,
1302+
},
1303+
});
1304+
});
1305+
1306+
for (const { ext, contentType } of [
1307+
{ ext: 'xsd', contentType: 'application/xml' },
1308+
{ ext: 'rng', contentType: 'application/xml' },
1309+
{ ext: 'rdf', contentType: 'application/rdf+xml' },
1310+
{ ext: 'owl', contentType: 'application/rdf+xml' },
1311+
{ ext: 'mathml', contentType: 'application/mathml+xml' },
1312+
]) {
1313+
it(`blocks .${ext} file upload by default`, async () => {
1314+
const content = Buffer.from(
1315+
'<?xml version="1.0"?><html xmlns="http://www.w3.org/1999/xhtml"><body><script>alert(1)</script></body></html>'
1316+
).toString('base64');
1317+
for (const extension of [ext, ext.toUpperCase(), ext[0].toUpperCase() + ext.slice(1)]) {
1318+
await expectAsync(
1319+
request({
1320+
method: 'POST',
1321+
headers,
1322+
url: `http://localhost:8378/1/files/malicious.${extension}`,
1323+
body: JSON.stringify({
1324+
_ApplicationId: 'test',
1325+
_JavaScriptKey: 'test',
1326+
_ContentType: contentType,
1327+
base64: content,
1328+
}),
1329+
}).catch(e => {
1330+
throw new Error(e.data.error);
1331+
})
1332+
).toBeRejectedWith(
1333+
new Parse.Error(
1334+
Parse.Error.FILE_SAVE_ERROR,
1335+
`File upload of extension ${extension} is disabled.`
1336+
)
1337+
);
1338+
}
1339+
});
1340+
}
1341+
1342+
it('blocks extensionless upload with parameterized Content-Type that bypasses regex', async () => {
1343+
const content = Buffer.from(
1344+
'<?xml version="1.0"?><html xmlns="http://www.w3.org/1999/xhtml"><body><script>alert(1)</script></body></html>'
1345+
).toString('base64');
1346+
// MIME parameters like ;charset=utf-8 should not bypass the extension filter
1347+
const dangerousContentTypes = [
1348+
'application/xhtml+xml;charset=utf-8',
1349+
'application/xhtml+xml; charset=utf-8',
1350+
'image/svg+xml;charset=utf-8',
1351+
'application/xml;charset=utf-8',
1352+
'text/html;charset=utf-8',
1353+
'application/xslt+xml;charset=utf-8',
1354+
'application/rdf+xml;charset=utf-8',
1355+
'application/mathml+xml;charset=utf-8',
1356+
];
1357+
for (const contentType of dangerousContentTypes) {
1358+
await expectAsync(
1359+
request({
1360+
method: 'POST',
1361+
url: 'http://localhost:8378/1/files/payload',
1362+
body: JSON.stringify({
1363+
_ApplicationId: 'test',
1364+
_JavaScriptKey: 'test',
1365+
_ContentType: contentType,
1366+
base64: content,
1367+
}),
1368+
}).catch(e => {
1369+
throw new Error(e.data.error);
1370+
})
1371+
).toBeRejectedWith(jasmine.objectContaining({
1372+
message: jasmine.stringMatching(/File upload of extension .+ is disabled/),
1373+
}));
1374+
}
1375+
});
1376+
});
1377+
12921378
describe('(GHSA-3jmq-rrxf-gqrg) Stored XSS via file serving', () => {
12931379
it('sets X-Content-Type-Options: nosniff on file GET response', async () => {
12941380
const file = new Parse.File('hello.txt', [1, 2, 3], 'text/plain');

src/Options/Definitions.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,10 +1065,10 @@ module.exports.FileUploadOptions = {
10651065
},
10661066
fileExtensions: {
10671067
env: 'PARSE_SERVER_FILE_UPLOAD_FILE_EXTENSIONS',
1068-
help: "Sets the allowed file extensions for uploading files. The extension is defined as an array of file extensions, or a regex pattern.<br><br>It is recommended to restrict the file upload extensions as much as possible. HTML, SVG, and XML files are especially problematic as they may be used by an attacker who uploads a HTML form, SVG image, or XML document to look legitimate under your app's domain name, or to compromise the session token of another user via accessing the browser's local storage.<br><br>Defaults to `^(?!([xXsS]?[hH][tT][mM][lL]?(\\\\+[xX][mM][lL])?|[xX][hH][tT]|[sS][vV][gG]([zZ]|\\\\+[xX][mM][lL])?|[xX][mM][lL]|[xX][sS][lL][tT]?(\\\\+[xX][mM][lL])?)$)` which allows any file extension except those that are rendered as website or active content by a web browser.",
1068+
help: 'Sets the allowed file extensions for uploading files. The extension is defined as an array of file extensions, or a regex pattern.<br><br>It is recommended to only allow the file extensions that your app actually needs, rather than relying on blocking dangerous extensions. For example, if your app only needs images, set this to `["jpg", "jpeg", "png", "gif", "webp"]`. This allowlist approach is more secure because new dangerous file extensions may emerge that are not covered by the default blocklist.<br><br>The default blocks the most common file extensions that are known to be rendered as active content by web browsers, such as HTML, SVG, and XML files, which may be used by an attacker to compromise the session token of another user via accessing the browser\'s local storage. The blocked extensions are: `html`, `htm`, `shtml`, `xhtml`, `xhtml+xml`, `xht`, `svg`, `svgz`, `svg+xml`, `xml`, `xsl`, `xslt`, `xslt+xml`, `xsd`, `rng`, `rdf`, `rdf+xml`, `owl`, `mathml`, `mathml+xml`.<br><br>Defaults to `["^(?!([xXsS]?[hH][tT][mM][lL]?(\\\\+[xX][mM][lL])?|[xX][hH][tT]|[sS][vV][gG]([zZ]|\\\\+[xX][mM][lL])?|[xX][mM][lL]|[xX][sS][lL][tT]?(\\\\+[xX][mM][lL])?|[xX][sS][dD]|[rR][nN][gG]|[rR][dD][fF](\\\\+[xX][mM][lL])?|[oO][wW][lL]|[mM][aA][tT][hH][mM][lL](\\\\+[xX][mM][lL])?)$)"]`.',
10691069
action: parsers.arrayParser,
10701070
default: [
1071-
'^(?!([xXsS]?[hH][tT][mM][lL]?(\\+[xX][mM][lL])?|[xX][hH][tT]|[sS][vV][gG]([zZ]|\\+[xX][mM][lL])?|[xX][mM][lL]|[xX][sS][lL][tT]?(\\+[xX][mM][lL])?)$)',
1071+
'^(?!([xXsS]?[hH][tT][mM][lL]?(\\+[xX][mM][lL])?|[xX][hH][tT]|[sS][vV][gG]([zZ]|\\+[xX][mM][lL])?|[xX][mM][lL]|[xX][sS][lL][tT]?(\\+[xX][mM][lL])?|[xX][sS][dD]|[rR][nN][gG]|[rR][dD][fF](\\+[xX][mM][lL])?|[oO][wW][lL]|[mM][aA][tT][hH][mM][lL](\\+[xX][mM][lL])?)$)',
10721072
],
10731073
},
10741074
};

src/Options/docs.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Options/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,8 +657,8 @@ export interface PasswordPolicyOptions {
657657
}
658658

659659
export interface FileUploadOptions {
660-
/* Sets the allowed file extensions for uploading files. The extension is defined as an array of file extensions, or a regex pattern.<br><br>It is recommended to restrict the file upload extensions as much as possible. HTML, SVG, and XML files are especially problematic as they may be used by an attacker who uploads a HTML form, SVG image, or XML document to look legitimate under your app's domain name, or to compromise the session token of another user via accessing the browser's local storage.<br><br>Defaults to `^(?!([xXsS]?[hH][tT][mM][lL]?(\\+[xX][mM][lL])?|[xX][hH][tT]|[sS][vV][gG]([zZ]|\\+[xX][mM][lL])?|[xX][mM][lL]|[xX][sS][lL][tT]?(\\+[xX][mM][lL])?)$)` which allows any file extension except those that are rendered as website or active content by a web browser.
661-
:DEFAULT: ["^(?!([xXsS]?[hH][tT][mM][lL]?(\\+[xX][mM][lL])?|[xX][hH][tT]|[sS][vV][gG]([zZ]|\\+[xX][mM][lL])?|[xX][mM][lL]|[xX][sS][lL][tT]?(\\+[xX][mM][lL])?)$)"] */
660+
/* Sets the allowed file extensions for uploading files. The extension is defined as an array of file extensions, or a regex pattern.<br><br>It is recommended to only allow the file extensions that your app actually needs, rather than relying on blocking dangerous extensions. For example, if your app only needs images, set this to `["jpg", "jpeg", "png", "gif", "webp"]`. This allowlist approach is more secure because new dangerous file extensions may emerge that are not covered by the default blocklist.<br><br>The default blocks the most common file extensions that are known to be rendered as active content by web browsers, such as HTML, SVG, and XML files, which may be used by an attacker to compromise the session token of another user via accessing the browser's local storage. The blocked extensions are: `html`, `htm`, `shtml`, `xhtml`, `xhtml+xml`, `xht`, `svg`, `svgz`, `svg+xml`, `xml`, `xsl`, `xslt`, `xslt+xml`, `xsd`, `rng`, `rdf`, `rdf+xml`, `owl`, `mathml`, `mathml+xml`.<br><br>Defaults to `["^(?!([xXsS]?[hH][tT][mM][lL]?(\\+[xX][mM][lL])?|[xX][hH][tT]|[sS][vV][gG]([zZ]|\\+[xX][mM][lL])?|[xX][mM][lL]|[xX][sS][lL][tT]?(\\+[xX][mM][lL])?|[xX][sS][dD]|[rR][nN][gG]|[rR][dD][fF](\\+[xX][mM][lL])?|[oO][wW][lL]|[mM][aA][tT][hH][mM][lL](\\+[xX][mM][lL])?)$)"]`.
661+
:DEFAULT: ["^(?!([xXsS]?[hH][tT][mM][lL]?(\\+[xX][mM][lL])?|[xX][hH][tT]|[sS][vV][gG]([zZ]|\\+[xX][mM][lL])?|[xX][mM][lL]|[xX][sS][lL][tT]?(\\+[xX][mM][lL])?|[xX][sS][dD]|[rR][nN][gG]|[rR][dD][fF](\\+[xX][mM][lL])?|[oO][wW][lL]|[mM][aA][tT][hH][mM][lL](\\+[xX][mM][lL])?)$)"] */
662662
fileExtensions: ?(string[]);
663663
/* Is true if file upload should be allowed for anonymous users.
664664
:DEFAULT: false */

src/Routers/FilesRouter.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,8 @@ export class FilesRouter {
368368
} else if (contentType && contentType.includes('/')) {
369369
extension = contentType.split('/')[1];
370370
}
371-
extension = extension?.split(' ')?.join('');
371+
// Strip MIME parameters (e.g. ";charset=utf-8") and whitespace
372+
extension = extension?.split(';')[0]?.split(' ')?.join('');
372373

373374
if (extension && !isValidExtension(extension)) {
374375
next(

0 commit comments

Comments
 (0)