Skip to content

Commit 0d6d8c3

Browse files
authored
Merge pull request #1532 from mattolson/backport-security-fixes
Backport security fixes to 3.x branch
2 parents 7cf753b + 7c39440 commit 0d6d8c3

File tree

9 files changed

+102
-7
lines changed

9 files changed

+102
-7
lines changed

README.markdown

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[![Travis Build Status](https://img.shields.io/travis/wycats/handlebars.js/master.svg)](https://travis-ci.org/wycats/handlebars.js)
2+
[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/wycats/handlebars.js?branch=master&svg=true)](https://ci.appveyor.com/project/wycats/handlebars-js)
23
[![Selenium Test Status](https://saucelabs.com/buildstatus/handlebars)](https://saucelabs.com/u/handlebars)
34

45
Handlebars.js

appveyor.yml

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Test against these versions of Node.js
2+
environment:
3+
matrix:
4+
- nodejs_version: "10"
5+
6+
platform:
7+
- x64
8+
9+
# Install scripts (runs after repo cloning)
10+
install:
11+
# Get the latest stable version of Node.js
12+
- ps: Install-Product node $env:nodejs_version $env:platform
13+
# Clone submodules (mustache spec)
14+
- cmd: git submodule update --init --recursive
15+
# Install modules
16+
- cmd: npm install
17+
- cmd: npm install -g grunt-cli
18+
19+
20+
# Post-install test scripts
21+
test_script:
22+
# Output useful info for debugging
23+
- cmd: node --version
24+
- cmd: npm --version
25+
# Run tests
26+
- cmd: grunt --stack travis
27+
28+
# Don't actually build
29+
build: off
30+
31+
on_failure:
32+
- cmd: 7z a coverage.zip coverage
33+
- cmd: appveyor PushArtifact coverage.zip
34+
35+
36+
# Set build version format here instead of in the admin panel
37+
version: "{build}"

lib/handlebars/base.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,13 @@ function registerDefaultHelpers(instance) {
212212
});
213213

214214
instance.registerHelper('lookup', function(obj, field) {
215-
return obj && obj[field];
215+
if (!obj) {
216+
return obj;
217+
}
218+
if (field === 'constructor' && !obj.propertyIsEnumerable(field)) {
219+
return undefined;
220+
}
221+
return obj[field];
216222
});
217223
}
218224

lib/handlebars/compiler/javascript-compiler.js

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ JavaScriptCompiler.prototype = {
1313
// PUBLIC API: You can override these methods in a subclass to provide
1414
// alternative compiled forms for name lookup and buffering semantics
1515
nameLookup: function(parent, name /* , type*/) {
16+
if (name === 'constructor') {
17+
return ['(', parent, '.propertyIsEnumerable(\'constructor\') ? ', parent, '.constructor : undefined', ')'];
18+
}
1619
if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
1720
return [parent, '.', name];
1821
} else {

spec/env/browser.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ var fs = require('fs'),
44
vm = require('vm');
55

66
global.Handlebars = 'no-conflict';
7-
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.js'), 'dist/handlebars.js');
7+
8+
var filename = 'dist/handlebars.js';
9+
if (global.minimizedTest) {
10+
filename = 'dist/handlebars.min.js';
11+
}
12+
var distHandlebars = fs.readFileSync(require.resolve(`../../${filename}`), 'utf-8');
13+
vm.runInThisContext(distHandlebars, filename);
814

915
global.CompilerContext = {
1016
browser: true,

spec/env/runner.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ var files = [ testDir + '/basic.js' ];
1111

1212
var files = fs.readdirSync(testDir)
1313
.filter(function(name) { return (/.*\.js$/).test(name); })
14-
.map(function(name) { return testDir + '/' + name; });
14+
.map(function(name) { return testDir + path.sep + name; });
1515

1616
run('./node', function() {
1717
run('./browser', function() {

spec/security.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
describe('security issues', function() {
2+
describe('GH-1495: Prevent Remote Code Execution via constructor', function() {
3+
it('should not allow constructors to be accessed', function() {
4+
shouldCompileTo('{{constructor.name}}', {}, '');
5+
shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {}, '');
6+
});
7+
8+
it('should allow the "constructor" property to be accessed if it is enumerable', function() {
9+
shouldCompileTo('{{constructor.name}}', {'constructor': {
10+
'name': 'here we go'
11+
}}, 'here we go');
12+
shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {'constructor': {
13+
'name': 'here we go'
14+
}}, 'here we go');
15+
});
16+
17+
it('should allow prototype properties that are not constructors', function() {
18+
function TestClass() {
19+
}
20+
21+
Object.defineProperty(TestClass.prototype, 'abc', {
22+
get: function() {
23+
return 'xyz';
24+
}
25+
});
26+
27+
shouldCompileTo('{{#with this}}{{this.abc}}{{/with}}',
28+
new TestClass(), 'xyz');
29+
shouldCompileTo('{{#with this}}{{lookup this "abc"}}{{/with}}',
30+
new TestClass(), 'xyz');
31+
});
32+
});
33+
});

tasks/test.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
var childProcess = require('child_process'),
2-
fs = require('fs');
2+
fs = require('fs'),
3+
os = require('os');
34

45
module.exports = function(grunt) {
56
grunt.registerTask('test:bin', function() {
67
var done = this.async();
78

8-
childProcess.exec('./bin/handlebars -a spec/artifacts/empty.handlebars', function(err, stdout) {
9+
var cmd = './bin/handlebars';
10+
var args = [ '-a', 'spec/artifacts/empty.handlebars' ];
11+
12+
// On Windows, the executable handlebars.js file cannot be run directly
13+
if (os.platform() === 'win32') {
14+
args.unshift(cmd);
15+
cmd = process.argv[0];
16+
}
17+
childProcess.execFile(cmd, args, function(err, stdout) {
918
if (err) {
1019
throw err;
1120
}
@@ -32,7 +41,7 @@ module.exports = function(grunt) {
3241
grunt.registerTask('test:cov', function() {
3342
var done = this.async();
3443

35-
var runner = childProcess.fork('node_modules/.bin/istanbul', ['cover', '--', './spec/env/runner.js'], {stdio: 'inherit'});
44+
var runner = childProcess.fork('node_modules/istanbul/lib/cli.js', ['cover', '--source-map', '--', './spec/env/runner.js'], {stdio: 'inherit'});
3645
runner.on('close', function(code) {
3746
if (code != 0) {
3847
grunt.fatal(code + ' tests failed');

tasks/util/git.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ module.exports = {
8686
});
8787
},
8888
tagName: function(callback) {
89-
childProcess.exec('git describe --tags', {}, function(err, stdout) {
89+
childProcess.exec('git describe --tags --always', {}, function(err, stdout) {
9090
if (err) {
9191
throw new Error('git.tagName: ' + err.message);
9292
}

0 commit comments

Comments
 (0)