CVE-2024-28245: Insufficient Information (NVD-CWE-noinfo)
value: KaTeX is a JavaScript library for TeX math rendering on the web. KaTeX users who render untrusted mathematical expressions could encounter malicious input using `\includegraphics` that runs arbitrary JavaScript, or generate invalid HTML. Upgrade to KaTeX v0.16.10 to remove this vulnerability.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-03-25T20:15Z
Repository: https://github.com/KaTeX/KaTeX
Language: JavaScript
Files changed: src/domTree.js, test/katex-spec.js
Commit date: 2024-03-24 17:55:44 -0400
Commit message: fix: escape \includegraphics src and alt

Diff: diff --git a/src/domTree.js b/src/domTree.js
index 6b4e5f16..4ace3d12 100644
--- a/src/domTree.js
+++ b/src/domTree.js
@@ -313,11 +313,12 @@ export class Img implements VirtualNode {
 
         return node;
     }
 
     toMarkup(): string {
-        let markup = `<img  src='${this.src} 'alt='${this.alt}' `;
+        let markup = `<img src="${utils.escape(this.src)}"` +
+          ` alt="${utils.escape(this.alt)}"`;
 
         // Add the styles, after hyphenation
         let styles = "";
         for (const style in this.style) {
             if (this.style.hasOwnProperty(style)) {
@@ -510,11 +511,11 @@ export class SvgNode implements VirtualNode {
         let markup = `<svg xmlns="http://www.w3.org/2000/svg"`;
 
         // Apply attributes
         for (const attr in this.attributes) {
             if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
-                markup += ` ${attr}='${this.attributes[attr]}'`;
+                markup += ` ${attr}="${utils.escape(this.attributes[attr])}"`;
             }
         }
 
         markup += ">";
 
@@ -551,13 +552,13 @@ export class PathNode implements VirtualNode {
         return node;
     }
 
     toMarkup(): string {
         if (this.alternate) {
-            return `<path d='${this.alternate}'/>`;
+            return `<path d="${utils.escape(this.alternate)}"/>`;
         } else {
-            return `<path d='${path[this.pathName]}'/>`;
+            return `<path d="${utils.escape(path[this.pathName])}"/>`;
         }
     }
 }
 
 export class LineNode implements VirtualNode {
@@ -584,11 +585,11 @@ export class LineNode implements VirtualNode {
     toMarkup(): string {
         let markup = "<line";
 
         for (const attr in this.attributes) {
             if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
-                markup += ` ${attr}='${this.attributes[attr]}'`;
+                markup += ` ${attr}="${utils.escape(this.attributes[attr])}"`;
             }
         }
 
         markup += "/>";
 
diff --git a/test/katex-spec.js b/test/katex-spec.js
index 4f20a962..a51b0fed 100644
--- a/test/katex-spec.js
+++ b/test/katex-spec.js
@@ -2094,10 +2094,22 @@ describe("An includegraphics builder", function() {
 
     it("should render with trust setting", function() {
         const built = getBuilt(img, trustSettings);
         expect(built).toMatchSnapshot();
     });
+
+    it("should escape source", () => {
+        const built = katex.renderToString(
+            "\\includegraphics{'\"}", trustSettings);
+        expect(built).toContain('<img src="&#x27;&quot;"');
+    });
+
+    it("should escape alt", () => {
+        const built = katex.renderToString(
+            "\\includegraphics[alt='\"]{image.png}", trustSettings);
+        expect(built).toContain('<img src="image.png" alt="&#x27;&quot;"');
+    });
 });
 
 describe("An HTML extension builder", function() {
     const html =
         "\\htmlId{bar}{x}\\htmlClass{foo}{x}\\htmlStyle{color: red;}{x}\\htmlData{foo=a, bar=b}{x}";

Vulnerable code:         let markup = `<img  src='${this.src} 'alt='${this.alt}' `;
                markup += ` ${attr}='${this.attributes[attr]}'`;
            return `<path d='${this.alternate}'/>`;
            return `<path d='${path[this.pathName]}'/>`;
                markup += ` ${attr}='${this.attributes[attr]}'`;
Fixed code:         let markup = `<img src="${utils.escape(this.src)}"` +
          ` alt="${utils.escape(this.alt)}"`;
                markup += ` ${attr}="${utils.escape(this.attributes[attr])}"`;
            return `<path d="${utils.escape(this.alternate)}"/>`;
            return `<path d="${utils.escape(path[this.pathName])}"/>`;
                markup += ` ${attr}="${utils.escape(this.attributes[attr])}"`;

    it("should escape source", () => {
        const built = katex.renderToString(
            "\\includegraphics{'\"}", trustSettings);
        expect(built).toContain('<img src="&#x27;&quot;"');
    });

    it("should escape alt", () => {
        const built = katex.renderToString(
            "\\includegraphics[alt='\"]{image.png}", trustSettings);
        expect(built).toContain('<img src="image.png" alt="&#x27;&quot;"');
    });