CVE-2021-21277 (JavaScript, published 2021-02-01T15:15Z)
Description: value: angular-expressions is "angular's nicest part extracted as a standalone module for the browser and node". In angular-expressions before version 1.1.2 there is a vulnerability which allows Remote Code Execution if you call "expressions.compile(userControlledInput)" where "userControlledInput" is text that comes from user input. The security of the package could be bypassed by using a more complex payload, using a ".constructor.constructor" technique. In terms of impact: If running angular-expressions in the browser, an attacker could run any browser script when the application code calls expressions.compile(userControlledInput). If running angular-expressions on the server, an attacker could run any Javascript expression, thus gaining Remote Code Execution. This is fixed in version 1.1.2 of angular-expressions A temporary workaround might be either to disable user-controlled input that will be fed into angular-expressions in your application or allow only following characters in the userControlledInput.
Severity: MEDIUM (CVSS v3 base score: 8.8)
Weakness: CWE-94 \u2014 Improper Control of Generation of Code ('Code Injection')
The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
Repository: https://github.com/peerigon/angular-expressions
Files changed:
CHANGELOG.md
lib/parse.js
test/main.test.js
Commit message: Fix Security Vulnerability by using hasOwnProperty defensively


Vulnerable code:
  isIdentifierContinue: validChars
function angularInit(element, bootstrap) {
	var appElement,
		module,
		config = {};

	// The element `element` has priority over any other element.
	forEach(ngAttrPrefixes, function (prefix) {
		var name = prefix + "app";

		if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
			appElement = element;
			module = element.getAttribute(name);
		}
	});
	forEach(ngAttrPrefixes, function (prefix) {
		var name = prefix + "app";
		var candidate;

		if (
			!appElement &&
			(candidate = element.querySelector("[" + name.replace(":", "\\:") + "]"))
		) {
			appElement = candidate;
			module = candidate.getAttribute(name);
		}
	});
	if (appElement) {
		if (!isAutoBootstrapAllowed) {
			window.console.error(
				"Angular: disabling automatic bootstrap. <script> protocol indicates " +
					"an extension, document.location.href does not match."
			);
			return;
		}
		config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
		bootstrap(appElement, module ? [module] : [], config);
	}
}
				this.lazyAssign(intoId, this.computedMember("i", ast.watchId)),
							member = self.computedMember(left, right);
							member = self.nonComputedMember(left, ast.property.name);
										x + ".call(" + [left.context].concat(args).join(",") + ")";
						self.assign(self.member(intoId, left, property.computed), right);
	computedMember: function (left, right) {
			expect(evaluate({ a: "" })).to.eql(undefined);
		//
		//

Fixed code:
### 1.1.2

- Disallow access to prototype chain (CVE-2021-21277)

  isIdentifierContinue: validChars,
				this.lazyAssign(intoId, this.unsafeComputedMember("i", ast.watchId)),

						const inAssignment = self.current().inAssignment;
							if (inAssignment || self.state.computing === "assign") {
								member = self.unsafeComputedMember(left, right);
							} else {
								member = self.computedMember(left, right);
							}
							if (inAssignment || self.state.computing === "assign") {
								member = self.unsafeNonComputedMember(left, ast.property.name);
							} else {
								member = self.nonComputedMember(left, ast.property.name);
							}
										"(" +
										x +
										" === null ? null : " +
										self.unsafeMember(left.context, left.name, left.computed) +
										".call(" +
										[left.context].concat(args).join(",") +
										"))";
				self.current().inAssignment = true;
						self.current().inAssignment = false;
						self.recurse(ast.right, right);
						self.current().inAssignment = true;
				self.current().inAssignment = false;
						self.assign(
							self.unsafeMember(intoId, left, property.computed),
							right
						);
	unsafeComputedMember: function (left, right) {
	unsafeNonComputedMember: function (left, right) {
		return this.nonComputedMember(left, right);
	},

	computedMember: function (left, right) {
		if (this.state.computing === "assign") {
			return this.unsafeComputedMember(left, right);
		}
		// return left + "[" + right + "]";
		return (
			"(" +
			left +
			".hasOwnProperty(" +
			right +
			") ? " +
			left +
			"[" +
			right +
			"] : null)"
		);
	},

	unsafeMember: function (left, right, computed) {
		if (computed) return this.unsafeComputedMember(left, right);
		return this.unsafeNonComputedMember(left, right);
	},

		describe("Security", function () {
			it("should not leak", function () {
				evaluate = compile(
					"''['c'+'onstructor']['c'+'onstructor']('return process;')()"
				);
				const result = evaluate({});
				expect(result).to.equal(undefined);
			});

			it("should not leak indirectly with string concatenation", function () {
				evaluate = compile(
					"a = null; a = ''['c'+'onstructor']['c'+'onstructor']; a = a('return process;'); a();"
				);
				const result = evaluate({});
				expect(result).to.equal(undefined);
			});

			it("should not leak indirectly with literal string", function () {
				evaluate = compile(
					"a = null; a = ''['constructor']['constructor']; a = a('return process;'); a();"
				);
				const result = evaluate({});
				expect(result).to.equal(undefined);
			});
		});

			expect(evaluate({ a: "" })).to.eql(null);