@@ -89,6 +89,24 @@ describe('Meta service', () => {
8989 expect ( actual ! . getAttribute ( 'content' ) ) . toEqual ( '4321' ) ;
9090 } ) ;
9191
92+ it ( 'should not allow a custom selector to match off target elements like the body tag' , ( ) => {
93+ // This payload attempts to break out of the `meta[name="..."]` constraint entirely
94+ // and inject a comma to target arbitrary DOM elements like the `body` tag via the
95+ // `selector` argument of `updateTag`.
96+ const attackerSelector = 'name="description"], body, meta[name="pwned"' ;
97+
98+ const firstMeta = metaService . updateTag ( { content : 'pwned' } , attackerSelector ) ! ;
99+
100+ expect ( firstMeta ) . not . toBeNull ( ) ;
101+ // It creates a new meta element instead of targeting `body` because it did not
102+ // find a meta element matching the dirty selector since `body` is not a `meta` tag
103+ expect ( firstMeta ! . nodeName . toLowerCase ( ) ) . toEqual ( 'meta' ) ;
104+ expect ( firstMeta ! . getAttribute ( 'content' ) ) . toEqual ( 'pwned' ) ;
105+ expect ( doc . body . getAttribute ( 'content' ) ) . toBeNull ( ) ;
106+
107+ metaService . removeTagElement ( firstMeta ) ;
108+ } ) ;
109+
92110 it ( 'should extract selector from the tag definition' , ( ) => {
93111 const selector = 'property="fb:app_id"' ;
94112 metaService . updateTag ( { property : 'fb:app_id' , content : '666' } ) ;
@@ -137,6 +155,43 @@ describe('Meta service', () => {
137155 metaService . removeTagElement ( actual ) ;
138156 } ) ;
139157
158+ it ( 'should escape selector values when deriving the match selector' , ( ) => {
159+ // This payload attempts to prematurely close the attribute selector
160+ // and match another attribute.
161+ const property = 'fb:app_id"][content="123456789' ;
162+
163+ const meta = metaService . updateTag ( { property, content : 'pwned' } ) ! ;
164+
165+ expect ( meta ) . not . toBe ( defaultMeta ) ;
166+ expect ( meta . getAttribute ( 'property' ) ) . toEqual ( property ) ;
167+ expect ( meta . getAttribute ( 'content' ) ) . toEqual ( 'pwned' ) ;
168+ expect ( metaService . getTags ( 'property="fb:app_id"' ) . length ) . toEqual ( 1 ) ;
169+
170+ // clean up
171+ metaService . removeTagElement ( meta ) ;
172+ } ) ;
173+
174+ it ( 'should not let a quoted name break out of the meta selector and target body' , ( ) => {
175+ // This payload attempts to break out of the `meta[name="..."]` constraint entirely
176+ // and inject a comma to target arbitrary DOM elements like the `body` tag.
177+ const attackerName = 'description"], body' ;
178+
179+ const firstMeta = metaService . addTag ( { name : attackerName , content : 'safe' } ) ! ;
180+ const secondMeta = metaService . addTag ( { name : attackerName , content : 'safe' } ) ! ;
181+
182+ expect ( firstMeta ) . toBe ( secondMeta ) ;
183+ expect ( firstMeta . tagName ) . toEqual ( 'META' ) ;
184+ expect (
185+ Array . from ( doc . getElementsByTagName ( 'meta' ) ) . filter (
186+ ( meta ) => meta . getAttribute ( 'name' ) === attackerName ,
187+ ) . length ,
188+ ) . toEqual ( 1 ) ;
189+ expect ( doc . body ) . not . toBeNull ( ) ;
190+
191+ // clean up
192+ metaService . removeTagElement ( firstMeta ) ;
193+ } ) ;
194+
140195 it ( 'should add multiple new meta tags' , ( ) => {
141196 const nameSelector = 'name="twitter:title"' ;
142197 const propertySelector = 'property="og:title"' ;
0 commit comments