This is an automated email from the ASF dual-hosted git repository.
thiagohp pushed a commit to branch javax
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
The following commit(s) were added to refs/heads/javax by this push:
new ce496a84c TAP5-2811: fixing possible XSS in Confirm mixin JS
ce496a84c is described below
commit ce496a84c0f6f0f85bb4741758f56cd008008b39
Author: Thiago H. de Paula Figueiredo <[email protected]>
AuthorDate: Tue Aug 12 19:39:07 2025 -0300
TAP5-2811: fixing possible XSS in Confirm mixin JS
Thanks Yannick Dylla (https://github.com/ydylla) for bringing this to
our attention!
---
.../main/typescript/src/t5/core/confirm-click.ts | 17 +++--
.../main/typescript/src/t5/core/html-sanitizer.ts | 84 ++++++++++++++++++++++
tapestry-core/src/test/app1/ConfirmDemo.tml | 2 +-
.../integration/app1/ConfirmMixinTests.groovy | 3 +
.../integration/app1/pages/ConfirmDemo.java | 5 ++
5 files changed, 101 insertions(+), 10 deletions(-)
diff --git a/tapestry-core/src/main/typescript/src/t5/core/confirm-click.ts
b/tapestry-core/src/main/typescript/src/t5/core/confirm-click.ts
index 1a804f036..8e7a82df8 100644
--- a/tapestry-core/src/main/typescript/src/t5/core/confirm-click.ts
+++ b/tapestry-core/src/main/typescript/src/t5/core/confirm-click.ts
@@ -17,7 +17,8 @@
*/
import $ from "jquery";
-import "bootstrap/modal";
+import "bootstrap/modal";
+import sanitizeHtml from "t5/core/html-sanitizer";
/**
* Dialog options.
@@ -40,7 +41,7 @@ interface DialogOptions {
// options.okLabel - default "OK"
// options.cancelLabel - default "Cancel"
// options.ok - callback function, required
-const runDialog = function(options: DialogOptions) {
+export const runDialog = function(options: DialogOptions) {
let confirmed = false;
@@ -50,12 +51,12 @@ const runDialog = function(options: DialogOptions) {
<div class="modal-content">
<div class="modal-header">
<a class="close" data-dismiss="modal">×</a>
- <h3>${options.title || "Confirm"}</h3>
+ <h3>${sanitizeHtml(options.title || "Confirm")}</h3>
</div>
- <div class="modal-body">${options.message}</div>
+ <div class="modal-body">${sanitizeHtml(options.message)}</div>
<div class="modal-footer">
- <button class="btn ${options.okClass || "btn-warning"}"
data-dismiss="modal">${options.okLabel || "OK"}</button>
- <button class="btn btn-default"
data-dismiss="modal">${options.cancelLabel || "Cancel"}</button>
+ <button class="btn ${sanitizeHtml(options.okClass || "btn-warning")}"
data-dismiss="modal">${sanitizeHtml(options.okLabel || "OK")}</button>
+ <button class="btn btn-default"
data-dismiss="modal">${sanitizeHtml(options.cancelLabel || "Cancel")}</button>
</div>
</div>
</div>
@@ -123,6 +124,4 @@ $("body").on("click",
"[data-confirm-message]:not(.disabled)", function(event){
// of the
window.location.href = target.attr("href");
return false;
-});
-
-export default runDialog;
\ No newline at end of file
+});
\ No newline at end of file
diff --git a/tapestry-core/src/main/typescript/src/t5/core/html-sanitizer.ts
b/tapestry-core/src/main/typescript/src/t5/core/html-sanitizer.ts
new file mode 100644
index 000000000..cd6921341
--- /dev/null
+++ b/tapestry-core/src/main/typescript/src/t5/core/html-sanitizer.ts
@@ -0,0 +1,84 @@
+// Copyright 2025 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http:#www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * ## t5/core/html-sanitizer
+ *
+ * Provides a function that sanitizes HTML.
+ * @packageDocumentation
+ */
+export default function(html: string) {
+
+ if (html === null || html === undefined) {
+ return "";
+ }
+
+ // No elements, no problems
+ if (html.indexOf("<") < 0) {
+ return html;
+ }
+
+ // Mostly copied from
+ //
https://gomakethings.com/how-to-sanitize-html-strings-with-vanilla-js-to-reduce-your-risk-of-xss-attacks/
+
+ let parser = new DOMParser();
+ let document = parser.parseFromString(html, "text/html");
+
+ // Remove <script> and <iframe> elements
+
+ let scripts = document.querySelectorAll("script, iframe");
+ for (let script of scripts) {
+ script.remove();
+ }
+
+ let root = document.body;
+
+ clean(root);
+
+ return root.innerHTML;
+
+}
+
+// Recursively cleans the elements
+function clean(element: HTMLElement) {
+ removeDangerousAttributes(element);
+ for (let child of element.children) {
+ clean(child as HTMLElement);
+ }
+}
+
+// Remove on[something] attributes
+function removeDangerousAttributes(element: HTMLElement): void {
+
+ let attributes = element.attributes;
+ if (attributes != null) {
+ for (let {name, value} of attributes) {
+ if (isDangerousAttribute(name, value)) {
+ element.removeAttribute(name);
+ }
+ }
+ }
+
+}
+
+const DANGEROUS_ATTRIBUTE_NAMES = ['src', 'href', 'xlink:href'];
+
+function isDangerousAttribute(name: string, value: string) {
+ value = value.replace(/\s+/g, '').toLowerCase();
+ return name.toLocaleLowerCase().startsWith("on") ||
+ (DANGEROUS_ATTRIBUTE_NAMES.includes(name) && (
+ value.includes("javascript:") ||
+ value.includes("data:text/html")
+ ));
+}
diff --git a/tapestry-core/src/test/app1/ConfirmDemo.tml
b/tapestry-core/src/test/app1/ConfirmDemo.tml
index 774306fea..b97aa4768 100644
--- a/tapestry-core/src/test/app1/ConfirmDemo.tml
+++ b/tapestry-core/src/test/app1/ConfirmDemo.tml
@@ -2,7 +2,7 @@
<h1>Confirm Mixin Demo</h1>
- <t:actionlink t:id="confirmed" class="btn btn-primary"
t:mixins="confirm">Click This</t:actionlink>
+ <t:actionlink t:id="confirmed" class="btn btn-primary" t:mixins="confirm"
t:title="prop:title">Click This</t:actionlink>
</html>
diff --git
a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/ConfirmMixinTests.groovy
b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/ConfirmMixinTests.groovy
index 99cae0e6d..87d5ce6ae 100644
---
a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/ConfirmMixinTests.groovy
+++
b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/ConfirmMixinTests.groovy
@@ -12,6 +12,9 @@ class ConfirmMixinTests extends App1TestCase {
click "link=Click This"
waitForVisible "css=.modal-dialog"
+
+ // TAP5-2811: XSS
+ assertText("css=.modal-content h3", "something else");
clickAndWait "css=.modal-dialog .btn-warning"
diff --git
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ConfirmDemo.java
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ConfirmDemo.java
index 34270204f..c5b25001c 100644
---
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ConfirmDemo.java
+++
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ConfirmDemo.java
@@ -12,4 +12,9 @@ public class ConfirmDemo
{
alertManager.info("Action was confirmed.");
}
+
+ public String getTitle()
+ {
+ return "some<span><script>window.alert('ouch1');</script></span><em
onclick=\"window.alert('ouch2')\">thing</em> else";
+ }
}