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:
@@ -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);
|
||||
|
||||
@@ -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+)$/;
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('test', [])
|
||||
.run(function($rootScope) {
|
||||
$rootScope.internalFnCalled = false;
|
||||
|
||||
$rootScope.internalFn = function() {
|
||||
$rootScope.internalFnCalled = true;
|
||||
};
|
||||
});
|
||||
@@ -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('');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user