Hidden dangers of Unsafe GET
Unsafe GET is a common security issue in web applications yet it is not so commonly discussed or spoken of. It is not a vulnerability per se but too often leads to serious security implications, including being the root cause (in a practical sense) of otherwise known vulnerabilities.
Table of contents:
- What is Unsafe GET?
- Disclosure of Sensitive Information
- CSRF (cross-site request forgery)
- How Do You Prevent Unsafe GET?
What is Unsafe GET?Unsafe HTTP-GET (simply “Unsafe GET” in this article) is an HTTP request that is performed via the method GET and does not comply with the definition of a “safe” HTTP method as defined by the HTTP standard (RFC7231).
This is a little bit too formal, so let’s dig deeper into what that means.Section 4.2.1 of the RFC 7231 “Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content” defines “safe” HTTP methods. Standards tend to be very long, dry, and boring so let’s rephrase it a bit:
A “safe” HTTP method is essentially read-only and is not expected to change state or cause any harm when applied.
Safe methods are: GET, HEAD, OPTIONS, and TRACE.
The purpose of the definition of “safe” methods is to allow various actors to make and/or process certain requests safely.
The definition does not prevent implementations from doing whatever they want, such as changing state and/or including potentially harmful behavior, while processing “safe” requests.
Also, clients are not held accountable for any harm caused by implementations that deviate from the definition of “safe”.
So, basically, search engines, web crawlers, web browsers, web servers, web frameworks, load balancers, etc. are free to assume that a GET request is harmless. And they do.
When web browsers, web servers, web frameworks, etc. make this assumption as a default, certain security measures, important for state-changing requests, will be relaxed or omitted in their entirety.
Barring some possible rare edge cases, Unsafe GET leads to the following distinct security issues:
- Disclosure of Sensitive Information
- Increased attack surface for CSRF, and
- Bypass of protections against CSRF.
Disclosure of Sensitive Information
All the data sent in a GET request will be placed in the request-target (i.e. in the URL), due to GET requests not having defined semantics of a payload body as outlined by the Section 4.3.1 of the HTTP standard.
Unsafe GET, as a state-changing request, is likely to contain sensitive information, such as passwords (via login forms) or personal information (via profile editing forms), or any time a user provides such information to the server (which is normally done via POST).
It is interesting to note that the standard explicitly calls out security implications of this behavior in Section 9.4:
Authors of services ought to avoid GET-based forms for the submission of sensitive data because that data will be placed in the request-target. Many existing servers, proxies, and user agents log or display the request-target in places where it might be visible to third parties. Such services ought to use POST-based form submission instead.
Unfortunately, this did not prevent countless oversights in web applications over the last 22 years since the consideration was originally published in its modern form.
Here is a possibly incomplete list of places sensitive information sent via GET can end up:
- History of the user agent (web browser)
- Logs of proxies (load balancers, firewalls, etc.)
- Logs of the web servers (reverse-proxies)
- Logs of the application itself, and
- Logs-aggregation SaaS.
Although many third parties will process requests in one way or another and can technically have access to contents of a POST request in transit, the logs will remain there at rest. Almost certainly accessing live production request streams requires a different (higher) level of privilege than accessing logs for debug and investigation.
Having sensitive data in the URL (like in the case of Unsafe GET) can disclose such information to individuals who were never meant to access it, including staff outside of the company owning the web application.
CSRF stands for cross-site request forgery and is a type of attack that targets state-changing requests where reading the response is not required (as this would be forbidden by the same-origin policy).
The aim of a CSRF attack is to initiate an action on a web application without the user’s explicit consent or awareness.
CSRF exploits the fact that cookies are sent along with every request by default as well as the fact that some cross-origin requests are allowed by default. However, it means that requests need to be created in the same web browser in which a user is logged in into the target web application (for the requests to have session cookies).
There exist several mechanisms in modern web browsers to prevent CSRF. However, as we will see later, they are surprisingly easily bypassed in the case of Unsafe GET.
Increased Attack Surface for CSRF
Unsafe GET is a state-changing GET request and thus is a potential target for CSRF.
A hyperlink, whether followed in a web browser directly or clicked on in a variety of other applications, will result in a GET request made by the web browser (the default one if opened from another application).
Here is a non-comprehensive list of places where it would be possible to place a clickable link:
- Messages in messaging apps (Slack, WhatsApp, Viber, Telegram, etc.)
- Hypertext documents (PDFs, Google Docs, etc.), and
- Any text (posts/comments/messages/descriptions) with Markup/WYSIWYG (Jira, Github, etc.).
To put it simply, just about anywhere you can put a clickable link, it is possible to put a CSRF attack on an Unsafe GET.
Considering that most people use only one web browser or use a single (default) one for almost everything, Unsafe GET widens the attack surface of CSRF substantially.
Bypass of Protections against CSRF
One may argue that there are multiple modern mechanisms that would make CSRF practically impossible to perform successfully.
Let’s look at each of them and see whether they actually protect us in case of Unsafe GET.
The same-origin policy is a security mechanism of a web browser that defines the defaults regarding which cross-origin requests are allowed and which requests are same-origin only.
The SameSite attribute of the Set-Cookie HTTP response header allows you to declare whether the cookie should be restricted to a first-party or same-site context.
It can have one of three values: None, Lax, and Strict.
None is just as it was historically (cookie is sent along with every request).
Strict means the cookie is never sent cross-origin for any requests.
Lax, however, is much more interesting, because it allows cookies to be sent when a user is navigating to the origin site (i.e, when following a link).
Did you catch that minor yet crucial difference? If you set this attribute to Strict, following a link to your web application will not include cookies and can make the user appear logged out or break any other features relying on cookies.
Most web applications will have to use Lax simply to behave properly and in the way a user expects.
However, Lax means that cookies are always attached to GET requests.
Built-in CSRF protection in web frameworksTo keep this one short, web frameworks simply skip CSRF protection for “safe” HTTP methods by default. Just to name a few major ones: Django, Spring, Ruby on Rails (links point to the proof in the source code).
Unless explicitly enabled for a request, GET requests are exempt from CSRF protection in web frameworks.
Origin and the Referer headers
Checking Origin and the Referer headers is one of the common ways to mitigate CSRF at the edge (firewall, proxy).
However, it performs extremely poorly for GET requests, as can be deduced from the caveats described in the OWASP CSRF Prevention Cheat Sheet. There are many cases when these headers are empty, null, or omitted entirely, so just to name a few:
- Normal navigation (following a link) doesn’t have the Origin header
- Referer is stripped if a site sets appropriate Referrer-Policy
- Same-origin GET usually doesn’t have the Origin header, and
- The Origin header is removed following a 302 cross-origin redirect.
Consequently it is not feasible to enforce this type of protection on GET requests in practice because a CSRF attack can be made indistinguishable from a totally legitimate normal navigation.
How Do You Prevent Unsafe GET?
Unsafe GET is unsafe purely due to the implementation details and the behavior of a web application, so it can be very tricky to identify it through static or dynamic automated scans.
The best approach would be to not let it happen in the first place, at the time when a new action (route) is added to a web application.
Many implications of Unsafe GET are not immediately obvious, and developers may consciously choose GET over POST and assume it is fine when it is not. Raising the awareness of its security implications is one way to reduce the amount and the likelihood of Unsafe GET routes in a web application.
Most web frameworks have a well-known and structured way to define actions (routes), specific to a particular framework. Although handled differently among various frameworks, for a particular case, a simple static analysis (with a script or even regexp) can be performed to find potentially unsafe routes. For example, to find if a verb (“create”, “delete”, “move”, “transfer”, etc.) is used for an action that is routed via GET, except for maybe trivial read-only actions (like “show”).
In any case, awareness of the issue is the first step towards finding the solution.
Unsafe GET is indeed unsafe not merely because of semantics but for very practical and real reasons.
Unsafe GET can leave sensitive data in places where you do not want it to be.
Unsafe GET is shockingly susceptible to CSRF due to the increased attack surface and bypass of multiple levels of defense.
Modern web frameworks make it very easy to prototype web applications and define various aspects of them in a simple way. A state-changing GET instead of POST usually “just works” and doesn’t draw any suspicion from a developer’s perspective while opening an extra opportunity for malicious actors.
If an Unsafe GET happens to be present in a web application due to an oversight, it is very likely that no additional protective measures have been explicitly taken, so it is vulnerable.
Be aware that GET behaves much differently than POST for state-changing requests and can lead to severe security implications.
Keep that fact in mind when working on your web applications and stay safe :)
Written by: Vlad Hesal, Security Engineer.