Skip to content

Commit 23f4d47

Browse files
committed
fix(cloudfunc) XSS vulnerability: html in file name: allows executing malicious javascript code in the user's browser
1 parent c413d0b commit 23f4d47

3 files changed

Lines changed: 97 additions & 24 deletions

File tree

client/dom/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ const Util = require('../../common/util');
1010
const {
1111
getTitle,
1212
FS,
13-
Entity,
1413
} = require('../../common/cloudfunc');
1514

15+
const {encode} = require('../../common/entity');
16+
1617
const DOMTree = require('./dom-tree');
1718

1819
const DOM = Object.assign({}, DOMTree, new CmdProto());
@@ -747,7 +748,7 @@ function CmdProto() {
747748
const dir = PREFIX + FS + Info.dirPath;
748749

749750
link.title = name;
750-
link.innerHTML = Entity.encode(name);
751+
link.innerHTML = encode(name);
751752
link.href = dir + name;
752753

753754
current.setAttribute('data-name', 'js-file-' + name);

common/cloudfunc.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const rendy = require('rendy');
44
const currify = require('currify/legacy');
55
const store = require('fullstore/legacy');
6-
const Entity = require('./entity');
6+
const encode = require('./entity').encode;
77

88
const getHeaderField = currify(_getHeaderField);
99

@@ -20,7 +20,6 @@ Path('/');
2020
module.exports.FS = FS;
2121
module.exports.apiURL = '/api/v1';
2222
module.exports.MAX_FILE_SIZE = 500 * 1024;
23-
module.exports.Entity = Entity;
2423
module.exports.getHeaderField = getHeaderField;
2524
module.exports.getPathLink = getPathLink;
2625
module.exports.getDotDot = getDotDot;
@@ -181,7 +180,8 @@ module.exports.buildFromJSON = (params) => {
181180
}
182181

183182
fileTable += files.map((file) => {
184-
const link = prefix + FS + path + file.name;
183+
const name = encode(file.name);
184+
const link = prefix + FS + path + name;
185185

186186
const type = getType(file.size);
187187
const size = getSize(file.size);
@@ -192,13 +192,13 @@ module.exports.buildFromJSON = (params) => {
192192

193193
const linkResult = rendy(templateLink, {
194194
link,
195-
title: file.name,
196-
name: Entity.encode(file.name),
195+
title: name,
196+
name,
197197
attribute: getAttribute(file.size)
198198
});
199199

200-
const dataName = 'data-name="js-file-' + file.name + '" ';
201-
const attribute = 'draggable="true" ' + dataName;
200+
const dataName = `data-name="js-file-${name}" `;
201+
const attribute = `draggable="true" ${dataName}`;
202202

203203
return rendy(templateFile, {
204204
tag: 'li',

test/server/route.js

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,20 @@ const test = require('tape');
66
const {promisify} = require('es6-promisify');
77
const pullout = require('pullout');
88
const request = require('request');
9+
const mockRequire = require('mock-require');
10+
const clear = require('clear-module');
911

1012
const rootDir = '../..';
1113

1214
const routePath = `${rootDir}/server/route`;
15+
const beforePath = '../before';
1316

1417
const {
1518
_getIndexPath,
1619
} = require(routePath);
1720

1821
const route = require(routePath);
19-
const {connect}= require('../before');
22+
const {connect} = require(beforePath);
2023

2124
const warp = (fn, ...a) => (...b) => fn(...b, ...a);
2225
const _pullout = promisify(pullout);
@@ -78,20 +81,6 @@ test('cloudcmd: route: buttons: console', async (t) => {
7881
done();
7982
});
8083

81-
test('cloudcmd: route: buttons: no terminal', async (t) => {
82-
const config = {
83-
terminal: false
84-
};
85-
86-
const {port, done} = await connect({config});
87-
const result = await getStr(`http://localhost:${port}`);
88-
89-
t.ok(/icon-terminal none/.test(result), 'should hide terminal');
90-
t.end();
91-
92-
done();
93-
});
94-
9584
test('cloudcmd: route: buttons: no config', async (t) => {
9685
const config = {
9786
configDialog: false
@@ -286,8 +275,91 @@ test('cloudcmd: route: sendIndex: error', async (t) => {
286275
const data = await getStr(`http://localhost:${port}`);
287276

288277
t.equal(data, error.message, 'should return error');
278+
279+
done();
280+
t.end();
281+
});
282+
283+
test('cloudcmd: route: sendIndex: encode', async (t) => {
284+
const name = '"><svg onload=alert(3);>';
285+
const nameEncoded = '&quot;&gt;&lt;svg&nbsp;onload=alert(3);&gt;';
286+
const files = [{
287+
name,
288+
}];
289+
290+
const read = (path, fn) => fn(null, {
291+
path,
292+
files,
293+
});
294+
295+
mockRequire('flop', {
296+
read
297+
});
298+
299+
clear(routePath);
300+
clear('../../server/cloudcmd');
301+
clear(beforePath);
302+
303+
const {connect} = require(beforePath);
304+
const {port, done} = await connect();
305+
const data = await getStr(`http://localhost:${port}`);
306+
307+
t.ok(data.includes(nameEncoded), 'should encode name');
308+
309+
clear('flop');
310+
clear(routePath);
311+
clear('../../server/cloudcmd');
312+
clear(beforePath);
313+
314+
done();
289315
t.end();
316+
});
317+
318+
test('cloudcmd: route: sendIndex: encode', async (t) => {
319+
const name = '"><svg onload=alert(3);>';
320+
const files = [{
321+
name,
322+
}];
323+
324+
const read = (path, fn) => fn(null, {
325+
path,
326+
files,
327+
});
328+
329+
mockRequire('flop', {
330+
read
331+
});
332+
333+
clear(routePath);
334+
clear('../../server/cloudcmd');
335+
clear(beforePath);
336+
337+
const {connect} = require(beforePath);
338+
const {port, done} = await connect();
339+
const data = await getStr(`http://localhost:${port}`);
340+
341+
t.notOk(data.includes(name), 'should put not encoded name');
342+
343+
clear('flop');
344+
clear(routePath);
345+
clear('../../server/cloudcmd');
346+
clear(beforePath);
290347

291348
done();
349+
t.end();
350+
});
351+
352+
test('cloudcmd: route: buttons: no terminal', async (t) => {
353+
const config = {
354+
terminal: false
355+
};
356+
357+
const {port, done} = await connect({config});
358+
const result = await getStr(`http://localhost:${port}`);
359+
360+
t.ok(/icon-terminal none/.test(result), 'should hide terminal');
361+
362+
done();
363+
t.end();
292364
});
293365

0 commit comments

Comments
 (0)