Moderate severityOSV Advisory· Published Apr 8, 2019· Updated Aug 4, 2024
CVE-2019-11004
CVE-2019-11004
Description
In Materialize through 1.0.0, XSS is possible via the Toast feature.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
materialize-cssnpm | <= 1.0.0 | — |
@materializecss/materializenpm | < 1.1.0-alpha | 1.1.0-alpha |
Affected products
1- Range: 1.0.0, 1.0.0-alpha.1, 1.0.0-alpha.2, …
Patches
13aae4cc9bb2bfixes vulnerabilities in #38
9 files changed · +440 −57
jade/page-contents/autocomplete_content.html+8 −0 modified@@ -39,6 +39,8 @@ <div id="initialization" class="scrollspy section"> <h3 class="header">Initialization</h3> <p>The data is a json object where the key is the matching string and the value is an optional image url.</p> + <p>The key must be a text string. If you trust your data, or have properly sanitized your user input, you may + use HTML by setting the option <code class="language-javascript">allowUnsafeHTML: true</code>.</p> <pre><code class="language-javascript"> document.addEventListener('DOMContentLoaded', function() { var elems = document.querySelectorAll('.autocomplete'); @@ -112,6 +114,12 @@ <h3 class="header">Options</h3> <td></td> <td>Sort function that defines the order of the list of autocomplete options.</td> </tr> + <tr> + <td>allowUnsafeHTML</td> + <td>Boolean</td> + <td>false</td> + <td>If true will render the key from each item directly as HTML. User input MUST be properly sanitized first.</td> + </tr> </tbody> </table>
jade/page-contents/toasts_content.html+41 −10 modified@@ -4,14 +4,14 @@ <div id="introduction" class="section scrollspy"> <p>Materialize provides an easy way for you to send unobtrusive alerts to your users through toasts. These toasts are also placed and sized responsively, try it out by clicking the button below on different device sizes.</p> - <a class="waves-effect waves-light btn" onclick="M.toast({html: 'I am a toast'})">Toast!</a> + <a class="waves-effect waves-light btn" onclick="M.toast({text: 'I am a toast'})">Toast!</a> <p>To do this, call the M.toast() function programmatically in JavaScript.</p> <pre><code class="language-javascript"> - M.toast({html: 'I am a toast!'}) + M.toast({text: 'I am a toast!'}) </code></pre> <p>One way to add this into your application is to add this as an onclick event to a button.</p> <pre><code class="language-markup"> - <a onclick="M.toast({html: 'I am a toast'})" class="btn">Toast!</a> + <a onclick="M.toast({text: 'I am a toast'})" class="btn">Toast!</a> </code></pre> </div> @@ -30,11 +30,37 @@ <h3 class="header">Options</h3> </thead> <tbody> + <tr> + <td>text</td> + <td>String</td> + <td>''</td> + <td>The content of the Toast.</td> + </tr> + <tr> + <td>unsafeHTML</td> + <td>String, HTMLElement</td> + <td>''</td> + <td> + HTML content that will be appended to to <code class="language-javascript">text</code>. + Only use properly sanitized or otherwise trusted data for <code class="language-javascript">unsafeHTML</code>. + </td> + </tr> <tr> <td>html</td> <td>String</td> <td>''</td> - <td>The HTML content of the Toast.</td> + <td> + <p> + (DEPRECATED): will be removed in a later release. + </p> + <p> + HTML content that will be appended to <code class="language-javascript">text</code>. + Only use properly sanitized or otherwise trusted data for <code class="language-javascript">html</code>. + </p> + <p> + Will be ignored if <code class="language-javascript">unsafeHTML</code> is set. + </p> + </td> </tr> <tr> <td>displayLength</td> @@ -117,21 +143,26 @@ <h3 class="header">Properties</h3> <div id="custom-html" class="section scrollspy"> <h3 class="header">Custom HTML</h3> - <p>You can pass in an HTML String as the first argument as well. Take a look at the example below, where we pass in text as well as a flat button. If you call an external function instead of in-line JavaScript, you will not need to escape quotation marks. </p> + <p>You can pass in an HTML String as the first argument as well. Take a look at the example below, where we pass + in text as well as a flat button. If you call an external function instead of in-line JavaScript, you will not + need to escape quotation marks. </p> + <p> + Only use a properly sanitized or otherwise trusted HTML string. + </p> <a class="waves-effect waves-light btn" onclick="displayCustomHTMLToast()">Toast with Action</a> <pre><code class="language-javascript"> var toastHTML = '<span>I am toast content</span><button class="btn-flat toast-action">Undo</button>'; - M.toast({html: toastHTML}); + M.toast({unsafeHTML: toastHTML}); </code></pre> </div> <div id="callback" class="scrollspy section"> <h3 class="header">Callback</h3> <p>You can have the toast callback a function when it has been dismissed.</p> - <a class="btn" onclick="M.toast({html: 'I am a toast', completeCallback: function(){alert('Your toast was dismissed')}})">Toast!</a> + <a class="btn" onclick="M.toast({text: 'I am a toast', completeCallback: function(){alert('Your toast was dismissed')}})">Toast!</a> <pre><code class="language-markup"> - <a class="btn" onclick="M.toast({html: 'I am a toast', completeCallback: function(){alert('Your toast was dismissed')}})">Toast!</a> + <a class="btn" onclick="M.toast({text: 'I am a toast', completeCallback: function(){alert('Your toast was dismissed')}})">Toast!</a> </code></pre> </div> @@ -140,11 +171,11 @@ <h3 class="header">Callback</h3> <h3 class="header">Styling Toasts</h3> <p>We've added the ability to customize your toasts easily. You can pass in classes as an optional parameter into the toast function. We've added a rounded class for you, but you can create your own CSS classes and apply them to toasts. Checkout out our full example below.</p> - <a class="waves-effect waves-light btn" onclick="M.toast({html: 'I am a toast!', classes: 'rounded'})">Round Toast!</a> + <a class="waves-effect waves-light btn" onclick="M.toast({text: 'I am a toast!', classes: 'rounded'})">Round Toast!</a> <pre><code class="language-javascript"> // 'rounded' is the class I'm applying to the toast - M.toast({html: 'I am a toast!', classes: 'rounded'}); + M.toast({text: 'I am a toast!', classes: 'rounded'}); </code></pre> </div>
jade/page-contents/tooltips_content.html+25 −1 modified@@ -70,11 +70,35 @@ <h3 class="header">Options</h3> <td>0</td> <td>Delay time before tooltip appears.</td> </tr> + <tr> + <td>text</td> + <td>String</td> + <td></td> + <td>Text string for the tooltip.</td> + </tr> + <tr> + <td>unsafeHTML</td> + <td>String</td> + <td>null</td> + <td>HTML content that will be appended to to <code class="language-javascript">text</code>. + Only use properly sanitized or otherwise trusted data for <code class="language-javascript">unsafeHTML</code>.</td> + </tr> <tr> <td>html</td> <td>String</td> <td>null</td> - <td>Can take regular text or HTML strings.</td> + <td> + <p> + (DEPRECATED): will be removed in a later release. + </p> + <p> + HTML content that will be appended to <code class="language-javascript">text</code>. + Only use properly sanitized or otherwise trusted data for <code class="language-javascript">html</code>. + </p> + <p> + Will be ignored if <code class="language-javascript">unsafeHTML</code> is set. + </p> + </td> </tr> <tr> <td>margin</td>
js/autocomplete.js+32 −25 modified@@ -9,7 +9,8 @@ sortFunction: function(a, b, inputString) { // Sort function for sorting autocomplete results return a.indexOf(inputString) - b.indexOf(inputString); - } + }, + allowUnsafeHTML: false }; /** @@ -282,22 +283,14 @@ /** * Highlight partial match */ - _highlight(string, $el) { - let img = $el.find('img'); - let matchStart = $el - .text() - .toLowerCase() - .indexOf('' + string.toLowerCase() + ''), - matchEnd = matchStart + string.length - 1, - beforeMatch = $el.text().slice(0, matchStart), - matchText = $el.text().slice(matchStart, matchEnd + 1), - afterMatch = $el.text().slice(matchEnd + 1); - $el.html( - `<span>${beforeMatch}<span class='highlight'>${matchText}</span>${afterMatch}</span>` - ); - if (img.length) { - $el.prepend(img); - } + _highlight(input, label) { + const start = label.toLowerCase().indexOf('' + input.toLowerCase() + ''); + const end = start + input.length - 1; + //custom filters may return results where the string does not match any part + if (start == -1 || end == -1) { + return [label, '', '']; + } + return [label.slice(0, start), label.slice(start, end + 1), label.slice(end + 1)]; } /** @@ -376,18 +369,32 @@ // Render for (let i = 0; i < matchingData.length; i++) { - let entry = matchingData[i]; - let $autocompleteOption = $('<li></li>'); + const entry = matchingData[i]; + const item = document.createElement('li'); if (!!entry.data) { - $autocompleteOption.append( - `<img src="${entry.data}" class="right circle"><span>${entry.key}</span>` - ); + const img = document.createElement('img'); + img.classList.add("right", "circle"); + img.src = entry.data; + item.appendChild(img); + } + + const parts = this._highlight(val, entry.key); + const s = document.createElement('span'); + if (this.options.allowUnsafeHTML) { + s.innerHTML = parts[0] + '<span class="highlight">' + parts[1] + '</span>' + parts[2]; } else { - $autocompleteOption.append('<span>' + entry.key + '</span>'); + s.appendChild(document.createTextNode(parts[0])) + if (!!parts[1]){ + const highlight = document.createElement('span'); + highlight.textContent = parts[1]; + highlight.classList.add("highlight"); + s.appendChild(highlight); + s.appendChild(document.createTextNode(parts[2])); + } } + item.appendChild(s); - $(this.container).append($autocompleteOption); - this._highlight(val, $autocompleteOption); + $(this.container).append(item); } }
js/toasts.js+23 −18 modified@@ -3,6 +3,8 @@ let _defaults = { html: '', + unsafeHTML: '', + text: '', displayLength: 4000, inDuration: 300, outDuration: 375, @@ -18,7 +20,12 @@ * @member Toast#options */ this.options = $.extend({}, Toast.defaults, options); - this.message = this.options.html; + this.htmlMessage = this.options.html; + // If the new unsafeHTML is used, prefer that + if (!!this.options.unsafeHTML){ + this.htmlMessage = this.options.unsafeHTML; + } + this.message = this.options.text; /** * Describes current pan state toast @@ -188,28 +195,26 @@ $(toast).addClass(this.options.classes); } - // Set content + // Set safe text content + toast.textContent = this.message; if ( typeof HTMLElement === 'object' - ? this.message instanceof HTMLElement - : this.message && - typeof this.message === 'object' && - this.message !== null && - this.message.nodeType === 1 && - typeof this.message.nodeName === 'string' - ) { - toast.appendChild(this.message); - - // Check if it is jQuery object - } else if (!!this.message.jquery) { - $(toast).append(this.message[0]); - - // Insert as html; + ? this.htmlMessage instanceof HTMLElement + : this.htmlMessage && + typeof this.htmlMessage === 'object' && + this.htmlMessage !== null && + this.htmlMessage.nodeType === 1 && + typeof this.htmlMessage.nodeName === 'string' + ) { //if the htmlMessage is an HTML node, append it directly + toast.appendChild(this.htmlMessage); + } else if (!!this.htmlMessage.jquery) { // Check if it is jQuery object, append the node + $(toast).append(this.htmlMessage[0]); } else { - toast.innerHTML = this.message; + // Append as unsanitized html; + $(toast).append(this.htmlMessage); } - // Append toasft + // Append toast Toast._container.appendChild(toast); return toast; }
js/tooltip.js+16 −3 modified@@ -5,6 +5,8 @@ exitDelay: 200, enterDelay: 0, html: null, + text: '', + unsafeHTML: null, margin: 5, inDuration: 250, outDuration: 200, @@ -68,13 +70,24 @@ let tooltipContentEl = document.createElement('div'); tooltipContentEl.classList.add('tooltip-content'); - tooltipContentEl.innerHTML = this.options.html; + this._setTooltipContent(tooltipContentEl); + tooltipEl.appendChild(tooltipContentEl); document.body.appendChild(tooltipEl); } + _setTooltipContent(tooltipContentEl) { + tooltipContentEl.textContent = this.options.text; + if (!!this.options.html){ + $(tooltipContentEl).append(this.options.html); + } + if (!!this.options.unsafeHTML){ + $(tooltipContentEl).append(this.options.unsafeHTML); + } + } + _updateTooltipContent() { - this.tooltipEl.querySelector('.tooltip-content').innerHTML = this.options.html; + this._setTooltipContent(this.tooltipEl.querySelector('.tooltip-content')); } _setupEventHandlers() { @@ -285,7 +298,7 @@ let positionOption = this.el.getAttribute('data-position'); if (tooltipTextOption) { - attributeOptions.html = tooltipTextOption; + attributeOptions.text = tooltipTextOption; } if (positionOption) {
test/html/autocomplete.html+195 −0 added@@ -0,0 +1,195 @@ +<html> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"> + + <title>Materialize - Documentation</title> + + <!-- Favicons --> + <link rel="apple-touch-icon-precomposed" href="images/favicon/apple-touch-icon-152x152.png"> + <meta name="msapplication-TileColor" content="#FFFFFF"> + <meta name="msapplication-TileImage" content="images/favicon/mstile-144x144.png"> + <link rel="icon" href="../../images/favicon/favicon-32x32.png" sizes="32x32"> + + <!-- Android 5 Chrome Color --> + <meta name="theme-color" content="#EE6E73"> + + <link href="../../bin/materialize.css" type="text/css" rel="stylesheet" media="screen,projection" /> + <link href="http://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> + + <link href="../../css/prism.css" rel="stylesheet" /> + +</head> + +<body> + + <div class="container"> + + <h5>Default autocomplete example</h5> + <div class="row"> + <div class="col s12"> + <div class="row"> + <div class="input-field col s12"> + <i class="material-icons prefix">textsms</i> + <input type="text" id="autocomplete-1" class="autocomplete"> + <label for="autocomplete-1">Autocomplete</label> + </div> + </div> + </div> + </div> + + <h5>Custom filter function</h5> + <div class="row"> + <div class="col s12"> + <div class="row"> + <div class="input-field col s12"> + <i class="material-icons prefix">textsms</i> + <input type="text" id="autocomplete-2" class="autocomplete"> + <label for="autocomplete-2">Filter Function</label> + </div> + </div> + </div> + </div> + + <h5>Limit Set</h5> + <div class="row"> + <div class="col s12"> + <div class="row"> + <div class="input-field col s12"> + <i class="material-icons prefix">textsms</i> + <input type="text" id="autocomplete-3" class="autocomplete"> + <label for="autocomplete-3">Filter Function</label> + </div> + </div> + </div> + </div> + + <h5>allowUnsafeHTML: false</h5> + <div class="row"> + <div class="col s12"> + <div class="row"> + <div class="input-field col s12"> + <i class="material-icons prefix">textsms</i> + <input type="text" id="autocomplete-4" class="autocomplete"> + <label for="autocomplete-4">Filter Function</label> + </div> + </div> + </div> + </div> + + <h5>allowUnsafeHTML: true</h5> + <div class="row"> + <div class="col s12"> + <div class="row"> + <div class="input-field col s12"> + <i class="material-icons prefix">textsms</i> + <input type="text" id="autocomplete-5" class="autocomplete"> + <label for="autocomplete-5">Filter Function</label> + </div> + </div> + </div> + </div> + + <!-- + <h5>Custom dropdown options</h5> + <div class="row"> + <div class="col s12"> + <div class="row"> + <div class="input-field col s12"> + <i class="material-icons prefix">textsms</i> + <input type="text" id="autocomplete-3" class="autocomplete"> + <label for="autocomplete-3">Autocomplete</label> + </div> + </div> + </div> + </div> + + <h5>With chips</h5> + <div class="row"> + <div class="col s12"> + <div class="row"> + <div class="input-field col s12"> + <i class="material-icons prefix">textsms</i> + <input type="text" id="autocomplete-4" class="autocomplete"> + <label for="autocomplete-4">Autocomplete</label> + </div> + </div> + </div> + </div> + + <h5>Pre-populate chips</h5> + <div class="row"> + <div class="col s12"> + <div class="row"> + <div class="input-field col s12"> + <i class="material-icons prefix">textsms</i> + <input type="text" id="autocomplete-5" class="autocomplete"> + <label for="autocomplete-5">Autocomplete</label> + </div> + </div> + </div> + </div> --> + + </div> + + <!-- Scripts--> + <script src="https://code.jquery.com/jquery-2.1.1.min.js"></script> + <script src="../../bin/materialize.js"></script> + <script> + const defaultData = { + "Apple": null, + "Microsoft": null, + "Google": 'https://placehold.it/250x250' + }; + + const bigData = {}; + for (let i = 200; i >= 0; i--) { + const randString = 'a' + Math.random().toString(36).substring(2); + bigData[randString] = null; + } + + const a1 = { + data: defaultData + }; + + const a2 = { + data: defaultData, + filterFunction: function (key_string, filter_string) { + return true; + } + }; + + const a3 = { + data: bigData, + limit: 20 + } + + const unsafeData = { + "<span style='color: red;'>payload</span>Apple": null, + '<img src=/ onerror="alert(\'xss\')">typethis': null, + '<img src=/ onerror="alert(\'xss\')">still not safe': 'https://placehold.it/250x250' + } + + const a4 = { + data: unsafeData, + allowUnsafeHTML: false + } + + const a5 = { + data: unsafeData, + allowUnsafeHTML: true + } + + document.addEventListener('DOMContentLoaded', function () { + M.Autocomplete.init(document.getElementById('autocomplete-1'), a1); + M.Autocomplete.init(document.getElementById('autocomplete-2'), a2); + M.Autocomplete.init(document.getElementById('autocomplete-3'), a3); + M.Autocomplete.init(document.getElementById('autocomplete-4'), a4); + M.Autocomplete.init(document.getElementById('autocomplete-5'), a5); + }); + + </script> +</body> + +</html> \ No newline at end of file
test/html/toast.html+58 −0 added@@ -0,0 +1,58 @@ +<html> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"> + + <title>Materialize - Documentation</title> + + <!-- Favicons --> + <link rel="apple-touch-icon-precomposed" href="images/favicon/apple-touch-icon-152x152.png"> + <meta name="msapplication-TileColor" content="#FFFFFF"> + <meta name="msapplication-TileImage" content="images/favicon/mstile-144x144.png"> + <link rel="icon" href="../../images/favicon/favicon-32x32.png" sizes="32x32"> + + <!-- Android 5 Chrome Color --> + <meta name="theme-color" content="#EE6E73"> + + <link href="../../bin/materialize.css" type="text/css" rel="stylesheet" media="screen,projection" /> + <link href="http://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> + + <link href="../../css/prism.css" rel="stylesheet" /> + +</head> + +<body> + + <div class="container"> + <a onclick="toast1()" class="btn">Toast with text!</a> + <a onclick="toast2()" class="btn">Toast with html!</a> + <a onclick="toast3()" class="btn">Toast with unsafeHTML!</a> + <a onclick="toast4()" class="btn">Toast with unsafeHTML and text!</a> + + + </div> + + <!-- Scripts--> + <script src="../../bin/materialize.js"></script> + <script type="text/javascript"> + function toast1(){ + text = '<span>I am toast content</span><button class="btn-flat toast-action">Undo</button>'; + M.toast({text: text}); + } + function toast2(){ + text = '<span>I am toast content</span><button class="btn-flat toast-action">Undo</button>'; + M.toast({html: text}); + } + function toast3(){ + text = '<span>I am toast content</span><button class="btn-flat toast-action">Undo</button>'; + M.toast({unsafeHTML: text}); + } + function toast4(){ + text = '<span>I am toast content</span><button class="btn-flat toast-action">Undo</button>'; + M.toast({unsafeHTML: text, text: text}); + } + </script> +</body> + +</html> \ No newline at end of file
test/html/tooltip.html+42 −0 added@@ -0,0 +1,42 @@ +<html> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"> + + <title>Materialize - Documentation</title> + + <!-- Favicons --> + <link rel="apple-touch-icon-precomposed" href="images/favicon/apple-touch-icon-152x152.png"> + <meta name="msapplication-TileColor" content="#FFFFFF"> + <meta name="msapplication-TileImage" content="images/favicon/mstile-144x144.png"> + <link rel="icon" href="../../images/favicon/favicon-32x32.png" sizes="32x32"> + + <!-- Android 5 Chrome Color --> + <meta name="theme-color" content="#EE6E73"> + + <link href="../../bin/materialize.css" type="text/css" rel="stylesheet" media="screen,projection" /> + <link href="http://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> + + <link href="../../css/prism.css" rel="stylesheet" /> + +</head> + +<body> + + <div class="container"> + <a class="btn tooltipped" data-position="bottom" data-tooltip="I am a tooltip">Hover me!</a> + <a class="btn tooltipped" data-position="bottom" data-tooltip="<span class='color: red'>I am a tooltip</span>">Hover me!</a> + </div> + + <!-- Scripts--> + <script src="../../bin/materialize.js"></script> + <script type="text/javascript"> + document.addEventListener('DOMContentLoaded', function () { + var elems = document.querySelectorAll('.tooltipped'); + var instances = M.Tooltip.init(elems, {unsafeHTML: "<span class='color: red'>I am a tooltip</span>"}); + }); + </script> +</body> + +</html> \ No newline at end of file
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-rg3q-jxmp-pvjjghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-11004ghsaADVISORY
- github.com/Dogfalo/materialize/issues/6286ghsax_refsource_MISCWEB
- github.com/Dogfalo/materialize/issues/6331ghsaWEB
- github.com/materializecss/materialize/pull/49ghsaWEB
- github.com/samschurter/materialize/commit/3aae4cc9bb2b58c337bf25d2f04f129a2a0fa78fghsaWEB
News mentions
0No linked articles in our index yet.