├── index.html ├── .gitignore ├── .travis.yml ├── page ├── xss.js └── index.html ├── karma.conf.js ├── gulpfile.js ├── bower.json ├── .jshintrc ├── package.json ├── karma.conf-ci.js ├── references.md ├── test ├── substitution.test.js ├── htmlPaser.test.js └── xss.test.js ├── securitySpec.md ├── README.md └── index.js /index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | coverage 4 | sauce_connect.log 5 | .env -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.0" 4 | addons: 5 | firefox: "42.0" 6 | before_script: 7 | - "export DISPLAY=:99.0" 8 | - "sh -e /etc/init.d/xvfb start" 9 | - sleep 3 # give xvfb some time to start 10 | after_script: "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" -------------------------------------------------------------------------------- /page/xss.js: -------------------------------------------------------------------------------- 1 | // @see https://developers.google.com/closure/templates/docs/security 2 | var xss = "javascript:/*/**/ /"; 3 | document.body.appendChild(html`${xss} 6 | 7 | `); -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | module.exports = function(config) { 3 | config.set({ 4 | basePath: '', 5 | frameworks: ['mocha', 'chai'], 6 | files: [ 7 | 'index.js', 8 | 'test/*.js' 9 | ], 10 | browsers: ['Chrome', 'Firefox', 'Safari'], 11 | reporters: ['progress', 'coverage'], 12 | preprocessors: { 13 | 'index.js': ['coverage'] 14 | }, 15 | coverageReporter: { 16 | dir : 'coverage/', 17 | reporters: [ 18 | {type: 'lcov', subdir: '.'}, 19 | {type: 'text-summary'} 20 | ] 21 | } 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /page/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |${xss}
`; 14 | document.body.appendChild(el); 15 | }); 16 | 17 | it('should prevent injection to non-quoted element attributes', function() { 18 | var el = html``; 19 | document.body.appendChild(el); 20 | }); 21 | 22 | it('should prevent injection to single quoted element attributes', function() { 23 | var el = html``; 24 | document.body.appendChild(el); 25 | }); 26 | 27 | it('should prevent injection to double quoted element attributes', function() { 28 | var el = html``; 29 | document.body.appendChild(el); 30 | }); 31 | 32 | it('should prevent injection as a javascript quoted string', function() { 33 | var el = html``; 34 | document.body.appendChild(el); 35 | }); 36 | 37 | it('should prevent injection on one side of a javascript quoted expression', function() { 38 | var el = html``; 39 | document.body.appendChild(el); 40 | }); 41 | 42 | it('should prevent injection into inlined quoted event handler', function() { 43 | var el = html`XSS <p> tag`; 44 | document.body.appendChild(el); 45 | el.click(); 46 | }); 47 | 48 | it('should prevent injection into quoted event handler', function() { 49 | var el = html`XSS <p> tag`; 50 | document.body.appendChild(el); 51 | el.click(); 52 | }); 53 | 54 | it('should prevent injection into CSS unquote property', function() { 55 | var el = html``; 56 | document.body.appendChild(el); 57 | }); 58 | 59 | it('should prevent injection into CSS quoted property', function() { 60 | var el = html``; 61 | document.body.appendChild(el); 62 | }); 63 | 64 | it('should prevent injection into CSS property of HTML style attribute', function() { 65 | var el = html``; 66 | document.body.appendChild(el); 67 | }); 68 | 69 | it('should prevent injection into query params of HTML urls', function() { 70 | var el = html``; 71 | document.body.appendChild(el); 72 | el.click(); 73 | }); 74 | 75 | it('should prevent injection into HREF attribute of tag', function() { 76 | var el = html`XSS'ed Link`; 77 | document.body.appendChild(el); 78 | el.click(); 79 | }); 80 | 81 | it('should prevent against clobbering of /attributes/', function() { 82 | var el = html``; 86 | document.body.appendChild(el); 87 | 88 | // el.submit() does not trigger a submit event, so we need to click the submit button 89 | // @see http://stackoverflow.com/questions/11557994/jquery-submit-vs-javascript-submit 90 | el.querySelector('input[type="submit"]').click(); 91 | }); 92 | 93 | it('should prevent injection out of a tag name by throwing an error', function() { 94 | var func = function() { 95 | var el = html``UNTRUSTED DATA` | HTML Entity Encoding |
8 | | String | Safe HTML Attributes | `` | `clickme` | URL Encoding |
10 | | String | Untrusted URL in a SRC or HREF attribute | `clickme``` | `Selection` | ```` | `UNTRUSTED HTML` | HTML Validation (JSoup, AntiSamy, HTML Sanitizer) |
14 | | String | DOM XSS | ``;
98 | ```
99 |
100 | - Except for alphanumeric characters, escape all characters less than 256 with the \xHH format to prevent switching out of the data value into the script context or into another attribute. DO NOT use any escaping shortcuts like \" because the quote character may be matched by the HTML attribute parser which runs first. These escaping shortcuts are also susceptible to "escape-the-escape" attacks where the attacker sends \" and the vulnerable code turns that into \\" which enables the quote.
101 |
102 | ### References
103 |
104 | 1. https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.233_-_JavaScript_Escape_Before_Inserting_Untrusted_Data_into_JavaScript_Data_Values
105 |
106 | ## As CSS Content
107 |
108 | ```js
109 | html``;
110 | ```
111 |
112 | - Please note there are some CSS contexts that can never safely use untrusted data as input - EVEN IF PROPERLY CSS ESCAPED! You will have to ensure that URLs only start with "http" not "javascript" and that properties never start with "expression".
113 | - Except for alphanumeric characters, escape all characters with ASCII values less than 256 with the \HH escaping format. DO NOT use any escaping shortcuts like \" because the quote character may be matched by the HTML attribute parser which runs first. These escaping shortcuts are also susceptible to "escape-the-escape" attacks where the attacker sends \" and the vulnerable code turns that into \\" which enables the quote.
114 | - If attribute is quoted, breaking out requires the corresponding quote. All attributes should be quoted but your encoding should be strong enough to prevent XSS when untrusted data is placed in unquoted contexts. Unquoted attributes can be broken out of with many characters including [space] % * + , - / ; < = > ^ and |. Also, the tag will close the style block even though it is inside a quoted string because the HTML parser runs before the JavaScript parser. Please note that we recommend aggressive CSS encoding and validation to prevent XSS attacks for both quoted and unquoted attributes.
115 |
116 | ### References
117 |
118 | 1. https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.234_-_CSS_Escape_And_Strictly_Validate_Before_Inserting_Untrusted_Data_into_HTML_Style_Property_Values
119 |
120 | ## Mix
121 |
122 | ```js
123 | html`${text} `;
124 | ```
125 |
126 | - ${attr}=${value} ${var} is difficult – was ${var} suppose to be part of ${attr} or it's own attribute?
127 | - unless the attribute was quoted, any whitespace will be treated as a new attribute, so in the example above ${var} would be a new attribute
128 |
129 | # References
130 |
131 | 1. https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
132 |
133 | # Idea on how to combine E4H principles and contextual auto escaping
134 |
135 | 1. replace all substitutions with placeholder values (their index in the array)
136 | 2. create DOM as normal using HTML parser (no chance for XSS since there is no user generated expression)
137 | - will need to check for correct closing tags/attributes or something so the HTML parser doesn't freak out
138 | 3. with the DOM created, we can use setAttribute() for safely encoding attribute values (prevent attribution injection) and use contextual auto escaping to figure out the rest
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/straker/html-tagged-template)
2 | [](https://coveralls.io/github/straker/html-tagged-template?branch=master)
3 |
4 | # Proposal
5 |
6 | Improve the DOM creation API so developers have a cleaner, simpler interface to DOM creation and manipulation.
7 |
8 | ## Installing
9 |
10 | `npm install html-tagged-template`
11 |
12 | or with Bower
13 |
14 | `bower install html-tagged-template`
15 |
16 | ## Usage
17 |
18 | ```js
19 | let min = 0, max = 99, disabled = true;
20 |
21 | // returns an tag with all attributes set
22 | // the use of ?= denotes an optional attribute which will only be added if the
23 | // value is true
24 | let el = html``;
25 | document.body.appendChild(el);
26 |
27 | // returns a DocumentFragment with two elements as children
28 | let el = html` `
29 | document.body.appendChild(el);
30 | ```
31 |
32 | ### Optional Attributes
33 |
34 | To add an attribute only when it's value is true (such as `disabled`), use `attrName?="${value}"`. If the value is true, the attribute will be added in the output, otherwise it will be omitted from the output.
35 |
36 | ## Contributing
37 |
38 | The only way this proposal will continue forward is with help from the community. If you would like to see the `html` function in the web, please upvote the [proposal on the W3C DOM repo](https://github.com/whatwg/dom/issues/150).
39 |
40 | If you find a bug or an XSS case that should to be handled, please submit an issue, or even better a PR with the relevant code to reproduce the error in the [xss test](test/xss.test.js).
41 |
42 | ## Problem Space
43 |
44 | The DOM creation API is a bit cumbersome to work with. To create a single element with several attributes requires several lines of code that repeat the same thing. The DOM selection API has received needed features that allow developers to do most DOM manipulation without needing a library. However, the DOM creation API still leaves something to be desired which sways developers from using it.
45 |
46 | Below are just a few examples of how DOM creation requires multiple lines of code to accomplish simple tasks and how developers tend to work around the API to gain access to a much simpler interface.
47 |
48 | ```js
49 | /*
50 | Create a single element with attributes:
51 |
52 | */
53 | let input = document.createElement('input');
54 | input.type = "number";
55 | input.min = 0;
56 | input.max = 99;
57 | input.name = 'number';
58 | input.id = 'number';
59 | input.classList.add('number-input');
60 | input.disabled = true;
61 | document.body.appendChild(input);
62 |
63 | // or the hacky way - create a throwaway parent node just to use innerHTML
64 | let div = document.createElement('div');
65 | div.innerHTML = '';
66 | document.body.appendChild(div.firstChild);
67 |
68 |
69 | /*
70 | Create an element with child elements:
71 |
72 |
73 |
74 | Hello
75 |
76 |
77 |
78 | */
79 | // use document fragment to batch appendChild calls for good performance
80 | let frag = document.createDocumentFragment();
81 | let div = document.createElement('div');
82 | div.classList.add('container');
83 | frag.appendChild(div);
84 |
85 | let row = document.createElement('div');
86 | row.classList.add('row');
87 | div.appendChild(row);
88 |
89 | let col = document.createElement('div');
90 | col.classList.add('col');
91 | row.appendChild(col);
92 |
93 | let child = document.createElement('div');
94 | child.appendChild(document.createTextNode('Hello')); // or child.textContext = 'Hello';
95 | col.appendChild(child);
96 | document.body.appendChild(frag);
97 |
98 | // or the convenient way using innerHTML
99 | let div = document.createElement('div');
100 | div.classList.add('container');
101 | div.innerHTML = 'Hello';
102 | document.body.appendChild(div);
103 |
104 |
105 | /*
106 | Create sibling elements to be added to a parent element:
107 |
108 |