Skip to content

Commit 5ee0b16

Browse files
authored
fix: [#1585] Fixes security vulnerability that allowed for server side code to be executed by a <script> tag (#1586)
* fix: [#1585] Fixes security vulnerability that allowed for server side code to be executed by a <script> tag * chore: [#1585] Fixes unit test
1 parent a20dba9 commit 5ee0b16

File tree

2 files changed

+68
-1
lines changed

2 files changed

+68
-1
lines changed

packages/happy-dom/src/fetch/utilities/SyncFetchScriptBuilder.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export default class SyncFetchScriptBuilder {
2323
}): string {
2424
const sortedHeaders = {};
2525
const headerNames = Object.keys(request.headers).sort();
26+
2627
for (const name of headerNames) {
2728
sortedHeaders[name] = request.headers[name];
2829
}
@@ -43,7 +44,7 @@ export default class SyncFetchScriptBuilder {
4344
null,
4445
4
4546
)};
46-
const request = sendRequest('${request.url.href}', options, (incomingMessage) => {
47+
const request = sendRequest(\`${request.url.href}\`, options, (incomingMessage) => {
4748
let data = Buffer.alloc(0);
4849
incomingMessage.on('data', (chunk) => {
4950
data = Buffer.concat([data, Buffer.from(chunk)]);

packages/happy-dom/test/fetch/SyncFetch.test.ts

+66
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,72 @@ describe('SyncFetch', () => {
249249
expect(response.body.toString()).toBe(responseText);
250250
});
251251

252+
it('Should not allow to inject code into scripts executed using child_process.execFileSync().', () => {
253+
browserFrame.url = 'https://localhost:8080/';
254+
255+
const url =
256+
"https://localhost:8080/`+require('child_process').execSync('id')+`/'+require('child_process').execSync('id')+'";
257+
const responseText = 'test';
258+
259+
mockModule('child_process', {
260+
execFileSync: (
261+
command: string,
262+
args: string[],
263+
options: { encoding: string; maxBuffer: number }
264+
) => {
265+
expect(command).toEqual(process.argv[0]);
266+
expect(args[0]).toBe('-e');
267+
expect(args[1]).toBe(
268+
SyncFetchScriptBuilder.getScript({
269+
url: new URL(
270+
"https://localhost:8080/%60+require('child_process').execSync('id')+%60/'+require('child_process').execSync('id')+'"
271+
),
272+
method: 'GET',
273+
headers: {
274+
Accept: '*/*',
275+
Connection: 'close',
276+
Referer: 'https://localhost:8080/',
277+
'User-Agent': window.navigator.userAgent,
278+
'Accept-Encoding': 'gzip, deflate, br'
279+
},
280+
body: null
281+
})
282+
);
283+
// new URL() will convert ` into %60
284+
// By using ` for the URL string within the script, we can prevent the script from being injected
285+
expect(
286+
args[1].includes(
287+
`\`https://localhost:8080/%60+require('child_process').execSync('id')+%60/'+require('child_process').execSync('id')+'\``
288+
)
289+
).toBe(true);
290+
expect(options).toEqual({
291+
encoding: 'buffer',
292+
maxBuffer: 1024 * 1024 * 1024
293+
});
294+
return JSON.stringify({
295+
error: null,
296+
incomingMessage: {
297+
statusCode: 200,
298+
statusMessage: 'OK',
299+
rawHeaders: [],
300+
data: Buffer.from(responseText).toString('base64')
301+
}
302+
});
303+
}
304+
});
305+
306+
const response = new SyncFetch({
307+
browserFrame,
308+
window,
309+
url,
310+
init: {
311+
method: 'GET'
312+
}
313+
}).send();
314+
315+
expect(response.body.toString()).toBe(responseText);
316+
});
317+
252318
it('Should send custom key/value object request headers.', () => {
253319
browserFrame.url = 'https://localhost:8080/';
254320

0 commit comments

Comments
 (0)