CVE-2022-23552 (Other, published 2023-01-27T23:15Z)
Description: value: Grafana is an open-source platform for monitoring and observability. Starting with the 8.1 branch and prior to versions 8.5.16, 9.2.10, and 9.3.4, Grafana had a stored XSS vulnerability affecting the core plugin GeoMap. The stored XSS vulnerability was possible because SVG files weren't properly sanitized and allowed arbitrary JavaScript to be executed in the context of the currently authorized user of the Grafana instance. 

An attacker needs to have the Editor role in order to change a panel to include either an external URL to a SVG-file containing JavaScript, or use the `data:` scheme to load an inline SVG-file containing JavaScript. This means that vertical privilege escalation is possible, where a user with Editor role can change to a known password for a user having Admin role if the user with Admin role executes malicious JavaScript viewing a dashboard.   

Users may upgrade to version 8.5.16, 9.2.10, or 9.3.4 to receive a fix.
Severity: nan (CVSS v3 base score: 5.4)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Repository: https://github.com/grafana/grafana
Files changed:
package.json
packages/grafana-ui/src/components/Icon/Icon.tsx
public/app/core/components/SVG/SanitizedSVG.tsx
public/app/features/canvas/elements/icon.tsx
public/app/features/dimensions/editors/FileUploader.tsx
public/app/features/dimensions/editors/ResourceCards.tsx
public/app/features/dimensions/editors/ResourcePicker.tsx
public/app/features/dimensions/editors/URLPickerTab.tsx
public/app/features/query-library/components/QueryEditorDrawerHeader.tsx
public/app/features/storage/FileView.tsx
public/app/plugins/panel/geomap/components/MarkersLegend.tsx
public/app/plugins/panel/geomap/style/markers.ts
yarn.lock
Commit message: SVG: Add dompurify preprocessor step (#62143)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>

Vulnerable code:
    const svgWid = name?.startsWith('gf-bar-align') ? 16 : name?.startsWith('gf-interp') ? 30 : svgSize;
import SVG from 'react-inlinesvg';
    <SVG
import SVG from 'react-inlinesvg';
        {mediaType === MediaType.Icon && <SVG src={file} className={styles.img} />}
import SVG from 'react-inlinesvg';
            <SVG src={card.imgUrl} className={styles.img} />
import SVG from 'react-inlinesvg';
      return <SVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />;
          prefix={sanitizedSrc && <SVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />}
import SVG from 'react-inlinesvg';
            {mediaType === MediaType.Icon && <SVG src={imgSrc} className={styles.img} />}
import InlineSVG from 'react-inlinesvg/esm';
                            <InlineSVG src={option.src} className={styles.optionSvg} />
import SVG from 'react-inlinesvg';
          <SVG src={src} className={styles.icon} />
import SVG from 'react-inlinesvg';
          <SVG

Fixed code:
    "@types/dompurify": "^2",
    "dompurify": "^2.4.1",
    if (!name || name.includes('..')) {
      return <div ref={ref}>invalid icon name</div>;
    }

    const svgWid = name.startsWith('gf-bar-align') ? 16 : name.startsWith('gf-interp') ? 30 : svgSize;
import * as DOMPurify from 'dompurify';
import React from 'react';
import SVG, { Props } from 'react-inlinesvg';

export const SanitizedSVG = (props: Props) => {
  return <SVG {...props} cacheRequests={true} preProcessor={getCleanSVG} />;
};

let cache = new Map<string, string>();

function getCleanSVG(code: string): string {
  let clean = cache.get(code);
  if (!clean) {
    clean = DOMPurify.sanitize(code, { USE_PROFILES: { svg: true, svgFilters: true } });
    cache.set(code, clean);
  }
  return clean;
}
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
    <SanitizedSVG
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';

        {mediaType === MediaType.Icon && <SanitizedSVG src={file} className={styles.img} />}
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
            <SanitizedSVG src={card.imgUrl} className={styles.img} />
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
      return <SanitizedSVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />;
          prefix={sanitizedSrc && <SanitizedSVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />}
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
            {mediaType === MediaType.Icon && <SanitizedSVG src={imgSrc} className={styles.img} />}
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
                            <SanitizedSVG src={option.src} className={styles.optionSvg} />
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
          <SanitizedSVG src={src} className={styles.icon} />
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
          <SanitizedSVG
import * as DOMPurify from 'dompurify';
      text = DOMPurify.sanitize(text, { USE_PROFILES: { svg: true, svgFilters: true } });

"@types/dompurify@npm:^2":
  version: 2.4.0
  resolution: "@types/dompurify@npm:2.4.0"
  dependencies:
    "@types/trusted-types": "*"
  checksum: b48cd81e997794ebc390c7c5bef1a67ec14a6f2f0521973e07e06af186c7583abe114d94d24868c0632b9573f5bd77131a4b76f3fffdf089ba99a4e53dd46c39
  languageName: node
  linkType: hard

"@types/trusted-types@npm:*":
  version: 2.0.2
  resolution: "@types/trusted-types@npm:2.0.2"
  checksum: 3371eef5f1c50e1c3c07a127c1207b262ba65b83dd167a1c460fc1b135a3fb0c97b9f508efebd383f239cc5dd5b7169093686a692a501fde9c3f7208657d9b0d
  languageName: node
  linkType: hard

"dompurify@npm:^2.4.1":
  version: 2.4.3
  resolution: "dompurify@npm:2.4.3"
  checksum: b440981f2a38cada2085759cc3d1e2f94571afc34343d011a8a6aa1ad91ae6abf651adbfa4994b0e2283f0ce81f7891cdb04b67d0b234c8d190cb70e9691f026
  languageName: node
  linkType: hard

    "@types/dompurify": ^2
    dompurify: ^2.4.1