Skip to content
Permalink
Browse files

fix(input): prevent browsers from autofilling hidden inputs

Autofilling with previous values (which will then be `$interpolate`ed) could lead to XSS or errors
  • Loading branch information
jbedard authored and petebacondarwin committed Sep 27, 2018
1 parent a8bfeff commit 7cbb1044fcb3576cdad791bd22ebea3dfd533ff8
@@ -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('');
});
});

0 comments on commit 7cbb104

Please sign in to comment.
You can’t perform that action at this time.