Very recently, the Cybersecurity & Infrastructure Security Agency (CISA) published an analysis report (AR21-013A) on strengthening security configurations to defend against attackers targeting cloud services. The analysis concluded that cyber threat actors involved in cloud service attacks use a variety of tactics and techniques including phishing, brute force login attacks, and possible ‘pass-the-cookie’ attacks.
CISA reported they verified that threat actors successfully signed into one user’s account with proper multi-factor authentication (MFA) and in that case, CISA believes the threat actors may have used browser cookies to defeat MFA with a “pass-the-cookie” attack (Use Alternate Authentication Material: Web Session Cookie [T1550.004]). This part raised a few questions on the effectiveness of multi-factor authentication (MFA) and if MFA is still fit for the purpose.
Web applications are typically off-line applications, meaning that the client-side of the application will contact the server-side or service whenever it needs an update unless the client-side has logic to act locally on events or data stored on the client such as is the case with Progressive Web Applications (PWA).
A PWA is a type of application software delivered through the web, built using common web technologies just as HTML, CSS, and JavaScript. It is intended to work on any platform that uses a standards-compliant browser, including both desktop and mobile devices.
A PWA is a single-page web application that can be installed and can run offline on any device that has a compliant browser. The application can access local storage, mobile device sensors, provide support for push notifications, etc. A PWA is much like an online application that provides real-time information from its web service when online but can work with locally cached information when offline and it looks and behaves like a native application.
A PWA does not require proprietary app stores to be delivered and installed; installation is simply done through the web browser. It can run on any platform that has a compliant browser and uses the browser’s run-time, meaning that it can run on Windows, macOS, Chrome, iOS, iPadOS, Android, Linux, etc.
PWAs extensively rely on the browser’s JavaScript engine and used to be written exclusively in JavaScript. Not everyone is a fan of JavaScript which resulted in JavaScript transpilers allowing developers to write complex client-side applications in Scala, Dart, CoffeeScript, Groovy, Lua, and others. No need for cross-compiling or rebuilding the application; in the case of a transpiled application, there is a translation step needed, but the application will run natively on all platforms with a standards-compliant browser.
More recently, in March of 2017, WebAssembly or Wasm was introduced as an open standard. Wasm defines a portable binary-code format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications. One could compare Wasm to the Java run-time. Java applications are compiled into bytecode that can execute on any platform that support a Java run-time. The difference between the Java run-time and Wasm is that Wasm is open standards and developed in a W3C Community Group that includes representatives from all major browsers, resulting in Wasm being support on every standards-compliant browser and platform much like Javascript. Since Wasm relies on compiled code, much like Java bytecode, it executes much faster and with less overhead compared to transpiled applications that are JavaScript interpreted by the web engine.
Wasm enabled languages such as Go, Rust, and dotNet C# to be compiled and delivered across the internet as binary code and execute efficiently in the context of the client browser’s engine, which could be an in-browser application or a native looking application. Wasm is redefining the way PWAs are developed and allows developers to write online applications and PWAs in C#, Rust, Go, or any of the 40 high-level programming languages that support WebAssembly, like C and C++, Python, Java, and PHP.
Even with all the progress made on the development side, the way that state is managed between server and client in online applications is very much unchanged from the way it was done back in the ‘80s.
Whether you are writing a web application, a progressive web application, or a web service, the application framework will need to manage the state between the client and server application. Even if HTTP is considered a stateful protocol, the context between the client application and the server application is still very much stateless. The communication circuits between the client and web or HTTP server during the lifetime of an application are rebuilt and destroyed for each new request originating from the client. I’m ignoring persistent HTTP connections, as these are merely a performance enhancement and do not change the way the state is managed between the client application and the service.
To make the interactions between client applications and their service stateful, web applications and web APIs have been using tokens that are stored on the client and transmitted to the server to identify the ‘session’ of the user.
There are several options to store states and tokens on the client side. One can store state in hidden variables and build application logic to post those variables with each subsequent request such as a POST or a GET. The state can also be stored in cookies which, by design, sends all related cookies to the server for each request – host and domain names are used to decide which cookies need to be sent to the server on each request.
The advantage of cookies is that they can provide persistence across browser sessions and browser contexts. Even if a user closes his browser, the cookie can persist in storage and will be available when a user restarts his browser. Also, when a user opens a second browser and connects to the same host or domain, the corresponding cookie will be transferred — a good way to make an application stateful and to keep track of users’ behavior over time.
Browsers can be instructed to treat cookies as volatile and not store them persistently, a browser can also be instructed to only transmit cookies over encrypted sessions to ensure privacy, such as for a secret token that would be used for authorizing the user.
By now it should be clear that to authorize user requests, the client application or browsers needs to provide some context to the web service (server-side) in the form of a secret token transmitted as an argument of the request or as a cookie.
Before a user is authorized, he needs to be authenticated and during the process of authenticating a user, the server will generate a unique token to identify that user and his session and will use it to authorize any subsequent requests. Once authenticated, whatever the complexity of the authentication process or whether MFA was involved or not, the security of the communication relies only on the token exchanged between client and service. Whoever knows the token can submit requests to the server and the server will be none the wiser about the identity of the requestor.
By consequence, stealing a token from a session between a user and the server will allow an attacker to perform any action on behalf of that user and within the context of the user. The token will be valid until the server invalidates the token in its server-side store or cache and this typically only happens when the client application requests it when the user explicitly logs out of the web application.
If the user does not explicitly log out but closes his browser window, the token will not be invalidated immediately but might get invalidated based on the timeout of the session or the total allowed lifetime of a user’s session. The timeouts are a server application side implementation and depend on the authors of the application. If a user does not explicitly log off from his web application by clicking a button and the browser sending the request to the server, an attacker can very much keep the session alive using the stolen token and continue to make requests within the user’s context until he decides he has done enough damage, or the server decides to timeout the session.
This vulnerability applies to all applications that implement state between client and server application and is not limited to web applications. Native mobile applications, native client/server applications, and web APIs leverage very similar ways of creating context. Only, in the case of a mobile application and machine-to-machine Web APIs, cookies do not exist as they are a browser service. So, the latter need to implement their own tokens which is what API tokens are about. Many APIs still use static tokens that are generated only once and can be used for a long time, some indefinitely.
Third-party web-based authentication frameworks such as SAML and OATH adopt a similar model using tokens, only are the tokens now exchanged and authorized with additional third-party services and not directly between client and server. These authentication frameworks allow the delegation of authority for server-side applications. An organization can manage identities and authorizations in its own identity server while the server-side application will authenticate users through that identity server and will authorize access based on the authorizations from that same identity server. Sill, the same token and vulnerabilities, and by consequence the pass-the-cookie attack, applies to those authentication implementations.
There are several ways to counter pass-the-cookie attacks, but all come with their own drawbacks:
Use client certificates. Give users a persistent token that can be stored securely on their system and that will be used in every connection to the server – this can be achieved using a client certificate stored in the user’s profile on the operating system. While the most secure option, the logistics make it much harder to roll out and as such, typically only used by applications that require access from a limited set of users. For example, business partners or employees that need access to a B2B or an internal online application. For eCommerce sites, however, where every human on our planet is a potential user, this does not scale.
Use a dynamic token that changes every few seconds or minutes, just like IPSEC VPNs implement a rekey timeout. This will reduce the window of opportunity for the attack. If the attacker is not fast enough to leverage the token, his stolen token will be invalid by the time he uses it. However, it does not completely mitigate the attack, only reduces the window of opportunity.
Add more context besides the token to identify the origin of the request. This could be, for example, using the source IP address of the request. Proxies and internet load balancing on the user side can cause issues and if the attacker is within the same public place (e.g., coffee shop, airport, etc.) or organization, both will use the same NATted IP of the gateway and will both be identified as the same legitimate user.
Use client-side fingerprinting such as a browser version, installed browser extensions, font names, etc. There has been a lot of controversy on fingerprinting; just like cookies, it allows tracking of a user but without the user being able to refuse the tracking. Cookies can be disabled or refused by the browser and allow anonymous access, whereas fingerprinting does not allow the user to disable it. However, it is still one of the best and most convenient ways to add unique identifying context to the request to ensure the person is who he claims to be.
Whatever the authentication used (MFA, SAML, OAUTH, electronic ID, pseudo-random number generator, etc.), the ‘pass-the-cookie’ attack is still very much relevant and cannot be thwarted by the strength of the authentication.
The strength of the authentication method dictates how accurate it is at identifying the person and preventing someone else to pose as that person. MFA is still very much relevant and fit for purpose, but as always, the chain is only as strong as its weakest link. A strong authentication combined with a weak token implementation will provide opportunities for malicious actors, the same as weak authentication with a strong communication implementation will leave it vulnerable to abuse through credential stuffing and brute-forcing.
Some randomness in tokens can reduce the window of opportunity for the attacker, but only additional and continuous identification between client and server can help, and that could be implemented in the form of client-side certificates or browser identification methods – with the latter probably not as secure as the certificates