fix(input): prevent browsers from autofilling hidden inputs

Autofilling with previous values (which will then be `$interpolate`ed) could lead to XSS or errors
This commit is contained in:
Jason Bedard
2018-09-27 01:00:54 -07:00
parent 09751be9bc
commit 6e3bbfc744
5 changed files with 137 additions and 2 deletions
+3 -2
View File
@@ -7,7 +7,7 @@
htmlAnchorDirective,
inputDirective,
inputDirective,
hiddenInputBrowserCacheDirective,
formDirective,
scriptDirective,
selectDirective,
@@ -221,7 +221,8 @@ function publishExternalAPI(angular) {
ngModelOptions: ngModelOptionsDirective
}).
directive({
ngInclude: ngIncludeFillContentDirective
ngInclude: ngIncludeFillContentDirective,
input: hiddenInputBrowserCacheDirective
}).
directive(ngAttributeAliasDirectives).
directive(ngEventDirectives);
+42
View File
@@ -2193,6 +2193,48 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
}];
var hiddenInputBrowserCacheDirective = function() {
var valueProperty = {
configurable: true,
enumerable: false,
get: function() {
return this.getAttribute('value') || '';
},
set: function(val) {
this.setAttribute('value', val);
}
};
return {
restrict: 'E',
priority: 200,
compile: function(_, attr) {
if (lowercase(attr.type) !== 'hidden') {
return;
}
return {
pre: function(scope, element, attr, ctrls) {
var node = element[0];
// Support: Edge
// Moving the DOM around prevents autofillling
if (node.parentNode) {
node.parentNode.insertBefore(node, node.nextSibling);
}
// Support: FF, IE
// Avoiding direct assignment to .value prevents autofillling
if (Object.defineProperty) {
Object.defineProperty(node, 'value', valueProperty);
}
}
};
}
};
};
var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
/**
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html ng-app="test">
<body ng-class="{hacked: internalFnCalled}">
<form>
<input id="input1" type="hidden" value="{{value}}" />
<input id="input2" type="hidden" ng-value="value" />
<textarea ng-model="value"></textarea>
</form>
<script src="angular.js"></script>
<script src="script.js"></script>
</body>
</html>
+11
View File
@@ -0,0 +1,11 @@
'use strict';
angular
.module('test', [])
.run(function($rootScope) {
$rootScope.internalFnCalled = false;
$rootScope.internalFn = function() {
$rootScope.internalFnCalled = true;
};
});
+68
View File
@@ -14,4 +14,72 @@ describe('hidden thingy', function() {
var expectedValue = browser.params.browser === 'safari' ? '{{ 7 * 6 }}' : '';
expect(element(by.css('input')).getAttribute('value')).toEqual(expectedValue);
});
it('should prevent browser autofill on browser.refresh', function() {
loadFixture('back2dom');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('');
element(by.css('textarea')).sendKeys('{{ internalFn() }}');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('body')).getAttribute('class')).toBe('');
browser.refresh();
expect(element(by.css('body')).getAttribute('class')).toBe('');
});
it('should prevent browser autofill on location.reload', function() {
loadFixture('back2dom');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('');
element(by.css('textarea')).sendKeys('{{ internalFn() }}');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('body')).getAttribute('class')).toBe('');
browser.driver.executeScript('location.reload()');
expect(element(by.css('body')).getAttribute('class')).toBe('');
});
it('should prevent browser autofill on history.back', function() {
loadFixture('back2dom');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('');
element(by.css('textarea')).sendKeys('{{ internalFn() }}');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('body')).getAttribute('class')).toBe('');
loadFixture('sample');
browser.driver.executeScript('history.back()');
expect(element(by.css('body')).getAttribute('class')).toBe('');
});
it('should prevent browser autofill on history.forward', function() {
loadFixture('sample');
loadFixture('back2dom');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('');
element(by.css('textarea')).sendKeys('{{ internalFn() }}');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('body')).getAttribute('class')).toBe('');
browser.driver.executeScript('history.back()');
browser.driver.executeScript('history.forward()');
expect(element(by.css('body')).getAttribute('class')).toBe('');
});
});