Skip to content

Fix quote and ampersand HTML escape in dropdown#3205

Merged
lubber-de merged 13 commits intofomantic:developfrom
mvorisek:escape_amp
Mar 26, 2025
Merged

Fix quote and ampersand HTML escape in dropdown#3205
lubber-de merged 13 commits intofomantic:developfrom
mvorisek:escape_amp

Conversation

@mvorisek
Copy link
Copy Markdown
Contributor

@mvorisek mvorisek commented Mar 10, 2025

merge after #3224

fix #3199

@mvorisek mvorisek marked this pull request as draft March 10, 2025 22:54
@mvorisek mvorisek changed the title Fix ampersand HTML escape Fix ampersand HTML escape in dropdown Mar 10, 2025
@mvorisek mvorisek changed the title Fix ampersand HTML escape in dropdown Fix quote and ampersand HTML escape in dropdown Mar 11, 2025
@mvorisek mvorisek marked this pull request as ready for review March 11, 2025 11:48
@auto-assign auto-assign bot requested a review from lubber-de March 11, 2025 11:48
@mvorisek
Copy link
Copy Markdown
Contributor Author

mvorisek commented Mar 11, 2025

Test strings with preserveHTML = false:

$makeTestStringFx = static fn ($v) => $v . ' <b>"\' &lt;';
$htmlValues = [
    $makeTestStringFx('d') => $makeTestStringFx('dTitle'),
    $makeTestStringFx('u') => $makeTestStringFx('uTitle'),
];

before:
image
image

after:
image
image

@mvorisek
Copy link
Copy Markdown
Contributor Author

For unknown reasons to me, multiple values are escaped by default by get values API in https://github.com/fomantic/Fomantic-UI/blob/2.9.4/src/definitions/modules/dropdown.js#L2012. This is addressed in de27144 to fix #3199 (comment).

I tested both (t/f) preserveHTML and this PR fixes all #3199 issues.

@mvorisek mvorisek marked this pull request as draft March 12, 2025 10:00
Copy link
Copy Markdown
Member

@lubber-de lubber-de left a comment

Choose a reason for hiding this comment

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

I took the time and went through (possibly) all related old issues and PRs the past years.
Most of them work fine with this PR. 👍

However, i found 3 of them which are now broken when this PR (de27144) is applied:

1. Original Issue: #1373, Original PR: #1770

FUI 2.9.4

https://jsfiddle.net/lubber/23htdnw0/1/
--> works

Your PR applied

https://jsfiddle.net/lubber/gmh5reoy/5/
--> fails (even though preserveHTML is true, the labels are encoded)
--> fails (when preserveHTML is false, ampersand is double encoded)

2. Original Issue: #2782, Original PR: #2790

Mentioned example c)

FUI 2.9.4

https://jsfiddle.net/lubber/q8hL21yg/1/
--> works

Your PR applied

https://jsfiddle.net/lubber/zap03L4k/5/
--> fails (Ampersand always encoded even when preserveHTML is true.
--> fails (When preserveHTML is false, the ampersand is double encoded)

3. Original Issue: #2817, Original PR: #2705

FUI 2.9.4

https://jsfiddle.net/lubber/97druzp4/1/
--> works

Your PR applied

https://jsfiddle.net/lubber/rhs4aq7u/3/
--> fails (always encoded)

@mvorisek
Copy link
Copy Markdown
Contributor Author

Thank you for the extensive review.

I extracted changes I consider safe into #3212 to minimize the LoC changed of this PR to discuss it later.

lubber-de pushed a commit that referenced this pull request Mar 13, 2025
extracted from #3205

We use here a trick that HTML attributes can be escaped the same way as HTML values - jsfiddle.net/45q9zL7j, thus HTML attributes does not need a separate escape function.

Under normal usage this PR should imply no function change.
lubber-de pushed a commit that referenced this pull request Mar 14, 2025
extracted from #3205

The new code is simpler, faster and easy to fix ampersand escape later.

No functional change.
@mvorisek mvorisek force-pushed the escape_amp branch 3 times, most recently from 85ee58e to cc0663a Compare March 16, 2025 00:07
@mvorisek
Copy link
Copy Markdown
Contributor Author

Here is a jsfiddle where XSS takes place inside FUI when preserveHTML is false and the dropdown is NOT multiple: https://jsfiddle.net/8rabcw71/1/

Paste "<script>alert('gotcha!')</script>" into the second dropdown and it gets executed immedialty. This way an attacker could add someting less obvious like <img onload="some_evil_stuff()" style="visibility:hidden;"><b>hi there</b> Also the dropdown kinda gets stuck afterwards....

Good catch, fixed.

@lubber-de
Copy link
Copy Markdown
Member

Two things left for me atm:

1st

The fix from fddfe7a should also be applied to

html = settings.templates.addition(module.add.variables(message.addResult, value));

so it also becomes

html = settings.templates.addition(module.add.variables(message.addResult, settings.templates.escape(value, settings)));

2nd

As i explained the meaning of preserveHTML in #3205 (comment)
The check at

module.add.userSuggestion(settings.preserveHTML

should be vice versa.

It should be either

module.add.userSuggestion(!settings.preserveHTML
	? module.escape.htmlEntities(query)
	: query);

or

module.add.userSuggestion(settings.preserveHTML
	? query
	: module.escape.htmlEntities(query));

But the related change was implemented in #3224, so it might be adjusted there, if you want the other PR to be merged first

@mvorisek
Copy link
Copy Markdown
Contributor Author

mvorisek commented Mar 25, 2025

1st

Fixed by me, my mistake. I now checked all .html( inputs.

2nd

You are worried about a995f2f. No, it makes sense - with preserveHTML = true we want to preserve HTML, but not from user input :), thus we encode the input first and then preserve it everywhere else as other values. With preserveHTML = false the input is text already, no need to encode it, we encode it only for HTML render.

@lubber-de
Copy link
Copy Markdown
Member

lubber-de commented Mar 26, 2025

No, it makes sense - with preserveHTML = true we want to preserve HTML, but not from user input :),

Sure thing 👍🏼 That's why it was basically always encoded before just in case.

thus we encode the input first and then preserve it everywhere else as other values. With preserveHTML = false the input is text already, no need to encode it, we encode it only for HTML render.

Alright. I debugged it down now more deeply and i can confirm, after your latest fixes, that the usersuggestion is now safely encoded when finally being added to the DOM 🥳

Just to clarify/summarize (for myself, if i ever need to rethink/reconfirm this in some later situation/refactoring)

  1. The query variable, when preserveHTML is false, is still unsanitized when provided to the module.filter() function because it just gets the .val() from the input, no matter what there might be HTML inside
  2. It is even still unsanitized, when preserveHTML is false, when provided to the module.add.userSuggestion function

Those 2 situations, in addition to the found XSS use case yesterday, made me not believing the code was correct... until today 😉

  1. BUT: inside module.add.userSuggestion the value GETS encoded when preserveHTML is false: The two LOC where settings.templates.addition() is called (that was fixed in the latest commits)

@mvorisek Thanks for being patient with me (again) and explaining all my confusion

I will now look into the other modules usages for the escape method which were also adjusted in this PR to finally merge your both PRs

lubber-de pushed a commit that referenced this pull request Mar 26, 2025
extracted from #3205

And fix many double encoding issues.

All escape functions must be lossless and must return the same value when called again on a reverse function.

When using preserveHTML=false and the text is read from innerHTML input (<option>xxx</option>), &amp; must be encoded in HTML code as &amp;amp; to be preserved 1:1, This is due & -> &amp; browser HTML normalization. Explained in [1], [2], [3].

[1] original issue: #2782
[2] browser behaviour: jsfiddle.net/wdyjfvz0
[3] how to encode innerHTML for <option> tag: 3v4l.org/E2BQY
Copy link
Copy Markdown
Member

@lubber-de lubber-de left a comment

Choose a reason for hiding this comment

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

LGTM, Thanks for your patience :)

@lubber-de lubber-de merged commit 180c662 into fomantic:develop Mar 26, 2025
8 checks passed
@lubber-de lubber-de added type/bug Any issue which is a bug or PR which fixes a bug lang/javascript Anything involving JavaScript labels Mar 26, 2025
@lubber-de lubber-de added this to the 2.10.0 milestone Mar 26, 2025
@mvorisek mvorisek deleted the escape_amp branch March 26, 2025 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lang/javascript Anything involving JavaScript type/bug Any issue which is a bug or PR which fixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Dropdown] multiple issues with remote data and multiple

2 participants