Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Apr 12, 2024. It is now read-only.
/angular.jsPublic archive

fix(jqLite): prevent possible XSS due to regex-based HTML replacement#17028

Merged
petebacondarwin merged 3 commits intoangular:masterfrommgol:xss-htmlprefilter
May 27, 2020

Conversation

mgol
Copy link
Member

AngularJS is in LTS mode

We are no longer accepting changes that are not critical bug fixes into this project.
Seehttps://blog.angular.io/stable-angularjs-and-long-term-support-7e077635ee9c for more detail.

Does this PR fix a regression since 1.7.0, a security flaw, or a problem caused by a new browser version?

Yes

What is the current behavior? (You can also link to an open issue here)

  1. The regex-based input HTML replacement may turn sanitized code into unsanitized one. An analogous jQuery advisory:GHSA-gxr4-xjj5-5px2
  2. Wrapping<option> elements in<select> ones changes parsing behavior, leading to possibly unsanitizing sanitized code. An analogous jQuery advisory:GHSA-jpcq-cgw6-v4j6

What is the new behavior (if this is a feature change)?

The issues are fixed. The second one is a breaking change so a new method restoring legacy insecure behavior:

angular.UNSAFE_restoreLegacyJqLiteXHTMLReplacement()

was added.

Does this PR introduce a breaking change?

Yes

Please check if the PR fulfills these requirements

  • The commit message follows ourguidelines
  • Fix/Feature:Docs have been added/updated
  • Fix/Feature: Tests have been added; existing tests pass

Other information:

w9w reacted with eyes emoji
// the select element.
if (msie < 10) {
wrapMap.optgroup = wrapMap.option = [1, '<select multiple="multiple">', '</select>'];
}
Copy link
Contributor

@kotokotoMay 20, 2020
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm not sure if you actually need the wrapping at all in modern browsers. At least in Chrome appending the elements directly works, even if, say,td is not undertbody ortable.

In any case, while the current patch fixes the security bug we currently know about, a better way would be to avoid concatenating HTML strings whatsoever. If, for example, you need wrapping, usedocument.createElement andappendChild and append to the newly created one.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm not sure if you actually need the wrapping at all in modern browsers.

You do. Seehttps://jsbin.com/cibiwet/edit?html,js,console, it doesn't actually append the element, either in Firefox or in Chrome.

I'm curious how it worked for you, my test case is pretty basic.

If, for example, you need wrapping, usedocument.createElement andappendChild and append to the newly created one.

That would work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Heh, my bad. I would've sworn this worked last week (I was debugging jqLite, so all calls were inside a fragment too), but indeed it does not (only<option> works), sorry for the confusion.

window.setTimeout(function() {
expect(window.xss).not.toHaveBeenCalledWith(index);
donePartial();
}, 1000);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Is it possible to get a false-positive (i.e. an anti-flake)?
If the timeout completes before the async changes have occurred, it is possible that the test could have failed if the timeout were longer.

I guess that theonerror handler is always called async?

Could we avoid relying upon the timeout being long enough. Perhaps create an additional "real" error that will call a differentonerror handler? Then assume that if this second handler is called but thexss one is not called then we are good... Then we could make theit async and only calldone() once the second real error handler has been called.

gkalpak reacted with thumbs up emoji
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Feel free to experiment. I've tried a few things when I prepared a similar patch for jQuery & I wasn't able to achieve something that would not have the delay and that wouldn't suffer from race conditions. Maybe there's something but it might require extensive testing - we definitely don't want to miss the error just because it fired too late.

src/jqLite.js Outdated
@@ -215,7 +229,10 @@ function jqLiteBuildFragment(html, context) {
tmp = fragment.appendChild(context.createElement('div'));
tag = (TAG_NAME_REGEXP.exec(html) || ['', ''])[1].toLowerCase();
wrap = wrapMap[tag] || wrapMap._default;
Copy link
Contributor

@kotokotoMay 20, 2020
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

You could get rid of_default, e.g. like this:

wrap=wrapMap[tag]finalHTML= ...tmp.innerHTML=wrap ?wrap[1]+finalHtml+wrap[2]  :finalHtml;i=wrap ?wrap[0] :0;while(i--){//...

That's a no-op for the security fix, but is shorter - and lets you pipe through the data without any string concatenation (e.g. letting throughTrustedHTML objects that DOMPurify might return).

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Sound sensible. That said, this would technically be a new feature and we're at the point where we mostly care about security fixes so I wouldn't want to spend too much time on tweaking this code.

Copy link
MemberAuthor

@mgolmgolMay 20, 2020
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

OK, apparently we have a few days to finish the PR; in that case, I can have a look early next week.

'<noscript/><img src=url404 onerror=xss(10)>',
'<noembed><noembed/><img src=url404 onerror=xss(11)>',

'<option><style></option></select><img src=url404 onerror=xss(12)></style>'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I think that the following test cases were useful for jQuery (which used a slightly different regex) but not for jqLite'sXHTML_TAG_REGEXP (i.e. they would pass without the changes in this PR, so they don't add much value):

  • '<img alt="<x" title="/><img src=url404 onerror=xss(0)>">'
  • '<img alt="\n<x" title="/>\n<img src=url404 onerror=xss(1)>">'
  • '<foo" alt="" title="/><img src=url404 onerror=xss(8)>">'
  • '<img alt="<x" title="" src="/><img src=url404 onerror=xss(9)>">'

(That is because jQuery's tegex would match" as part of the tag name, while jqLite won't.)

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

We’re testing Angular with jQuery as well here. I’d leave them all; the purpose is not to match only what was broken but to prevent future regressions here.

gkalpak and Splaktar reacted with thumbs up emoji
@mgolmgolforce-pushed thexss-htmlprefilter branch 4 times, most recently from0b474b3 tof21f2d8CompareMay 26, 2020 15:06
@mgol
Copy link
MemberAuthor

mgol commentedMay 26, 2020
edited
Loading

@koto I refactored the whole thing to not rely on passing strings toinnerHTML at all except in IE 9 where it's unavoidable - IE 9 doesn't let you to assign'<td></td>' to atr element even if it lies in a correct table structure; it needs the whole HTML up front.

This makes the PR increase the minified size by407 bytes (before gzip) due to duplication but at least it should be more secure in modern browsers.

Please take a look.

Splaktar reacted with thumbs up emoji

Copy link
Contributor

@petebacondarwinpetebacondarwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

On master theangular.min.js file is 176333 bytes.
This PR appears to make it 176724 (391 byte increase over master).
My two suggestions appear to make it 176544 (211 byte increase over master).

This also splits the wrapping logic to one for modern browsers & one for IE 9as IE 9 has restrictions that make it impossible to make it as secure.
@mgol
Copy link
MemberAuthor

@petebacondarwin PR updated.

Copy link
Contributor

@petebacondarwinpetebacondarwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

We should see if@koto has any more feedback before we merge, yes?

@koto
Copy link
Contributor

It looks good (thanks!), but I have a small suggestion (and I realise this is not about fixing the bug, but rather adding support for passingTrustedHTML objects to jqLite).

You don't modify the payload injqLiteBuildFragment anymore for most cases, butTrustedHTML would still not be able to be passed there, as it fails theargIsString check inJQLite function, TrustedHTMLs are objects -typeof trustedTypes.emptyHTML == 'object'.

How about changing it to :

if(argIsString||(typeofTrustedHTML=="function"&&elementinstanceofTrustedHTML)){jqLiteAddNodes(this,jqLiteParseHTML(element));}// ...

that would pipe the TrustedHTML objects tojqLiteBuildFragment.

For testing, one can create TrustedHTML objects like so:

if(window.trustedTypes){varpolicy=trustedTypes.createPolicy('jqlitetests',{createHTML:function(s){returns}});vartrustedHTML=policy.createHTML('<div><img src=x></div>');}

@mgol
Copy link
MemberAuthor

mgol commentedMay 26, 2020
edited
Loading

@koto

if(argIsString||(typeofTrustedHTML=="function"&&elementinstanceofTrustedHTML)){jqLiteAddNodes(this,jqLiteParseHTML(element));}// ...

This won't work cross-frame, will it? I'm not sure if that's an important or even supported use case for AngularJS, I know this would be a big roadblock for jQuery.

In any case, this is getting a bit far from the original purpose of this PR. I may not have much more time to spend on this PR and if we want to add explicit support for trusted types, we should carefully look at all existing jqLite APIs likeafter and make sure they work as well by adding tests for them all. Can we merge this PR as-is and then handle remaining issues with trusted types in a separate one? This would also be clearer in the Git log.

A small offtop: as I'm involved in jQuery as well, I've been thinking about including similar improvements to jQuery. AngularJS has included code specific to APIs not available cross-browser like Chrome apps and now possibly trusted types but jQuery has historically avoided such things. AvoidinginnerHTML for wrapping like done in this PR would work but we wouldn't want to mention theTrustedHTML constructor explicitly as long as it's a Blink-only feature. Do you think there's any other way to support this use case without mentioning any trusted types API explicitly? I'm asking here as if it's possible maybe the same technique could be applied here. If you prefer to continue this part of the discussion on the jQuery side, there's an open issue about trusted types support:jquery/jquery#4409.

Splaktar and koto reacted with thumbs up emoji

@Splaktar

This comment has been minimized.

@mgol

This comment has been minimized.

Co-authored-by: Michael Prentice <splaktar@gmail.com>
Copy link
Contributor

@SplaktarSplaktar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

LGTM

@petebacondarwin
Copy link
Contributor

@koto - thanks for your review.

Regarding the suggestion of handling passingTrustedHTML objects to jqLite. I think it is a good idea, but given the current status of the AngularJS project, I am loathe for us to make any more changes than necessary to the code. Any change we make is a potential opportunity to open up issues for projects that are already out there in the wild. This would make more work for us, and potentially lead to yet more changes in the project.

As I understand it, the suggestion is not an essential fix to help with security, right? If so, then we should leave the PR as it is here, get it merged and release AngularJS to mitigate the current security concern. Does that sounds reasonable or am I missing something?

@petebacondarwinpetebacondarwin merged commitc8b7c16 intoangular:masterMay 27, 2020
@koto
Copy link
Contributor

@koto

if(argIsString||(typeofTrustedHTML=="function"&&elementinstanceofTrustedHTML)){jqLiteAddNodes(this,jqLiteParseHTML(element));}// ...

This won't work cross-frame, will it?

Correct. Trusted Types don't work if passed to different realms (i.e. DOM would reject them if Types are enforced), so it makes sense that TT would not be recognised as special values in jqLite as well (instanceof check would fail). But that doesn't break the application, unless it already opted into enforcing Trusted Types via CSP. In such case there's no way out of refactoring the application into creating a trusted type locally.

I'm not sure if that's an important or even supported use case for AngularJS, I know this would be a big roadblock for jQuery.

I'd like to understand this more. jQuery would be a TT consumer, the intention of a potential PR is not to make all applications using jQuery compliant with TT restrictions, but only to make apps that already create types able to use jQuery without losing them. Some refactoring is needed at the application side (to create types in the first place, but also to possibly change insecure patterns) But this is off-topic here, let's discuss in jQuery or separate bugs.

In any case, this is getting a bit far from the original purpose of this PR. I may not have much more time to spend on this PR and if we want to add explicit support for trusted types, we should carefully look at all existing jqLite APIs likeafter and make sure they work as well by adding tests for them all. Can we merge this PR as-is and then handle remaining issues with trusted types in a separate one? This would also be clearer in the Git log.

Fully agreed.

A small offtop: as I'm involved in jQuery as well, I've been thinking about including similar improvements to jQuery. AngularJS has included code specific to APIs not available cross-browser like Chrome apps and now possibly trusted types but jQuery has historically avoided such things. AvoidinginnerHTML for wrapping like done in this PR would work but we wouldn't want to mention theTrustedHTML constructor explicitly as long as it's a Blink-only feature. Do you think there's any other way to support this use case without mentioning any trusted types API explicitly? I'm asking here as if it's possible maybe the same technique could be applied here. If you prefer to continue this part of the discussion on the jQuery side, there's an open issue about trusted types support:jquery/jquery#4409.

Let's move this to jQuery. Thanks Michał and all for the fix!

Splaktar reacted with thumbs up emoji

mgol added a commit to mgol/jquery that referenced this pull requestJun 1, 2020
Concatenating HTML strings in buildFragment is a possible security risk as itcreates an opportunity of escaping the concatenated wrapper. It also makes itimpossible to support secure HTML wrappers like[trusted types](https://web.dev/trusted-types/). It's safer to create wrapperelements using `document.createElement` & `appendChild`.The previous way was needed in jQuery <4 because IE <10 doesn't accept tableparts set via `innerHTML`, even if the element which contents are set isa proper table element, e.g.:```jstr.innerHTML = "<td></td>";```The whole structure needs to be passed in one HTML string. jQuery 4 dropssupport for IE <11 so this is no longer an issue; in older version we'd have toduplicate the code paths.IE <10 needed to have `<option>` elements wrapped in`<select multiple="multiple">` but we no longer need that on master which makesthe `document.createElement` way shorter as we don't have to call`setAttribute`.jQuery 1.x sometimes needed to have more than one element in the wrapper thatwould precede parts wrapping HTML input so descending needed to use `lastChild`.Since all wrappers are single-element now, we can use `firstChild` whichcompresses better as it's used in other places in the code as well.All these improvements, apart from making logic more secure, decrease thegzipped size by 55 bytes.Refjquerygh-4409Refangular/angular.js#17028
evilaliv3 added a commit to globaleaks/globaleaks-whistleblowing-software that referenced this pull requestJun 4, 2020
@mgolmgol deleted the xss-htmlprefilter branchJune 4, 2020 21:36
mgol added a commit to mgol/jquery that referenced this pull requestJun 5, 2020
Concatenating HTML strings in buildFragment is a possible security risk as itcreates an opportunity of escaping the concatenated wrapper. It also makes itimpossible to support secure HTML wrappers like[trusted types](https://web.dev/trusted-types/). It's safer to create wrapperelements using `document.createElement` & `appendChild`.The previous way was needed in jQuery <4 because IE <10 doesn't accept tableparts set via `innerHTML`, even if the element which contents are set isa proper table element, e.g.:```jstr.innerHTML = "<td></td>";```The whole structure needs to be passed in one HTML string. jQuery 4 dropssupport for IE <11 so this is no longer an issue; in older version we'd have toduplicate the code paths.IE <10 needed to have `<option>` elements wrapped in`<select multiple="multiple">` but we no longer need that on master which makesthe `document.createElement` way shorter as we don't have to call`setAttribute`.jQuery 1.x sometimes needed to have more than one element in the wrapper thatwould precede parts wrapping HTML input so descending needed to use `lastChild`.Since all wrappers are single-element now, we can use `firstChild` whichcompresses better as it's used in other places in the code as well.All these improvements, apart from making logic more secure, decrease thegzipped size by 55 bytes.Refjquerygh-4409Refangular/angular.js#17028
nskazki added a commit to Crowd9/angular.js that referenced this pull requestJun 8, 2020
mgol added a commit to jquery/jquery that referenced this pull requestJun 10, 2020
Concatenating HTML strings in buildFragment is a possible security risk as itcreates an opportunity of escaping the concatenated wrapper. It also makes itimpossible to support secure HTML wrappers like[trusted types](https://web.dev/trusted-types/). It's safer to create wrapperelements using `document.createElement` & `appendChild`.The previous way was needed in jQuery <4 because IE <10 doesn't accept tableparts set via `innerHTML`, even if the element which contents are set isa proper table element, e.g.:```jstr.innerHTML = "<td></td>";```The whole structure needs to be passed in one HTML string. jQuery 4 dropssupport for IE <11 so this is no longer an issue; in older version we'd haveto duplicate the code paths.IE <10 needed to have `<option>` elements wrapped in`<select multiple="multiple">` but we no longer need that on master whichmakes the `document.createElement` way shorter as we don't have to call`setAttribute`.All these improvements, apart from making logic more secure, decrease thegzipped size by 58 bytes.Closesgh-4724Refgh-4409Refangular/angular.js#17028Co-authored-by: Richard Gibson <richard.gibson@gmail.com>
LeSuisse added a commit to Enalean/tuleap that referenced this pull requestJun 23, 2020
This new version of AngularJS fixes a security issue (CVE-2020-7676).angular/angular.js#17028https://github.com/angular/angular.js/blob/v1.8.0/CHANGELOG.mdChange-Id: Id5a19f74e025c4de4ec2e6170eb4f01a6a606328
PPInfy added a commit to PPInfy/angular.js that referenced this pull requestOct 5, 2020
@zelivans
Copy link

It appearsCVE-2020-7676 was assigned in association with this PR.
The CVE description mentions the the two problems fixed here so it is currently confusing. Not sure if it should be split, up to the maintainers.

@mgol
Copy link
MemberAuthor

mgol commentedOct 8, 2020

@zelivans The two issues are caused by code in the same area and they're quite similar so it was decided to use one only. Equivalent jQuery issues had two CVEs created.

I think it's fine both ways.

evilaliv3 added a commit to globaleaks/globaleaks-whistleblowing-software that referenced this pull requestJul 27, 2021
mgoldspink-salesforce added a commit to vlocityinc/angular.js that referenced this pull requestJan 20, 2022
mgoldspink-salesforce added a commit to vlocityinc/angular.js that referenced this pull requestJan 25, 2022
GHSA-mhp6-pxh8-r675NOTE: since we only support using with jquery 3.5.1 in Salesforce/vlocity we've modified the tests to account for this and drop older versions of jquery.
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.
Reviewers

@kotokotokoto left review comments

@SplaktarSplaktarSplaktar approved these changes

@petebacondarwinpetebacondarwinpetebacondarwin approved these changes

@gkalpakgkalpakgkalpak approved these changes

Assignees
No one assigned
Projects
None yet
Milestone
1.8.x
Development

Successfully merging this pull request may close these issues.

7 participants
@mgol@koto@Splaktar@petebacondarwin@zelivans@gkalpak@googlebot

[8]ページ先頭

©2009-2025 Movatter.jp