Before jumping into the latest post in our discussion of vulnerabilities in the MEAN stack, look back at the first three posts on MongoDB, ExpressJS (Core), and ExpressJS (Sessions and CSRF).
Angular 1.2 and greater include the built-in strict contextual escaping service ($sce) by default. This service strips malicious HTML tags (e.g., <script>), attributes (e.g., onmouseover, onerror), and URI protocols (e.g., javascript) from data rendered as HTML with the ng-bind-html directive. This service can be disabled globally with the $sceProvider.enabled() method in the controller’s config block or per instance with the $sce.trustAs methods. But doing so leaves the application vulnerable to cross-site scripting (XSS) attacks when binding untrusted data as HTML.
Consider the excerpt below from our example application where the $sce service is disabled and the untrusted user input is rendered with the ng-bind-html directive.
If we submit the payload <img src=x onerror=alert('XSS_SUCCESS!')></img>, we can generate an XSS exploit.
XSS exploit with SCE disabled
The simplest solution is to leave the $sce service enabled for all untrusted input bound to the ng-bind-html directive. Removing the $sceProvider.enabled(false) method from the excerpt above means the malicious onerror attribute will be sanitized appropriately.
SCE strips malicious onerror attribute and prevents XSS exploit
Alternatively, the data could also be rendered with the ng-bind directive, which renders the text outside of the HTML context and performs HTML encoding on all malicious characters, such as angle brackets (<>) or double quotes (").
Angular templates (defined by the ng-app directive) use double curly braces ({{}}) to denote expressions that are evaluated by the Angular rendering engine. If an attacker can inject curly braces into the template, they can easily enter the code execution context. To further complicate matters, off-the-shelf HTML encoding or sanitizing functions do not handle curly braces.
Consider the login page for the example MEAN Bug application, which reflects the username during a failed login attempt.
Username reflected in message
If we examine the source code, we see that the message is rendered as a text attribute, encoding all HTML control characters.
If we attempt the following payload, we see that the <script> tags are encoded and the exploit does not work.
http://localhost:9000/login?user=<script>alert('XSS_SUCCESS!')</script>
HTML characters encoded to prevent XSS
However, because the text is rendered within an HTML element bound by the ng-app directive, anything encapsulated by double curly braces will be evaluated as an Angular expression. If we submit the following request, we can enter a code execution context within an Angular expression.
http://localhost:9000/login?user={{constructor.constructor('alert("XSS_SUCCESS!")')()}}
This request also contains a sandbox-escaping payload (i.e., constructor.constructor()), which allows us to break out of the Angular scope and do things like generate an alert message.
Angular expression injection used to execute XSS
RELATED: Break out of the Angular sandbox
You can remediate this vulnerability by sanitizing curly braces from untrusted input. You can also prevent the input from being written inside an Angular template by reducing the scope of the ng-app directive (e.g., bind ng-app to a div or table element rather than the body element).
Alternatively, the ng-non-bindable directive can be used to tell the rendering engine to ignore any expressions within that element. The example application has been modified with this solution to prevent the expression-injection exploit from executing.
Directive ng-non-bindable used to prevent expression injection
Angular’s angular-storage service allows applications to easily persist data client-side in web storage without the size restrictions imposed by cookies. However, this service defaults to using local storage, which persists indefinitely unless it is explicitly cleared. This data will remain on the user’s file system even after the browser is closed, exposing the data to any user with physical access to the system. The excerpt below demonstrates how the angular-storage service is used in its default configuration.
The data persisted by the angular-storage method store.set is in the browser’s local storage.
Sensitive data stored in local storage will persist indefinitely
Because any client-side data is inherently insecure, local storage should not be used to persist sensitive data such as credentials or credit card numbers. A slightly more secure alternative is to configure the angular-storage service to use session storage, which is cleared automatically when the browser tab is closed. This can be done with the storeProvider service within the config block as shown below.
Application configured to use session storage
This example uses the angular-storage module, but keep in mind that data may also be persisted to local storage (and session storage) via other Angular modules such as ngStorage and angular-locker or by calling $window.localStorage or $window.sessionStorage directly.
READ NEXT: Node.js: Preventing common vulnerabilities in the MEAN stack