XSS vulnerability allowing arbitrary JavaScript execution
Description
Grafana is an open-source platform for monitoring and observability. In affected versions if an attacker is able to convince a victim to visit a URL referencing a vulnerable page, arbitrary JavaScript content may be executed within the context of the victim's browser. The user visiting the malicious link must be unauthenticated and the link must be for a page that contains the login button in the menu bar. The url has to be crafted to exploit AngularJS rendering and contain the interpolation binding for AngularJS expressions. AngularJS uses double curly braces for interpolation binding: {{ }} ex: {{constructor.constructor(‘alert(1)’)()}}. When the user follows the link and the page renders, the login button will contain the original link with a query parameter to force a redirect to the login page. The URL is not validated and the AngularJS rendering engine will execute the JavaScript expression contained in the URL. Users are advised to upgrade as soon as possible. If for some reason you cannot upgrade, you can use a reverse proxy or similar to block access to block the literal string {{ in the path.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@grafana/datanpm | >= 8.0.0, < 8.2.3 | 8.2.3 |
Affected products
1Patches
3fb85ed691290Merge pull request #147 from grafana/axelav/sanitized-nav-links-8-2-3
1 file changed · +5 −3
public/app/core/components/NavBar/DropdownChild.tsx+5 −3 modified@@ -1,6 +1,6 @@ import React from 'react'; import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, textUtil } from '@grafana/data'; import { Icon, IconName, Link, useTheme2 } from '@grafana/ui'; export interface Props { @@ -29,13 +29,15 @@ const DropdownChild = ({ isDivider = false, icon, onClick, target, text, url }: </button> ); if (url) { + const sanitizedUrl = textUtil.sanitizeAngularInterpolation(url); + element = !target && url.startsWith('/') ? ( - <Link className={styles.element} onClick={onClick} href={url}> + <Link className={styles.element} onClick={onClick} href={sanitizedUrl}> {linkContent} </Link> ) : ( - <a className={styles.element} href={url} target={target} rel="noopener" onClick={onClick}> + <a className={styles.element} href={sanitizedUrl} target={target} rel="noopener" onClick={onClick}> {linkContent} </a> );
3cb5214fa45eMerge pull request #151 from grafana/dcech/sanitize-replaceAll
1 file changed · +1 −1
packages/grafana-data/src/text/sanitize.ts+1 −1 modified@@ -40,5 +40,5 @@ export function escapeHtml(str: string): string { } export function sanitizeAngularInterpolation(url: string): string { - return url.replace('{{', '%7B%7B').replace('}}', '%7D%7D'); + return url.replace(/\{\{/g, '%7B%7B').replace(/\}\}/g, '%7D%7D'); }
31b78d51c693Merge pull request #146 from grafana/oscark/sanitize-nav-links
4 files changed · +12 −12
packages/grafana-data/src/text/index.ts+2 −1 modified@@ -1,11 +1,12 @@ export * from './string'; export * from './markdown'; export * from './text'; -import { escapeHtml, hasAnsiCodes, sanitize, sanitizeUrl } from './sanitize'; +import { escapeHtml, hasAnsiCodes, sanitize, sanitizeUrl, sanitizeAngularInterpolation } from './sanitize'; export const textUtil = { escapeHtml, hasAnsiCodes, sanitize, sanitizeUrl, + sanitizeAngularInterpolation, };
packages/grafana-data/src/text/sanitize.ts+4 −0 modified@@ -38,3 +38,7 @@ export function hasAnsiCodes(input: string): boolean { export function escapeHtml(str: string): string { return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); } + +export function sanitizeAngularInterpolation(url: string): string { + return url.replace('{{', '%7B%7B').replace('}}', '%7D%7D'); +}
public/app/angular/AngularApp.ts+0 −6 modified@@ -103,12 +103,6 @@ export class AngularApp { } bootstrap() { - // Do not initalize angular when the path contains an interpolation directive - const { pathname } = window.location; - if (pathname && (pathname.includes('%7B%7B') || pathname.includes('{{'))) { - return; - } - const injector = angular.bootstrap(document, this.ngModuleDependencies); monkeyPatchInjectorWithPreAssignedBindings(injector);
public/app/core/components/NavBar/NavBarItem.tsx+6 −5 modified@@ -1,6 +1,6 @@ import React, { ReactNode } from 'react'; import { css, cx } from '@emotion/css'; -import { GrafanaTheme2, NavModelItem } from '@grafana/data'; +import { GrafanaTheme2, NavModelItem, textUtil } from '@grafana/data'; import { Link, useTheme2 } from '@grafana/ui'; import NavBarDropdown from './NavBarDropdown'; @@ -34,13 +34,14 @@ const NavBarItem = ({ <span className={styles.icon}>{children}</span> </button> ); + const sanitizedUrl = textUtil.sanitizeAngularInterpolation(url ?? ''); if (url) { element = - !target && url.startsWith('/') ? ( + !target && sanitizedUrl.startsWith('/') ? ( <Link className={styles.element} - href={url} + href={sanitizedUrl} target={target} aria-label={label} onClick={onClick} @@ -49,7 +50,7 @@ const NavBarItem = ({ <span className={styles.icon}>{children}</span> </Link> ) : ( - <a href={url} target={target} className={styles.element} onClick={onClick} aria-label={label}> + <a href={sanitizedUrl} target={target} className={styles.element} onClick={onClick} aria-label={label}> <span className={styles.icon}>{children}</span> </a> ); @@ -61,7 +62,7 @@ const NavBarItem = ({ <NavBarDropdown headerTarget={target} headerText={label} - headerUrl={url} + headerUrl={sanitizedUrl} items={menuItems} onHeaderClick={onClick} reverseDirection={reverseMenuDirection}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-3j9m-hcv9-rpj8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-41174ghsaADVISORY
- github.com/grafana/grafana/commit/31b78d51c693d828720a5b285107a50e6024c912ghsax_refsource_MISCWEB
- github.com/grafana/grafana/commit/3cb5214fa45eb5a571fd70d6c6edf0d729983f82ghsax_refsource_MISCWEB
- github.com/grafana/grafana/commit/fb85ed691290d211a5baa44d9a641ab137f0de88ghsax_refsource_MISCWEB
- github.com/grafana/grafana/security/advisories/GHSA-3j9m-hcv9-rpj8ghsax_refsource_CONFIRMWEB
- security.netapp.com/advisory/ntap-20211125-0003ghsaWEB
- security.netapp.com/advisory/ntap-20211125-0003/mitrex_refsource_CONFIRM
News mentions
0No linked articles in our index yet.