Skip to content

Commit 2580457

Browse files
committed
fix
1 parent a6c0926 commit 2580457

File tree

4 files changed

+169
-5
lines changed

4 files changed

+169
-5
lines changed

spec/vulnerabilities.spec.js

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,168 @@ describe('(GHSA-qpr4-jrj4-6f27) SQL Injection via sort dot-notation field name',
10211021
});
10221022
});
10231023

1024+
describe('(GHSA-v5hf-f4c3-m5rv) Stored XSS via .svgz, .xht, .xml, .xsl, .xslt file upload', () => {
1025+
const headers = {
1026+
'X-Parse-Application-Id': 'test',
1027+
'X-Parse-REST-API-Key': 'rest',
1028+
};
1029+
1030+
beforeEach(async () => {
1031+
await reconfigureServer({
1032+
fileUpload: {
1033+
enableForPublic: true,
1034+
},
1035+
});
1036+
});
1037+
1038+
it('blocks .svgz file upload by default', async () => {
1039+
const svgContent = Buffer.from(
1040+
'<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>'
1041+
).toString('base64');
1042+
for (const extension of ['svgz', 'SVGZ', 'Svgz']) {
1043+
await expectAsync(
1044+
request({
1045+
method: 'POST',
1046+
headers,
1047+
url: `http://localhost:8378/1/files/malicious.${extension}`,
1048+
body: JSON.stringify({
1049+
_ApplicationId: 'test',
1050+
_JavaScriptKey: 'test',
1051+
_ContentType: 'image/svg+xml',
1052+
base64: svgContent,
1053+
}),
1054+
}).catch(e => {
1055+
throw new Error(e.data.error);
1056+
})
1057+
).toBeRejectedWith(
1058+
new Parse.Error(
1059+
Parse.Error.FILE_SAVE_ERROR,
1060+
`File upload of extension ${extension} is disabled.`
1061+
)
1062+
);
1063+
}
1064+
});
1065+
1066+
it('blocks .xht file upload by default', async () => {
1067+
const xhtContent = Buffer.from(
1068+
'<?xml version="1.0"?><html xmlns="http://www.w3.org/1999/xhtml"><body><script>alert(1)</script></body></html>'
1069+
).toString('base64');
1070+
for (const extension of ['xht', 'XHT', 'Xht']) {
1071+
await expectAsync(
1072+
request({
1073+
method: 'POST',
1074+
headers,
1075+
url: `http://localhost:8378/1/files/malicious.${extension}`,
1076+
body: JSON.stringify({
1077+
_ApplicationId: 'test',
1078+
_JavaScriptKey: 'test',
1079+
_ContentType: 'application/xhtml+xml',
1080+
base64: xhtContent,
1081+
}),
1082+
}).catch(e => {
1083+
throw new Error(e.data.error);
1084+
})
1085+
).toBeRejectedWith(
1086+
new Parse.Error(
1087+
Parse.Error.FILE_SAVE_ERROR,
1088+
`File upload of extension ${extension} is disabled.`
1089+
)
1090+
);
1091+
}
1092+
});
1093+
1094+
it('blocks .xml file upload by default', async () => {
1095+
const xmlContent = Buffer.from(
1096+
'<?xml version="1.0"?><root><data>test</data></root>'
1097+
).toString('base64');
1098+
for (const extension of ['xml', 'XML', 'Xml']) {
1099+
await expectAsync(
1100+
request({
1101+
method: 'POST',
1102+
headers,
1103+
url: `http://localhost:8378/1/files/malicious.${extension}`,
1104+
body: JSON.stringify({
1105+
_ApplicationId: 'test',
1106+
_JavaScriptKey: 'test',
1107+
_ContentType: 'application/xml',
1108+
base64: xmlContent,
1109+
}),
1110+
}).catch(e => {
1111+
throw new Error(e.data.error);
1112+
})
1113+
).toBeRejectedWith(
1114+
new Parse.Error(
1115+
Parse.Error.FILE_SAVE_ERROR,
1116+
`File upload of extension ${extension} is disabled.`
1117+
)
1118+
);
1119+
}
1120+
});
1121+
1122+
it('blocks .xsl file upload by default', async () => {
1123+
const xslContent = Buffer.from(
1124+
'<?xml version="1.0"?><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"></xsl:stylesheet>'
1125+
).toString('base64');
1126+
for (const extension of ['xsl', 'XSL', 'Xsl']) {
1127+
await expectAsync(
1128+
request({
1129+
method: 'POST',
1130+
headers,
1131+
url: `http://localhost:8378/1/files/malicious.${extension}`,
1132+
body: JSON.stringify({
1133+
_ApplicationId: 'test',
1134+
_JavaScriptKey: 'test',
1135+
_ContentType: 'application/xml',
1136+
base64: xslContent,
1137+
}),
1138+
}).catch(e => {
1139+
throw new Error(e.data.error);
1140+
})
1141+
).toBeRejectedWith(
1142+
new Parse.Error(
1143+
Parse.Error.FILE_SAVE_ERROR,
1144+
`File upload of extension ${extension} is disabled.`
1145+
)
1146+
);
1147+
}
1148+
});
1149+
1150+
it('blocks .xslt file upload by default', async () => {
1151+
const xsltContent = Buffer.from(
1152+
'<?xml version="1.0"?><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"></xsl:stylesheet>'
1153+
).toString('base64');
1154+
for (const extension of ['xslt', 'XSLT', 'Xslt']) {
1155+
await expectAsync(
1156+
request({
1157+
method: 'POST',
1158+
headers,
1159+
url: `http://localhost:8378/1/files/malicious.${extension}`,
1160+
body: JSON.stringify({
1161+
_ApplicationId: 'test',
1162+
_JavaScriptKey: 'test',
1163+
_ContentType: 'application/xslt+xml',
1164+
base64: xsltContent,
1165+
}),
1166+
}).catch(e => {
1167+
throw new Error(e.data.error);
1168+
})
1169+
).toBeRejectedWith(
1170+
new Parse.Error(
1171+
Parse.Error.FILE_SAVE_ERROR,
1172+
`File upload of extension ${extension} is disabled.`
1173+
)
1174+
);
1175+
}
1176+
});
1177+
1178+
it('still allows common file types', async () => {
1179+
for (const type of ['txt', 'png', 'jpg', 'gif', 'pdf', 'doc']) {
1180+
const file = new Parse.File(`file.${type}`, { base64: 'ParseA==' });
1181+
await file.save();
1182+
}
1183+
});
1184+
});
1185+
10241186
describe('(GHSA-3jmq-rrxf-gqrg) Stored XSS via file serving', () => {
10251187
it('sets X-Content-Type-Options: nosniff on file GET response', async () => {
10261188
const file = new Parse.File('hello.txt', [1, 2, 3], 'text/plain');

src/Options/Definitions.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,9 +1059,11 @@ module.exports.FileUploadOptions = {
10591059
},
10601060
fileExtensions: {
10611061
env: 'PARSE_SERVER_FILE_UPLOAD_FILE_EXTENSIONS',
1062-
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 and SVG files are especially problematic as they may be used by an attacker who uploads a HTML form or SVG image 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]?|[sS][vV][gG](\\\\+[xX][mM][lL])?)$)` which allows any file extension except those that are rendered as website or active content by a web browser.",
1062+
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][hH][tT]|[sS][vV][gG]([zZ]|\\\\+[xX][mM][lL])?|[xX][mM][lL]|[xX][sS][lL][tT]?)$)` which allows any file extension except those that are rendered as website or active content by a web browser.",
10631063
action: parsers.arrayParser,
1064-
default: ['^(?!([xXsS]?[hH][tT][mM][lL]?|[sS][vV][gG](\\+[xX][mM][lL])?)$)'],
1064+
default: [
1065+
'^(?!([xXsS]?[hH][tT][mM][lL]?|[xX][hH][tT]|[sS][vV][gG]([zZ]|\\+[xX][mM][lL])?|[xX][mM][lL]|[xX][sS][lL][tT]?)$)',
1066+
],
10651067
},
10661068
};
10671069
/* The available log levels for Parse Server logging. Valid values are:<br>- `'error'` - Error level (highest priority)<br>- `'warn'` - Warning level<br>- `'info'` - Info level (default)<br>- `'verbose'` - Verbose level<br>- `'debug'` - Debug level<br>- `'silly'` - Silly level (lowest priority) */

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
@@ -648,8 +648,8 @@ export interface PasswordPolicyOptions {
648648
}
649649

650650
export interface FileUploadOptions {
651-
/* 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 and SVG files are especially problematic as they may be used by an attacker who uploads a HTML form or SVG image 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]?|[sS][vV][gG](\\+[xX][mM][lL])?)$)` which allows any file extension except those that are rendered as website or active content by a web browser.
652-
:DEFAULT: ["^(?!([xXsS]?[hH][tT][mM][lL]?|[sS][vV][gG](\\+[xX][mM][lL])?)$)"] */
651+
/* 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][hH][tT]|[sS][vV][gG]([zZ]|\\+[xX][mM][lL])?|[xX][mM][lL]|[xX][sS][lL][tT]?)$)` which allows any file extension except those that are rendered as website or active content by a web browser.
652+
:DEFAULT: ["^(?!([xXsS]?[hH][tT][mM][lL]?|[xX][hH][tT]|[sS][vV][gG]([zZ]|\\+[xX][mM][lL])?|[xX][mM][lL]|[xX][sS][lL][tT]?)$)"] */
653653
fileExtensions: ?(string[]);
654654
/* Is true if file upload should be allowed for anonymous users.
655655
:DEFAULT: false */

0 commit comments

Comments
 (0)