Reproduction of AngularJS <textarea> XSS vulnerability
+
+
Instructions
+
+
+ Populate the textarea with content that could be malicious when evaluated by AngularJS.
+ For example, copy and paste the following text:
+ {{ $eval.constructor('alert("Hacked!")')() }}
+
+
+ Navigate to a different page.
+ Either type a URL in the browser address bar or click the following link:
+ Other page
+
+
+ Come back to this page by clicking the browser's "Back" button.
+
+
+
+
+
+
+
+
+
+
+
+ NOTE 1:
+ This vulnerability only affects Internet Explorer.
+
+ A regular expression used by the angular.copy() utility function to copy RegExp objects is vulnerable to super-linear runtime due to backtracking.
+ With a large carefully-crafted input, this can result in catastrophic backtracking and cause a denial of service of the application, also known as a ReDoS attack.
+
+
+
+
+
+
+
Reproduction instructions
+
+ The app below demonstrates the issue by constructing a regular expression from a user-provided pattern and then copying it with angular.copy().
+
+ Set the regular expression pattern to a value containing many characters.
+
+
+ You can click one of the available buttons to set the regular expression pattern to a value with many characters.
+
+
+ Click the "Copy regular expression" button to trigger angular.copy() to copy the regular expression.
+
+ π NOTICE:
+ The tab will be unresponsive while the regular expression is being copied. The more characters in the regular expression pattern, the longer the tab will remain unresponsive.
+
+
+
+ Observe that the time it takes to copy each regular expression increases super-linearly relative to the pattern length.
+
+
+
+
+
+
+
Reproduction
+
+ Set regular expression pattern to value with N characters:
+
+
+
+
+
+
+
+ Time to copy regular expression: {{ $ctrl.duration }}
+
+ A regular expression used by the $resource service to strip trailing slashes is vulnerable to super-linear runtime due to backtracking.
+ With a large carefully-crafted input, this can result in catastrophic backtracking and cause a denial of service of the application, also known as a ReDoS attack.
+
+
+
+
+
+
+
Reproduction instructions
+
+ The app below demonstrates the issue by using a user-provided URL suffix to $resource.
+
+ Set the URL suffix to a value containing many slashes (/) followed by a non-slash character.
+
+
+ You can click one of the available buttons to set the URL suffix to a value with many slashes.
+
+
+ Click the "Send request" button to trigger $resource to process the URL for the request.
+
+ π NOTICE:
+ The tab will be unresponsive while the URL is being processed. The more slashes in the URL suffix, the longer the tab will remain unresponsive.
+
+
+
+ Observe that the time it takes to process each URL increases super-linearly relative to the URL length.
+
+
+
+
+
+
+
Reproduction
+
+ Set URL suffix to value with N trailing slashes:
+
+
+
+
+
+
+
+ Time to process URL: {{ $ctrl.duration }}
+
+
+
+
diff --git a/cve/CVE-2023-26117/index.js b/cve/CVE-2023-26117/index.js
new file mode 100644
index 000000000000..6ff8fda95bfc
--- /dev/null
+++ b/cve/CVE-2023-26117/index.js
@@ -0,0 +1,51 @@
+// Define controllers.
+class AppCtrl {
+ urlSuffix = null;
+ duration = '(N/A)';
+
+ static $inject = ['$resource'];
+ constructor($resource) {
+ this.$resource = $resource;
+ this.setUrlSuffixWithSlashesPowerOf2Exponent(0);
+ }
+
+ setUrlSuffixWithSlashesPowerOf2Exponent(exponent) {
+ this.urlSuffix = `foo${'/'.repeat(2 ** exponent)}bar`;
+ }
+
+ sendRequest() {
+ try {
+ const api = this.$resource(`https://example.com/something/${this.urlSuffix}`, {}, {}, {
+ cancellable: true,
+ });
+
+ const start = performance.now();
+ const items = api.query();
+ const end = performance.now();
+
+ this.duration = `${((end - start) / 1000).toFixed(2)} seconds`;
+
+ // Cancel the request before it has a chance of being
+ // sent; that's not what we're testing here.
+ items.$cancelRequest();
+ } catch (err) {
+ const isChromeBrowser = navigator.userAgent.includes('Chrome');
+ const isTooMuchRecursionError = `${err}` === 'InternalError: too much recursion';
+
+ let message = `The operation failed with the following error:\n\n ${err}`
+ if (!isChromeBrowser && isTooMuchRecursionError) {
+ message += '\n\nTo avoid this error, it is recommended to run this test case in the Chrome browser.';
+ }
+
+ alert(message);
+ console.error(err);
+
+ this.duration = '(APP CRASHED)';
+ }
+ }
+}
+
+// Define and configure the app.
+window.angular
+ .module('app', ['ngResource'])
+ .controller('AppCtrl', AppCtrl);
diff --git a/cve/CVE-2023-26117/package-lock.json b/cve/CVE-2023-26117/package-lock.json
new file mode 100644
index 000000000000..ccc97ad7dba3
--- /dev/null
+++ b/cve/CVE-2023-26117/package-lock.json
@@ -0,0 +1,18 @@
+{
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "angular": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz",
+ "integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw=="
+ },
+ "angular-resource": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/angular-resource/-/angular-resource-1.8.3.tgz",
+ "integrity": "sha512-x3+4w2SyIDEIe6PUBTzcjDFI5MhZNWAPELUxkYK0QMOkOo4w3BN/qhpd+fO1deFiDS23mZKqT8kfctO1IDkCDA=="
+ }
+ }
+}
diff --git a/cve/CVE-2023-26117/package.json b/cve/CVE-2023-26117/package.json
new file mode 100644
index 000000000000..7f37f2dacd9a
--- /dev/null
+++ b/cve/CVE-2023-26117/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "angular": "1.8.3",
+ "angular-resource": "1.8.3"
+ }
+}
diff --git a/cve/CVE-2023-26118/Readme.md b/cve/CVE-2023-26118/Readme.md
new file mode 100644
index 000000000000..2e6345cb4d2f
--- /dev/null
+++ b/cve/CVE-2023-26118/Readme.md
@@ -0,0 +1,5 @@
+CVE-2023-26118
+
+https://security.snyk.io/vuln/SNYK-JS-ANGULAR-3373046
+
+https://stackblitz.com/edit/angularjs-vulnerability-inpur-url-validation-redos?file=index.html
\ No newline at end of file
diff --git a/cve/CVE-2023-26118/index.html b/cve/CVE-2023-26118/index.html
new file mode 100644
index 000000000000..a78733cba7fc
--- /dev/null
+++ b/cve/CVE-2023-26118/index.html
@@ -0,0 +1,116 @@
+
+
+
+
+ AngularJS vulnerability: `input[url]` validation ReDoS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproduction of AngularJS vulnerability:
+ input[url] validation ReDoS
+
+ A regular expression used to validate the value of the input[url] directive is vulnerable to super-linear runtime due to backtracking.
+ With a large carefully-crafted input, this can result in catastrophic backtracking and cause a denial of service of the application, also known as a ReDoS attack.
+
+
+
+
+
+
+
Reproduction instructions
+
+ The app below demonstrates the issue by filling an <input type="url"> element with a user-provided invalid URL and then running AngularJS' built-in URL validation logic.
+
+ In order to make it easier to measure and observe the issue, the built-in validation logic is disabled and validation is run on demand (see instructions below).
+ In a regular app, the validation logic would run as soon as a value would be entered into the <input> element.
+
+
+ Steps:
+
+
+ Set the URL to a value containing any scheme (for example, http:) followed by many slashes (/).
+
+
+ You can click one of the available buttons to set the URL to a value with many trailing slashes.
+
+
+ Click the "Validate URL" button to trigger the built-in URL validation of input[url].
+
+ π NOTICE:
+ The tab will be unresponsive while the URL is being validated. The more slashes in the URL, the longer the tab will remain unresponsive.
+
+
+
+ Observe that the time it takes to validate each URL increases super-linearly relative to the URL length.
+
+
+
+
+
+
+
Reproduction
+
+ Set URL to value with N trailing slashes:
+
+
+
+
+
+
+
+ Time to validate URL: {{ $ctrl.duration }}
+
+
+
+
diff --git a/cve/CVE-2023-26118/index.js b/cve/CVE-2023-26118/index.js
new file mode 100644
index 000000000000..119cb014757b
--- /dev/null
+++ b/cve/CVE-2023-26118/index.js
@@ -0,0 +1,58 @@
+// Define controllers.
+class AppCtrl {
+ url = null;
+ urlInput = null;
+ urlInputController = null;
+ urlInputValidators = null;
+ duration = '(N/A)';
+
+ constructor() {
+ this.setUrlWithSlashesPowerOf2Exponent(0);
+ }
+
+ $postLink() {
+ // Remove the default validators (which include the URL validator) to prevent
+ // the browser's freezing during entering a value and run validation on demand.
+ this.urlInputController = this.urlInput.controller('ngModel');
+ this.urlInputValidators = this.urlInputController.$validators;
+
+ this.urlInputController.$validators = {};
+ }
+
+ setUrlWithSlashesPowerOf2Exponent(exponent) {
+ this.url = `scheme:${'/'.repeat(2 ** exponent)}`;
+ }
+
+ validateUrl() {
+ try {
+ // Temporarily restore the default validators (which include the URL validator)
+ // and run validation on demand (while measuring the time it takes).
+ this.urlInputController.$validators = this.urlInputValidators;
+
+ const start = performance.now();
+ this.urlInputController.$validate();
+ const end = performance.now();
+
+ this.urlInputController.$validators = {};
+ this.duration = `${((end - start) / 1000).toFixed(2)} seconds`;
+ } catch (err) {
+ const isChromeBrowser = navigator.userAgent.includes('Chrome');
+ const isTooMuchRecursionError = `${err}` === 'InternalError: too much recursion';
+
+ let message = `The operation failed with the following error:\n\n ${err}`
+ if (!isChromeBrowser && isTooMuchRecursionError) {
+ message += '\n\nTo avoid this error, it is recommended to run this test case in the Chrome browser.';
+ }
+
+ alert(message);
+ console.error(err);
+
+ this.duration = '(APP CRASHED)';
+ }
+ }
+}
+
+// Define and configure the app.
+window.angular
+ .module('app', [])
+ .controller('AppCtrl', AppCtrl);
diff --git a/cve/CVE-2023-26118/package-lock.json b/cve/CVE-2023-26118/package-lock.json
new file mode 100644
index 000000000000..f728f5c15516
--- /dev/null
+++ b/cve/CVE-2023-26118/package-lock.json
@@ -0,0 +1,13 @@
+{
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "angular": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz",
+ "integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw=="
+ }
+ }
+}
diff --git a/cve/CVE-2023-26118/package.json b/cve/CVE-2023-26118/package.json
new file mode 100644
index 000000000000..76ca774805ee
--- /dev/null
+++ b/cve/CVE-2023-26118/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "angular": "1.8.3"
+ }
+}
diff --git a/src/Angular.js b/src/Angular.js
index 9b11090518b2..a3acd594b540 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -999,6 +999,12 @@ function copy(source, destination, maxDepth) {
return new source.constructor(source.valueOf());
case '[object RegExp]':
+ // CVE-2023-26116: A regular expression used by the angular.copy() utility function to copy RegExp objects is vulnerable to super-linear runtime due to backtracking. With a large carefully-crafted input, this can result in catastrophic backtracking and cause a denial of service of the application, also known as a ReDoS attack.
+ // As a mitigation the length of the pattern should be restricted to 10000 characters.
+ if (source.toString().length > 10000) {
+ window.console.error('Angular: You are trying to angular.copy a large RegExp. Mitigation of CVE-2023-26116: Better to throw an error than to crash the browser.');
+ return;
+ }
var re = new RegExp(source.source, source.toString().match(/[^/]*$/)[0]);
re.lastIndex = source.lastIndex;
return re;
diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index 7dfbf164b7be..e2f80543a960 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -1940,7 +1940,9 @@ function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
ctrl.$validators.url = function(modelValue, viewValue) {
var value = modelValue || viewValue;
- return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
+ // CVE-2023-26118: A regular expression used to validate the value of the input[url] directive is vulnerable to super-linear runtime due to backtracking. With a large carefully-crafted input, this can result in catastrophic backtracking and cause a denial of service of the application, also known as a ReDoS attack.
+ // As a mitigation the length of the pattern should be restricted to 10000 characters.
+ return ctrl.$isEmpty(value) || (value.length <= 10000 && URL_REGEXP.test(value));
};
}
diff --git a/src/ngResource/resource.js b/src/ngResource/resource.js
index 11bb45ba20b3..d7decc8d631c 100644
--- a/src/ngResource/resource.js
+++ b/src/ngResource/resource.js
@@ -607,6 +607,13 @@ angular.module('ngResource', ['ng']).
encodedVal,
protocolAndIpv6 = '';
+ // CVE-2023-26117: A regular expression used by the $resource service to strip trailing slashes is vulnerable to super-linear runtime due to backtracking. With a large carefully-crafted input, this can result in catastrophic backtracking and cause a denial of service of the application, also known as a ReDoS attack.
+ // As a mitigation the length of the url should be restricted to 10000 characters.
+ if (url.length > 10000) {
+ window.console.error('Angular: You are trying to parse a large url ($resource(url).query()). Mitigation of CVE-2023-26117: Better to throw an error than to crash the browser.');
+ return;
+ }
+
var urlParams = self.urlParams = Object.create(null);
forEach(url.split(/\W/), function(param) {
if (param === 'hasOwnProperty') {
From 7fa269db0a9aefaca6075eaebf6a3ac8249b7dcc Mon Sep 17 00:00:00 2001
From: Stefan Nesbigall
Date: Mon, 15 Jul 2024 15:12:05 +0200
Subject: [PATCH 2/7] 1.8.5: Mitigation of CVE-2024-21490
---
README.md | 8 ++
cve/CVE-2024-21490/Readme.md | 3 +
cve/CVE-2024-21490/index.html | 120 +++++++++++++++++++++++++++
cve/CVE-2024-21490/index.js | 49 +++++++++++
cve/CVE-2024-21490/package-lock.json | 21 +++++
cve/CVE-2024-21490/package.json | 9 ++
package.json | 1 +
src/ng/compile.js | 7 ++
8 files changed, 218 insertions(+)
create mode 100644 cve/CVE-2024-21490/Readme.md
create mode 100644 cve/CVE-2024-21490/index.html
create mode 100644 cve/CVE-2024-21490/index.js
create mode 100644 cve/CVE-2024-21490/package-lock.json
create mode 100644 cve/CVE-2024-21490/package.json
diff --git a/README.md b/README.md
index 7eff5362a41f..abf7b4b0343b 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,14 @@ version in node_modules (npm install first).
To test the mitigation build this repo (yarn grunt package (node 12.22.12)) and change the according index.html
to point to /build/angular.js.
+## CVE-2024-21490
+
+In order to reproduce the problem see /cve/CVE-2024-21490/ (run with angular from node_modules).
+
+This was mitigated by checking the length (max 10000 characters) of the RegExp pattern when one is found in sanitizeSrcset.
+An error is thrown if there are to many characters.
+See /src/ng/compile.js#2099.
+
## CVE-2022-25869 Is not mitigated: Don't ever use Internet Explorer.
## CVE-2022-25844
diff --git a/cve/CVE-2024-21490/Readme.md b/cve/CVE-2024-21490/Readme.md
new file mode 100644
index 000000000000..9397e892b869
--- /dev/null
+++ b/cve/CVE-2024-21490/Readme.md
@@ -0,0 +1,3 @@
+CVE-2024-21490
+
+https://security.snyk.io/vuln/SNYK-JS-ANGULAR-6091113
\ No newline at end of file
diff --git a/cve/CVE-2024-21490/index.html b/cve/CVE-2024-21490/index.html
new file mode 100644
index 000000000000..5d7d1ca66dbd
--- /dev/null
+++ b/cve/CVE-2024-21490/index.html
@@ -0,0 +1,120 @@
+
+
+
+
+ AngularJS vulnerability: `ng-srcset` parsing ReDoS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproduction of AngularJS vulnerability:
+ ng-srcset parsing ReDoS
+
+ A regular expression used to split the value of the ng-srcset directive is vulnerable to super-linear runtime due to backtracking.
+ With a large carefully-crafted input, this can result in catastrophic backtracking and cause a denial of service of the application, also known as a ReDoS attack.
+
+
+
+
+
+
+
Reproduction instructions
+
+ Note: reproduction steps should be run in Chrome or Safari due to differences in handling large strings between those two browsers or Firefox.
+
+
+ The app below demonstrates the issue by filling an <img ng-srcset="..."> element with a user-provided string and then running AngularJS' compiler, triggering built-in ng-srcset parsing logic.
+
+
+ Note that the test case doesn't insert interpolation markers (`{` or `}` symbols); stripping them out from user input before applying to `ng-srcset` does not avoid the issue.
+
+ Set the input value to a valid ng-srcset string containing many spaces.
+
+
+ You can click one of the available buttons to set the string to a value with many spaces.
+
+
+ Click the "Compile ng-srcset" button to update the test img element with a requested value of ng-srcset.
+
+ π NOTICE:
+ The tab will be unresponsive while the img is being recompiled. The more spaces in the ng-srcset string, the longer the tab will remain unresponsive.
+
+
+
+ Observe that the time it takes to update each ng-srcset value increases super-linearly relative to the string length.
+
+
+
+
+
+
+
Reproduction
+
+ Compile ng-srcset to value with N spaces:
+
+
+
+
+
+
+
+ Time to compile ng-srcset: {{ $ctrl.duration }}
+
+
+
+
+
+
diff --git a/cve/CVE-2024-21490/index.js b/cve/CVE-2024-21490/index.js
new file mode 100644
index 000000000000..46db7a1d57d4
--- /dev/null
+++ b/cve/CVE-2024-21490/index.js
@@ -0,0 +1,49 @@
+// Define controllers.
+class AppCtrl {
+ static $inject = ['$compile', '$rootScope'];
+
+ ngSrcSet = null;
+ ngSrcSetCompiledElem = null;
+ timeoutId = null;
+ duration = '(N/A)';
+
+ constructor($compile, $rootScope) {
+ this.$compile = $compile;
+ this.$rootScope = $rootScope;
+ this.setNgSrcSetWithSpacesPowerOf2Exponent(0);
+ }
+
+ setNgSrcSetWithSpacesPowerOf2Exponent(exponent) {
+ this.ngSrcSet = `http://example.com/image.png 2x, ${' '.repeat(
+ 2 ** exponent
+ )}http://example.com/image.png`;
+ }
+
+ compileNgSrcSet() {
+ clearTimeout(this.timeoutId);
+
+ // Use `setTimeout` to allow manual `$apply` calls.
+ this.timeoutId = setTimeout(() => {
+ try {
+ this.ngSrcSetCompiledElem.html(``);
+ const scope = this.ngSrcSetCompiledElem.scope();
+
+ const start = performance.now();
+ this.$compile(this.ngSrcSetCompiledElem)(scope);
+ this.$rootScope.$apply();
+ const end = performance.now();
+
+ this.duration = `${((end - start) / 1000).toFixed(2)} seconds`;
+ } catch (err) {
+ console.error(err);
+
+ this.duration = '(APP CRASHED)';
+ } finally {
+ this.$rootScope.$apply(); // apply the duration change
+ }
+ });
+ }
+}
+
+// Define and configure the app.
+angular.module('app', []).controller('AppCtrl', AppCtrl);
diff --git a/cve/CVE-2024-21490/package-lock.json b/cve/CVE-2024-21490/package-lock.json
new file mode 100644
index 000000000000..f460751889e2
--- /dev/null
+++ b/cve/CVE-2024-21490/package-lock.json
@@ -0,0 +1,21 @@
+{
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "dependencies": {
+ "angular": "1.8.3"
+ }
+ },
+ "node_modules/angular": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz",
+ "integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw==",
+ "deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward."
+ }
+ }
+}
diff --git a/cve/CVE-2024-21490/package.json b/cve/CVE-2024-21490/package.json
new file mode 100644
index 000000000000..81abace4efb7
--- /dev/null
+++ b/cve/CVE-2024-21490/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "angular": "1.8.3"
+ }
+}
+
diff --git a/package.json b/package.json
index c3a3eeb70417..4da6829cf071 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,7 @@
{
"name": "angular",
"license": "MIT",
+ "version": "1.8.5",
"branchVersion": "^1.8.0",
"branchPattern": "1.8.*",
"distTag": "next",
diff --git a/src/ng/compile.js b/src/ng/compile.js
index e48b5a98b6c5..a0bcf4f32dae 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -2096,6 +2096,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var result = '';
+ // CVE-2024-21490: This affects versions of the package angular from 1.3.0. A regular expression used to split the value of the ng-srcset directive is vulnerable to super-linear runtime due to backtracking. With large carefully-crafted input, this can result in catastrophic backtracking and cause a denial of service.
+ // As a mitigation the length of the pattern should be restricted to 10000 characters.
+ if (value.length > 10000) {
+ window.console.error('Angular: You are trying to sanitize a large url string. Mitigation of CVE-2024-21490: Better to throw an error than to crash the browser.');
+ return;
+ }
+
// first check if there are spaces because it's not the same pattern
var trimmedSrcset = trim(value);
// ( 999x ,| 999w ,| ,|, )
From 15014ba87f5619dd11424e4d3abc4b8baa636e58 Mon Sep 17 00:00:00 2001
From: Stefan Nesbigall
Date: Mon, 15 Jul 2024 15:19:31 +0200
Subject: [PATCH 3/7] Reset version 1.8.4
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 4da6829cf071..435726f8c00f 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "angular",
"license": "MIT",
- "version": "1.8.5",
+ "version": "1.8.4",
"branchVersion": "^1.8.0",
"branchPattern": "1.8.*",
"distTag": "next",
From 0e2e78d8bf42d32254689f14d3b9ab160c7a8e2d Mon Sep 17 00:00:00 2001
From: Stefan Nesbigall
Date: Tue, 17 Sep 2024 05:47:53 +0200
Subject: [PATCH 4/7] Mitigation of CVE-2024-8372 and CVE-2024-8373
---
README.md | 15 ++
cve/CVE-2024-8372/Readme.md | 5 +
cve/CVE-2024-8372/index.html | 298 ++++++++++++++++++++++++++
cve/CVE-2024-8372/index.js | 8 +
cve/CVE-2024-8372/package-lock.json | 13 ++
cve/CVE-2024-8372/package.json | 8 +
cve/CVE-2024-8373/Readme.md | 5 +
cve/CVE-2024-8373/index.html | 311 ++++++++++++++++++++++++++++
cve/CVE-2024-8373/index.js | 8 +
cve/CVE-2024-8373/package-lock.json | 13 ++
cve/CVE-2024-8373/package.json | 8 +
src/ng/compile.js | 10 +-
src/ng/sanitizeUri.js | 6 +
13 files changed, 707 insertions(+), 1 deletion(-)
create mode 100644 cve/CVE-2024-8372/Readme.md
create mode 100644 cve/CVE-2024-8372/index.html
create mode 100644 cve/CVE-2024-8372/index.js
create mode 100644 cve/CVE-2024-8372/package-lock.json
create mode 100644 cve/CVE-2024-8372/package.json
create mode 100644 cve/CVE-2024-8373/Readme.md
create mode 100644 cve/CVE-2024-8373/index.html
create mode 100644 cve/CVE-2024-8373/index.js
create mode 100644 cve/CVE-2024-8373/package-lock.json
create mode 100644 cve/CVE-2024-8373/package.json
diff --git a/README.md b/README.md
index abf7b4b0343b..b8e87b75a0d4 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,21 @@ version in node_modules (npm install first).
To test the mitigation build this repo (yarn grunt package (node 12.22.12)) and change the according index.html
to point to /build/angular.js.
+## CVE-2024-8372
+
+In order to reproduce the problem see /cve/CVE-2024-8372/ (run with angular from node_modules).
+
+Some specially-crafted ngSrcset, ngAttrSrcset and ngPropSrcset values to bypass the image source sanitization restrictions and show images that should be blocked.
+This is mitigated by blocking any comma seperated multi srcset urls (all urls containing (,)), that do not start with (data:).
+
+## CVE-2024-8373
+
+In order to reproduce the problem see /cve/CVE-2024-8373/ (run with angular from node_modules).
+
+Setting a element's srcset attribute value via the ngAttrSrcset directive or interpolation is not subject to image source sanitization.
+This was mitigated by returning sce.MEDIA_URL for elements with srcset attribute values.
+See /src/ng/compile.js#3829.
+
## CVE-2024-21490
In order to reproduce the problem see /cve/CVE-2024-21490/ (run with angular from node_modules).
diff --git a/cve/CVE-2024-8372/Readme.md b/cve/CVE-2024-8372/Readme.md
new file mode 100644
index 000000000000..f5312cccdcf1
--- /dev/null
+++ b/cve/CVE-2024-8372/Readme.md
@@ -0,0 +1,5 @@
+CVE-2024-8372
+
+https://www.herodevs.com/vulnerability-directory/cve-2024-8372
+
+https://codepen.io/herodevs/full/xxoQRNL/0072e627abe03e9cda373bc75b4c1017
diff --git a/cve/CVE-2024-8372/index.html b/cve/CVE-2024-8372/index.html
new file mode 100644
index 000000000000..b59885340b30
--- /dev/null
+++ b/cve/CVE-2024-8372/index.html
@@ -0,0 +1,298 @@
+
+
+
+
+ Reproduction of AngularJS vulnerability: ng(Attr/Prop)Srcset image source sanitization bypass
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The logic used in the ngSrcset, ngAttrSrcset and ngPropSrcset directives to sanitize image source URLs has a vulnerability that allows bypassing the restrictions set by some common patterns, such as only allowing images from a specific domain.
+ With a specially-crafted input, the sanitization can be bypassed and images from an arbitrary domain can be shown, which can also lead to a form of Content Spoofing.
+
+
+
+
+
+
+
+
Reproduction instructions
+
+ The app below demonstrates the issue by using some specially-crafted ngSrcset, ngAttrSrcset and ngPropSrcset values to bypass the image source sanitization restrictions and show images that should be blocked.
+
+
+ For demonstration purposes, the app is configured to only allow images from the https://angularjs.org/ domain:
+
+ However, by taking advantage of the vulnerability, we are able to show images from other domains (e.g. https://angular.dev/) as well as different schemes (e.g. an arbitrary SVG image using the data:image/svg+xml format).
+
+
+ Steps:
+
+
+ Take a look at the table below.
+
+
+ For reference, the first 3 entries use the ngSrc directive (which is not vulnerable) and demonstrate that the image sources are sanitized correctly, only allowing images from https://angularjs.org/ to show.
+
+
+ In contrast, the last 6 entries use the ngSrcset, ngAttrSrcset and ngPropSrcset directives (with specially-crafted values), which allows us to bypass the sanitization and show images that should be blocked.
+
+
+
+
+
+
+
Reproduction
+
+
+ Examples with ngSrc
+ (not vulnerable)
+
+
+
+ Image from angularjs.org:
+ ALLOWED (Correct βοΈ)
+
+
+
+
+ Image from angular.dev:
+ BLOCKED (Correct βοΈ)
+
+
+
+
+ Arbitrary SVG image via data URL:
+ BLOCKED (Correct βοΈ)
+
+
+
+
+ Examples with ngSrcset
+ (vulnerable)
+
+
+
+ Image from angular.dev:
+ ALLOWED (Error β)
+
+
+
+
+ Arbitrary SVG image via data URL
+ ALLOWED (Error β)
+
+
+
+
+ Examples with ngAttrSrcset
+ (vulnerable)
+
+
+
+ Image from angular.dev:
+ ALLOWED (Error β)
+
+
+
+
+ Arbitrary SVG image via data URL:
+ ALLOWED (Error β)
+
+
+
+
+ Examples with ngPropSrcset
+ (vulnerable)
+
+
+
+ Image from angular.dev:
+ ALLOWED (Error β)
+
+
+
+
+ Arbitrary SVG image via data URL:
+ ALLOWED (Error β)
+
+
+
+ Setting a <source> element's srcset attribute value via the ngAttrSrcset directive or interpolation is not subject to image source sanitization. As a result, no restrictions are applied to the images that can be shown, which can also lead to a form of Content Spoofing.
+
+
+
+
+
+
+
+
Reproduction instructions
+
+ The app below demonstrates the issue by trying to load images that should be blocked by the image source sanitization rules. It is doing so by binding to a <source> element's srcset attribute using ngSrcset, ngAttrSrcset, ngPropSrcset and srcset interpolation.
+
+
+ For demonstration purposes, the app is configured to only allow images from the https://angularjs.org/ domain:
+
+ However, by taking advantage of the lack of sanitization in ngAttrSrcset and srcset interpolation, we are able to show images from other domains (e.g. https://angular.dev/) as well as different schemes (e.g. an arbitrary SVG image using the data:image/svg+xml format).
+
+
+ Steps:
+
+
+ Take a look at the table below.
+
+
+ For reference, the first 4 entries use the ngSrcset and ngPropSrcset directives (which are not vulnerable) and demonstrate that the image sources are sanitized correctly, not allowing images outside https://angularjs.org/ to show.
+
+
+ In contrast, the last 4 entries use the ngAttrSrcset directive or interpolation with the srcset attribute, which are not subject to sanitization, thus allowing us to show images that should be blocked.
+
+
+
+
diff --git a/cve/CVE-2024-8373/index.js b/cve/CVE-2024-8373/index.js
new file mode 100644
index 000000000000..e7d6aea029b4
--- /dev/null
+++ b/cve/CVE-2024-8373/index.js
@@ -0,0 +1,8 @@
+// Define and configure the app.
+window.angular
+ .module('app', [])
+ .config(['$compileProvider', $compileProvider => {
+ $compileProvider.imgSrcSanitizationTrustedUrlList(
+ // Only allow images from `angularjs.org`.
+ /^https:\/\/angularjs\.org\//);
+ }]);
diff --git a/cve/CVE-2024-8373/package-lock.json b/cve/CVE-2024-8373/package-lock.json
new file mode 100644
index 000000000000..f728f5c15516
--- /dev/null
+++ b/cve/CVE-2024-8373/package-lock.json
@@ -0,0 +1,13 @@
+{
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "angular": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz",
+ "integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw=="
+ }
+ }
+}
diff --git a/cve/CVE-2024-8373/package.json b/cve/CVE-2024-8373/package.json
new file mode 100644
index 000000000000..76ca774805ee
--- /dev/null
+++ b/cve/CVE-2024-8373/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "angular": "1.8.3"
+ }
+}
diff --git a/src/ng/compile.js b/src/ng/compile.js
index a0bcf4f32dae..441c865d4618 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -3824,7 +3824,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
// All nodes with src attributes require a RESOURCE_URL value, except for
// img and various html5 media nodes, which require the MEDIA_URL context.
- if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') {
+ // CVE-2024-8373: Setting a element's srcset attribute value via the ngAttrSrcset directive or interpolation is not subject to image source sanitization.
+ // This is mitigated by returning sce.MEDIA_URL for elements with srcset attribute values.
+ if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc' || attrNormalizedName === 'srcset' || attrNormalizedName === 'ngSrcset') {
if (['img', 'video', 'audio', 'source', 'track'].indexOf(nodeName) === -1) {
return $sce.RESOURCE_URL;
}
@@ -3856,6 +3858,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
function sanitizeSrcsetPropertyValue(value) {
+ // CVE-2024-8372: Some specially-crafted ngSrcset, ngAttrSrcset and ngPropSrcset values to bypass the image source sanitization restrictions and show images that should be blocked.
+ // This is mitigated by blocking any comma seperated multi srcset urls (all urls containing (,)).
+ if (value.includes(',')) {
+ window.console.error('You are trying to set a dynamic srcset. Mitigation of CVE-2024-8372: Blocked unsafe dynamic srcset.');
+ return '';
+ }
return sanitizeSrcset($sce.valueOf(value), 'ng-prop-srcset');
}
function addPropertyDirective(node, directives, attrName, propName) {
diff --git a/src/ng/sanitizeUri.js b/src/ng/sanitizeUri.js
index 495f49e03df0..54c3ed0cbf3e 100644
--- a/src/ng/sanitizeUri.js
+++ b/src/ng/sanitizeUri.js
@@ -74,6 +74,12 @@ function $$SanitizeUriProvider() {
// if (!uri) return uri;
var regex = isMediaUrl ? imgSrcSanitizationTrustedUrlList : aHrefSanitizationTrustedUrlList;
var normalizedVal = urlResolve(uri && uri.trim()).href;
+ // CVE-2024-8372: Some specially-crafted ngSrcset, ngAttrSrcset and ngPropSrcset values to bypass the image source sanitization restrictions and show images that should be blocked.
+ // This is mitigated by blocking any comma seperated multi srcset urls (all urls containing (,)), that do not start with (data:).
+ if (normalizedVal.includes(',') && !normalizedVal.startsWith('data:')) {
+ window.console.error('You are trying to set a dynamic srcset. Mitigation of CVE-2024-8372: Blocked unsafe dynamic srcset.');
+ return '';
+ }
if (normalizedVal !== '' && !normalizedVal.match(regex)) {
return 'unsafe:' + normalizedVal;
}
From 1379674240664b8434d9a939660d18aa02b740ba Mon Sep 17 00:00:00 2001
From: Stefan Nesbigall
Date: Tue, 10 Jun 2025 09:24:59 +0200
Subject: [PATCH 5/7] Mitigation of CVE-2025-0716 and CVE-2025-2336
---
README.md | 19 ++
cve/CVE-2025-0716/Readme.md | 3 +
cve/CVE-2025-0716/index.html | 397 ++++++++++++++++++++++++++++
cve/CVE-2025-0716/index.js | 19 ++
cve/CVE-2025-0716/package-lock.json | 22 ++
cve/CVE-2025-0716/package.json | 9 +
cve/CVE-2025-2336/Readme.md | 3 +
cve/CVE-2025-2336/index.html | 339 ++++++++++++++++++++++++
cve/CVE-2025-2336/index.js | 53 ++++
cve/CVE-2025-2336/package-lock.json | 30 +++
cve/CVE-2025-2336/package.json | 9 +
src/ng/compile.js | 4 +
src/ng/sanitizeUri.js | 2 +-
src/ngSanitize/sanitize.js | 4 +-
14 files changed, 911 insertions(+), 2 deletions(-)
create mode 100644 cve/CVE-2025-0716/Readme.md
create mode 100644 cve/CVE-2025-0716/index.html
create mode 100644 cve/CVE-2025-0716/index.js
create mode 100644 cve/CVE-2025-0716/package-lock.json
create mode 100644 cve/CVE-2025-0716/package.json
create mode 100644 cve/CVE-2025-2336/Readme.md
create mode 100644 cve/CVE-2025-2336/index.html
create mode 100644 cve/CVE-2025-2336/index.js
create mode 100644 cve/CVE-2025-2336/package-lock.json
create mode 100644 cve/CVE-2025-2336/package.json
diff --git a/README.md b/README.md
index b8e87b75a0d4..4dd4d3a1c988 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,25 @@ version in node_modules (npm install first).
To test the mitigation build this repo (yarn grunt package (node 12.22.12)) and change the according index.html
to point to /build/angular.js.
+## CVE-2025-0716
+
+In order to reproduce the problem see /cve/CVE-2025-0716/ (run with angular from node_modules).
+
+An improper sanitization vulnerability (CVE-2025-0716) has been identified in AngularJS, which allows attackers to bypass common image source restrictions normally applied to the value of the href or xlink:href attributes on SVG elements. This bypass can further lead to a form of Content Spoofing. Similarly, the application's performance and behavior could be negatively affected by using too large or slow-to-load images.
+
+This was mitigated, by providing a trust context ($sce.MEDIA_URL) in case of elements with such attributes.
+See /src/ng/compile.js#3840.
+
+## CVE-2025-2336
+
+In order to reproduce the problem see /cve/CVE-2025-2336/ (run with angular from node_modules).
+
+An improper sanitization vulnerability (CVE-2025-2336) has been identified in AngularJS' ngSanitize module, which allows attackers to bypass common image source restrictions normally applied to image elements. This bypass can further lead to a form of Content Spoofing. Similarly, the application's performance and behavior could be negatively affected by using too large or slow-to-load images.
+The bug lies within the sanitation process, image tags within a svg tag is not properly sanitized.
+
+This is mitigated by blocking the $SanitizeProvider.enableSvg which is a precondition for this vulnerabitlity.
+See /src/ngSanitize/sanitize.js#198.
+
## CVE-2024-8372
In order to reproduce the problem see /cve/CVE-2024-8372/ (run with angular from node_modules).
diff --git a/cve/CVE-2025-0716/Readme.md b/cve/CVE-2025-0716/Readme.md
new file mode 100644
index 000000000000..cbb33a5079a0
--- /dev/null
+++ b/cve/CVE-2025-0716/Readme.md
@@ -0,0 +1,3 @@
+CVE-2025-0716
+
+https://www.herodevs.com/vulnerability-directory/cve-2025-0716?angularjs-nes
diff --git a/cve/CVE-2025-0716/index.html b/cve/CVE-2025-0716/index.html
new file mode 100644
index 000000000000..ea323f9565fd
--- /dev/null
+++ b/cve/CVE-2025-0716/index.html
@@ -0,0 +1,397 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AngularJS vulnerability: `(ng(Attr))Href` SVG image source sanitization bypass
+
+
+
+ AngularJS vulnerability: `(ng(Attr))Href` SVG image source sanitization bypass
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Setting an <image> SVG element's href attribute value via the ngHref and ngAttrHref directives or using interpolation is not subject to image source sanitization. As a result, no restrictions are applied to the images that can be shown, which can also lead to a form of Content Spoofing. Similarly, the app's performance and behavior can be negatively affected by using too large or slow-to-load images.
+
+
+
+
+
+
+
+
+
Reproduction instructions
+
+ The app below demonstrates the issue by trying to load images that should be blocked by the image source sanitization rules. It is doing so by binding to an <image> SVG element's href attribute using ngHref, ngAttrHref and href interpolation.
+
+
+ For demonstration purposes, the app is configured to only allow images from the https://angularjs.org/ domain:
+
+ However, by taking advantage of the lack of sanitization in ngHref, ngAttrHref and href interpolation, we are able to show images from other domains (e.g. https://angular.dev/) as well as different schemes (e.g. an arbitrary SVG image using the data:image/svg+xml format).
+
+
+ Steps:
+
+
+ Take a look at the table below.
+
+
+ For reference, the first 6 entries use non-vulnerable methods, such as targeting the xlink:href attribute or using the ngHref directive with hard-codes values (i.e. no interpolation), and demonstrate that the image sources are sanitized correctly, not allowing images outside https://angularjs.org/ to show.
+
+
+ In contrast, the last 6 entries use vulnerable methods to target the href attribute, thus bypassing sanitization and allowing us to show images that should be blocked.
+
+
+
+
+
+
+
Reproduction
+
+
+ Examples with xlink:href + interpolation
+ (not vulnerable)
+
+
+
+ Image from angular.dev:
+ BLOCKED (Correct βοΈ)
+
+
+
+
+
+
+ Examples with href + interpolation
+ (vulnerable)
+
+
+
+ Image from angular.dev:
+ ALLOWED (Error β)
+
+
+
+
+ Arbitrary SVG image via data URL:
+ ALLOWED (Error β)
+
+
+
+
+ Examples with ng-href + interpolation
+ (vulnerable)
+
+
+
+ Image from angular.dev:
+ ALLOWED (Error β)
+
+
+
+
+ Arbitrary SVG image via data URL:
+ ALLOWED (Error β)
+
+
+
+
+ Examples with ng-attr-href
+ (vulnerable)
+
+
+
+ Image from angular.dev:
+ ALLOWED (Error β)
+
+
+
+
+ Arbitrary SVG image via data URL:
+ ALLOWED (Error β)
+
+
+
+ The $sanitize service is used for sanitizing HTML strings by stripping all potentially dangerous tokens. As part of the sanitization, it checks the URLs of images to ensure they abide by the defined image source rules. However, SVG <image> elements are not correctly detected as images by the $sanitize service. As a result, the image source restrictions are not applied to the images that can be shown, which can also lead to a form of Content Spoofing. Similarly, the app's performance and behavior can be negatively affected by using too large or slow-to-load images.
+
+
+
+
+
+
+
+
+
Reproduction instructions
+
+ The app below demonstrates the issue by inserting a sanitized HTML string containing SVG images that should be blocked by the image source sanitization rules.
+
+
+ The app uses the ngSanitize module to sanitize HTML.
+
+ angular.module('app', ['ngSanitize']);
+
+
+
+ For demonstration purposes, the app is configured to only allow images from the https://angularjs.org/ domain:
+
+ However, by taking advantage of the lack of sanitization for SVG <image> elements, we are able to show images from other domains (e.g. https://angular.dev/).
+
+
+ Steps:
+
+
+ Choose an image URL type.
+
+
URL from allowed domain is a URL that is allowed by the image source sanitization rules and should be kept in the sanitized HTML.
+
URL from disallowed domain is a URL that is disallowed by the image source sanitization rules and should be stripped from the sanitized HTML.
+
+
+
+
+ Choose an HTML payload type.
+
+
Types marked with [SAFE] are subject to proper sanitization with respect to image URLs.
+
Types marked with [UNSAFE] can bypass image URL sanitization.
+
+
+
+
+ In the Computed HTML payload section, you can see the resulting HTML payload that will be used inserted into the DOM using ngBindHtml.
+ Note that ngBindHtml sanitizes its value using the $sanitize service.
+
+
+
+ Look at the Sanitized HTML and Rendered HTML sections to see how different HTML payloads behave with respect to image source sanitization.
+
+
+ Observe that choosing an HTML template with a regular <img> element is subject to proper sanitization, meaning that it can only show an images from the allowed domains.
+
+
+ In contrast, SVG <image> elements can bypass sanitization and show images from disallowed domains.
+
+
+
+
+
+
+
+
+
Reproduction
+ AngularJS v{{ version }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cve/CVE-2025-2336/index.js b/cve/CVE-2025-2336/index.js
new file mode 100644
index 000000000000..37df51a970a8
--- /dev/null
+++ b/cve/CVE-2025-2336/index.js
@@ -0,0 +1,53 @@
+// Define and configure the app.
+window.angular
+ .module('app', ['ngSanitize'])
+ .config([
+ '$compileProvider', '$sanitizeProvider',
+ ($compileProvider, $sanitizeProvider) => {
+ // Before v1.8.1, use `imgSrcSanitizationWhitelist()` instead.
+ $compileProvider.imgSrcSanitizationTrustedUrlList(
+ // Only allow images from `angularjs.org`.
+ /^https:\/\/angularjs\.org\//);
+
+ $sanitizeProvider.enableSvg(true);
+ }
+ ]).
+run(['$rootScope', r => {
+ const urlPlaceholder = 'URL_PLACEHOLDER';
+
+ r.updateHtmlPayload = () => {
+ r.htmlPayload =
+ r.htmlTemplate.value.replaceAll(urlPlaceholder, r.imageUrl.value);
+ };
+
+ r.imageUrls = [
+ {
+ label: 'URL from allowed domain',
+ value: 'https://angularjs.org/favicon.ico',
+ },
+ {
+ label: 'URL from disallowed domain',
+ value: 'https://angular.dev/favicon.ico',
+ },
+ ];
+
+ r.htmlTemplates = [
+ {
+ label: '[SAFE] Regular image with `src`',
+ value: ``,
+ },
+ {
+ label: '[UNSAFE] SVG image with `href`',
+ value: ``,
+ },
+ {
+ label: '[UNSAFE] SVG image with `xlink:href`',
+ value: ``,
+ },
+ ];
+
+ r.version = angular.version.full;
+ r.imageUrl = r.imageUrls[0];
+ r.htmlTemplate = r.htmlTemplates[0];
+ r.updateHtmlPayload();
+}]);
diff --git a/cve/CVE-2025-2336/package-lock.json b/cve/CVE-2025-2336/package-lock.json
new file mode 100644
index 000000000000..e98bd3ae674b
--- /dev/null
+++ b/cve/CVE-2025-2336/package-lock.json
@@ -0,0 +1,30 @@
+{
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "dependencies": {
+ "angular": "1.8.3",
+ "angular-sanitize": "1.8.3"
+ }
+ },
+ "node_modules/angular": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz",
+ "integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw==",
+ "deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward.",
+ "license": "MIT"
+ },
+ "node_modules/angular-sanitize": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.8.3.tgz",
+ "integrity": "sha512-2rxdqzlUVafUeWOwvY/FtyWk1pFTyCtzreeiTytG9m4smpuAEKaIJAjYeVwWsoV+nlTOcgpwV4W1OCmR+BQbUg==",
+ "deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward.",
+ "license": "MIT"
+ }
+ }
+}
diff --git a/cve/CVE-2025-2336/package.json b/cve/CVE-2025-2336/package.json
new file mode 100644
index 000000000000..6a5cd7cd0916
--- /dev/null
+++ b/cve/CVE-2025-2336/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "angular": "1.8.3",
+ "angular-sanitize": "1.8.3"
+ }
+}
diff --git a/src/ng/compile.js b/src/ng/compile.js
index 441c865d4618..b451582a196c 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -3836,6 +3836,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (nodeName === 'image') return $sce.MEDIA_URL;
if (nodeName === 'a') return $sce.URL;
return $sce.RESOURCE_URL;
+ } else if ((attrNormalizedName === 'href' || attrNormalizedName === 'ngHref') && nodeName === 'image') {
+ // CVE-2025-0716: Setting an element's href attribute value via the ngHref directive or interpolation is not subject to image source sanitization.
+ // This is mitigated by returning sce.MEDIA_URL for elements with href attribute values.
+ return $sce.MEDIA_URL;
} else if (
// Formaction
(nodeName === 'form' && attrNormalizedName === 'action') ||
diff --git a/src/ng/sanitizeUri.js b/src/ng/sanitizeUri.js
index 54c3ed0cbf3e..a66cca4fb096 100644
--- a/src/ng/sanitizeUri.js
+++ b/src/ng/sanitizeUri.js
@@ -76,7 +76,7 @@ function $$SanitizeUriProvider() {
var normalizedVal = urlResolve(uri && uri.trim()).href;
// CVE-2024-8372: Some specially-crafted ngSrcset, ngAttrSrcset and ngPropSrcset values to bypass the image source sanitization restrictions and show images that should be blocked.
// This is mitigated by blocking any comma seperated multi srcset urls (all urls containing (,)), that do not start with (data:).
- if (normalizedVal.includes(',') && !normalizedVal.startsWith('data:')) {
+ if (normalizedVal.includes(',') && !normalizedVal.startsWith('data:') && !normalizedVal.startsWith('unsafe:')) {
window.console.error('You are trying to set a dynamic srcset. Mitigation of CVE-2024-8372: Blocked unsafe dynamic srcset.');
return '';
}
diff --git a/src/ngSanitize/sanitize.js b/src/ngSanitize/sanitize.js
index 34e0e09ba532..401e895fc2a0 100644
--- a/src/ngSanitize/sanitize.js
+++ b/src/ngSanitize/sanitize.js
@@ -195,7 +195,9 @@ function $SanitizeProvider() {
*/
this.enableSvg = function(enableSvg) {
if (isDefined(enableSvg)) {
- svgEnabled = enableSvg;
+ // CVE-2025-2336: SVG support cannot be enabled in $sanitizeProvider anymore.
+ window.console.error('Angular: You are trying to use $SanitizeProvider.enableSvg. Mitigation of CVE-2025-2336: SVG support cannot be enabled in $sanitizeProvider anymore.');
+ // svgEnabled = enableSvg;
return this;
} else {
return svgEnabled;
From 38d7b923ef10a4b1e7a7ce55923896d31f20b84c Mon Sep 17 00:00:00 2001
From: Stefan Nesbigall
Date: Tue, 10 Jun 2025 09:29:43 +0200
Subject: [PATCH 6/7] Reactivated full example.
---
cve/CVE-2025-0716/index.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/cve/CVE-2025-0716/index.html b/cve/CVE-2025-0716/index.html
index ea323f9565fd..a337003b19fa 100644
--- a/cve/CVE-2025-0716/index.html
+++ b/cve/CVE-2025-0716/index.html
@@ -273,7 +273,7 @@
Reproduction
-
+
Examples with href + interpolation
From be724ed44e28d0f6cadfc8fc0cce855383b3c4c6 Mon Sep 17 00:00:00 2001
From: Stefan Nesbigall
Date: Mon, 15 Sep 2025 09:58:44 +0200
Subject: [PATCH 7/7] Mitigation of CVE-2025-4690.
---
README.md | 8 +
cve/CVE-2025-4690/Readme.md | 4 +
cve/CVE-2025-4690/index.html | 306 ++++++++++++++++++++++++++++
cve/CVE-2025-4690/index.js | 53 +++++
cve/CVE-2025-4690/package-lock.json | 30 +++
cve/CVE-2025-4690/package.json | 9 +
src/ngSanitize/filter/linky.js | 10 +
7 files changed, 420 insertions(+)
create mode 100644 cve/CVE-2025-4690/Readme.md
create mode 100644 cve/CVE-2025-4690/index.html
create mode 100644 cve/CVE-2025-4690/index.js
create mode 100644 cve/CVE-2025-4690/package-lock.json
create mode 100644 cve/CVE-2025-4690/package.json
diff --git a/README.md b/README.md
index 4dd4d3a1c988..f0ef36d9d60a 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,14 @@ version in node_modules (npm install first).
To test the mitigation build this repo (yarn grunt package (node 12.22.12)) and change the according index.html
to point to /build/angular.js.
+## CVE-2025-4690
+
+In order to reproduce the problem see /cve/CVE-2025-4690/ (run with angular from node_modules).
+
+A Regular expression Denial of Service (ReDoS) vulnerability has been identified, which allows attackers to cause a denial of service of the application. Due to an implementation bug, the Regular Expression has a super-linear runtime relative to the input size. With a long, specially-crafted input, an attacker could cause a denial of service of the application, monopolizing browser resources or completely crash the application.
+
+As a mitigation the length of the pattern should be restricted to 20000 characters. The input is a text with links, not just a url. So this is a trade-off, between still running in most cases and does not lead to dos < 0.4 seconds.
+
## CVE-2025-0716
In order to reproduce the problem see /cve/CVE-2025-0716/ (run with angular from node_modules).
diff --git a/cve/CVE-2025-4690/Readme.md b/cve/CVE-2025-4690/Readme.md
new file mode 100644
index 000000000000..bcd17e47a9c5
--- /dev/null
+++ b/cve/CVE-2025-4690/Readme.md
@@ -0,0 +1,4 @@
+CVE-2025-4690
+
+https://www.herodevs.com/vulnerability-directory/cve-2025-4690?nes-for-angularjs
+https://codepen.io/herodevs/pen/RNNEPzP/751b91eab7730dff277523f3d50e4b77
diff --git a/cve/CVE-2025-4690/index.html b/cve/CVE-2025-4690/index.html
new file mode 100644
index 000000000000..583b9298813c
--- /dev/null
+++ b/cve/CVE-2025-4690/index.html
@@ -0,0 +1,306 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AngularJS vulnerability: `linky` filter ReDoS
+
+
+
+ AngularJS vulnerability: `linky` filter ReDoS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproduction of AngularJS vulnerability:
+ linky filter ReDoS
+
+ A regular expression used by the linky filter to detect URLs in a text is vulnerable to super-linear runtime due to backtracking.
+ With a large carefully-crafted input, this can cause a denial of service of the application, also known as a ReDoS attack.
+
+
+
+
+
+
+
Reproduction instructions
+
+ The app below demonstrates the issue by passing a user-provided text to the linky filter.
+
+
+ It is essentially the equivalent of:
+
+ linkyFilter(userProvidedText);
+
+
+
+ By using a specially crafted value—for example, a sequence of consecutive alphanumeric characters—you can see that the time taken to complete the "linkification" of the text grows super-linearly with regard to the input size.
+
+
+ Steps:
+
+
+ Set the input text to a value containing many consecutive alphanumeric characters (that are not part of a valid email; i.e. not followed by @something).
+
+
+ You can click one of the available buttons to set the input text to an appropriate value of the specified length.
+
+
+ Click the "Linkify" input text button to pass the input text to the linky filter.
+
+
+
+ Observe that the time it takes to process the input text increases super-linearly relative to the text length.
+
+
+
+
+
+
+
Reproduction
+
+ Set input text to a value with N consecutive alphanumeric characters:
+
+
+
+
+
+
+
+ Time to "linkify" text: {{ app.duration }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cve/CVE-2025-4690/index.js b/cve/CVE-2025-4690/index.js
new file mode 100644
index 000000000000..37df51a970a8
--- /dev/null
+++ b/cve/CVE-2025-4690/index.js
@@ -0,0 +1,53 @@
+// Define and configure the app.
+window.angular
+ .module('app', ['ngSanitize'])
+ .config([
+ '$compileProvider', '$sanitizeProvider',
+ ($compileProvider, $sanitizeProvider) => {
+ // Before v1.8.1, use `imgSrcSanitizationWhitelist()` instead.
+ $compileProvider.imgSrcSanitizationTrustedUrlList(
+ // Only allow images from `angularjs.org`.
+ /^https:\/\/angularjs\.org\//);
+
+ $sanitizeProvider.enableSvg(true);
+ }
+ ]).
+run(['$rootScope', r => {
+ const urlPlaceholder = 'URL_PLACEHOLDER';
+
+ r.updateHtmlPayload = () => {
+ r.htmlPayload =
+ r.htmlTemplate.value.replaceAll(urlPlaceholder, r.imageUrl.value);
+ };
+
+ r.imageUrls = [
+ {
+ label: 'URL from allowed domain',
+ value: 'https://angularjs.org/favicon.ico',
+ },
+ {
+ label: 'URL from disallowed domain',
+ value: 'https://angular.dev/favicon.ico',
+ },
+ ];
+
+ r.htmlTemplates = [
+ {
+ label: '[SAFE] Regular image with `src`',
+ value: ``,
+ },
+ {
+ label: '[UNSAFE] SVG image with `href`',
+ value: ``,
+ },
+ {
+ label: '[UNSAFE] SVG image with `xlink:href`',
+ value: ``,
+ },
+ ];
+
+ r.version = angular.version.full;
+ r.imageUrl = r.imageUrls[0];
+ r.htmlTemplate = r.htmlTemplates[0];
+ r.updateHtmlPayload();
+}]);
diff --git a/cve/CVE-2025-4690/package-lock.json b/cve/CVE-2025-4690/package-lock.json
new file mode 100644
index 000000000000..e98bd3ae674b
--- /dev/null
+++ b/cve/CVE-2025-4690/package-lock.json
@@ -0,0 +1,30 @@
+{
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "dependencies": {
+ "angular": "1.8.3",
+ "angular-sanitize": "1.8.3"
+ }
+ },
+ "node_modules/angular": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz",
+ "integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw==",
+ "deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward.",
+ "license": "MIT"
+ },
+ "node_modules/angular-sanitize": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.8.3.tgz",
+ "integrity": "sha512-2rxdqzlUVafUeWOwvY/FtyWk1pFTyCtzreeiTytG9m4smpuAEKaIJAjYeVwWsoV+nlTOcgpwV4W1OCmR+BQbUg==",
+ "deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward.",
+ "license": "MIT"
+ }
+ }
+}
diff --git a/cve/CVE-2025-4690/package.json b/cve/CVE-2025-4690/package.json
new file mode 100644
index 000000000000..6a5cd7cd0916
--- /dev/null
+++ b/cve/CVE-2025-4690/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "angularjs-vulnerability-reproduction",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "angular": "1.8.3",
+ "angular-sanitize": "1.8.3"
+ }
+}
diff --git a/src/ngSanitize/filter/linky.js b/src/ngSanitize/filter/linky.js
index 564799d59e4b..5684647ff3bb 100644
--- a/src/ngSanitize/filter/linky.js
+++ b/src/ngSanitize/filter/linky.js
@@ -152,6 +152,16 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
var html = [];
var url;
var i;
+
+ // CVE-2025-4690: A Regular expression Denial of Service (ReDoS) vulnerability has been identified, which allows attackers to cause a denial of service of the application.
+ // Due to an implementation bug, the Regular Expression has a super-linear runtime relative to the input size.
+ // With a long, specially-crafted input, an attacker could cause a denial of service of the application, monopolizing browser resources or completely crash the application.
+ // As a mitigation the length of the pattern should be restricted to 20000 characters (the input is a text with links, not just a url.
+ // So this is a trade-off, between still running in most cases and does not lead to dos < 0.4 seconds).
+ if (raw.length > 20000) {
+ window.console.error('Angular: You are trying to parse a large url using linky. Mitigation of CVE-2025-4690: Better to throw an error than to crash the browser.');
+ throw linkyMinErr('inputtoobig', 'Input to linky is too big ({0} characters, only 20000 allowd). This is a mitigation of CVE-2025-4690.', raw.length);
+ }
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];