HTTP POST vs GET: Is One More Secure For Use In REST APIs?
By: Robert Broeckelmann
Tags: APIs, Get, Security, Post, Rest

HTTP POST vs GET: Is One More Secure For Use In REST APIs? Robert Broeckelmann Feb 6·16 min read

Grain / Kamil Porembiński

The use of HTTP POST vs HTTP GET for read-only (or query) operations in REST APIs recently came up in a conversation. For this particular shop, there had been a long-standing ban on the use of GET requests for use in homegrown applications. This had been the case since before REST APIs were in common use and traditional web applications (server-side generated HTML) were the standard architecture. The only problem was no one really knew or remembered why it wasn’t allowed — just that it was “insecure”. It’s unfortunate that such a standard evolved because it prevents the full syntax of HTTP to be utilized in their REST APIs.

Still, we must understand what originally motivated such a standard to to be put in place in the beginning. Exploring motivations for such a standard is the focus of this blog post. This discussion will cover many topics. Unfortunately, there isn’t time to give every topic the attention it deserves but rather summarize the important points related to the relative security of GET and POST requests.

API Design

First, let’s ask ourselves why we might want to use an GET request in a RESTful API? The whole point of an API is to be able to exchange information using the native protocol of the World Wide Web as it was meant to be used — HTTP v1.1 (or v2) for our purposes. This architectural style (philosophy) is laid out in the Roy Fielding PhD dissertation (2000). That dissertation does not specifically discuss when one HTTP method or another should be used. Nor, does it specifically mention CRUD operations. The details of when to use one method over another are left to the HTTP spec, “the language of the World Wide Web.”

So, REST API design has to draw inspiration from other sources and a little pragmatism — and avoid the religion. This includes, at least the subset of available HTTP operations that implement traditional CRUD capabilities (Create, Read, Update, Delete); this is typically POST, GET, PUT, and DELETE. Though, it doesn’t have to be this way. Maybe we replace PUT with PATCH. Maybe DELETE isn’t used and is simply an additional update that PUT/PATCH could address. In a later article, Roy Fielding clarifies several points from his original thesis including:

that he doesn’t mention CRUD (and why)

that REST requires that the methods be used uniformly for all resources (paths)

that nothing in the REST architectural style requires that a specific method be used or not used.

In fact, the title of that article is “It is okay to use POST”. I thought about naming this blog post the same, but I wouldn’t want to be unoriginal. He further clarifies “ The main reason for my lack of specificity [defining how to use HTTP methods] is because the methods defined by HTTP are part of the Web’s architecture definition, not the REST architectural style. Specific method definitions (aside from the retrieval:resource duality of GET) simply don’t matter to the REST architectural style, so it is difficult to have a style discussion about them. The only thing REST requires of methods is that they be uniformly defined for all resources…”

If I adhered to all the pseudo-rules I’ve heard over the years about what can and cannot be used as part of REST APIs, there would be very little of the HTTP 1.1 spec left that could actually be used — which is kind of silly. I had someone tell me once that a true REST API can only use a 201 return code (rather than a 200) for POST calls. Well, I suppose, I’ve violated that rule many times — it’ll be okay. Hopefully, the reader recognizes this paragraph as sarcasm.

If the reader has ever seen any of my REST API design work, they will quickly recognize that I am comfortable with using GET, POST, PUT, and DELETE. This is inline with Leonard Richardson’s API Maturity model. In order to do this with REST APIs, the API designer must be comfortable with and be allowed to use available HTTP methods as appropriate.

Most of this conversation is centered around the use of GET within a web application running in a browser. There are some exceptions. We will consider both end-user browser-based apps and Business-to-Business (B2B) or system-to-system communication via REST APIs.

Relative security of HTTP POST vs. GET

I’ve been in organizations that have implemented a global, heavy-handed, ban of the use of GET requests for API-style communication or even traditional web applications. The reasons cited are usually something along the lines of:

The data will be seen in the address bar of a browser and recorded in the browser history.

GET is somehow less secure than POST when transmitted over the wire

The URL (including query parameters) are logged on the server.

If HTTPS (TLS v1.2 or better) is being used, neither GET nor POST request parameters can be read over the wire. If an AJAX call is made in the background, nothing shows up in the address bar or browser history. An HTTP access log doesn’t have to record query parameters or, if you wanted to be really fancy, don’t log values that are sensitive. Or, better yet, don’t put sensitive values in HTTP query parameters.

These points are potentially valid, but less intrusive solutions can be implemented to mitigate potential problems. Implementing heavy handed policies like banning the use of GET requests in applications without performing a detailed threat assessment amounts to security theater (which means, it probably isn’t helping very much from an overall security posture).

Often these questions center around PII, HIPAA, or similar specifications. Generally, placing this type of data directly into a the query parameter should be avoided. Some type of abstraction should be introduced to the design that can be used to indirectly reference sensitive data — think of something like tokenization for PCI data.

In general, it is strongly advised to operate within the spirit of what these methods are supposed to mean. There is some gray area and room for dogma/religion to creep into it — best to avoid that. Above all else, be consistent. In the absence of any other guidelines, be consistent. Make sure that it is well understood how HTTP methods are being used in APIs that you control.

Following that CRUD strategy described above + OPTIONS method (CORS) is the strategy I have used in almost every REST API I’ve design. The exceptions to that were driven by politics and silly compromises with people that have fallen into the traps described in this blog post.

Side Effects (versus intended purpose) of HTTP GET & POST

GET requests should be used to retrieve data when designing REST APIs; POST requests should be used to create data when designing REST APIs. Creating something is a side effect — if not the point.

The HTTP GET method isn’t supposed to have side effects. It’s considered read-only for retrieving data. It is generally considered a bad practice to implement any type of write behavior (the things that will have side effects) in an HTTP GET. Though, there isn’t really anything preventing a developer from doing this.

The recommendation here is to avoid unintended or non-intuitive behavior by creating some type of “write” functionality in a GET request. Save that for the HTTP POST operations. This is a situation where using POST is recommended from the very definition of the spec over GET. This applies to traditional web applications as well as REST APIs.

HTTP Logging on Servers (and proxies)

If your application is logging sensitive values, no matter how they are passed, STOP IT. Stop it now! This includes the traditional HTTP access log that most web server technology has enabled by default. This is a bad practice and one that basic auditing and adherence to best practices should be able to mitigate.

That being said, this doesn’t mean disable all logging in production. I’ve walked into several shops that either completely disabled all production logging of web servers and application servers or disabled the HTTP access log. It is nearly impossible to do forensic analysis or after-the-fact troubleshooting performed on a system that has had all application and middleware logging disabled. The goal should be to filter out or remove to begin with the logging of parameters that are sensitive or may contain sensitive information. Depending on the product being used, this could either be very easy or nearly impossible. I would consider the capabilities needed here to be part of the technical requirements for any product being considered; though, most organizations aren’t thinking that holistically when making product selections.

If you have control over the application’s infrastructure and what is logged, then it is possible to guarantee nothing sensitive is being logged. The real test is an independent audit (usually part of some type of security review or penetration test) performed to ensure that sensitive values are not being logged.

If your application relies upon third party APIs or systems that your organization has no control, it is much more difficult to directly ensure that best practices are being followed. This can be mitigated by legal agreements, but only in the most formal situations will hands-on audits be allowed; that type of or level of relationship tends to be expensive. For an intermediary such as a proxy server that may be logging request information (URLs), these are hopefully either under the control of the HTTP client organization or HTTP server organization. In either case, the proxy server is part of the trusted infrastructure of one of those organizations. In theory, that trust extends to the other organization through some type of legal agreement. There is an assumption in the phrase “trusted infrastructure” that it is properly configured (including logging) to meet requirements.

One should also take into account how log aggregation and storage is handled (think Splunk and the like). If sensitive values do need to be logged, encrypted file systems and proper access control must be implemented. Who has access to logs? What is the log retention policy? This should all be addressed as part of an organization’s information security standards and best practices, which are applied to the application and the trusted infrastructure.

A lot of this is going to come down to trade-offs of proper auditing, operational support, assurance that sensitive information is not being logged, and if it is, that proper safeguards are in place. Recently, I was part of a conversation where the proper use of trace logging was being discussed. Ordinarily, no sensitive data is logged (info, error, debug-level logging), but in a troubleshooting situation where low-level details such as interaction with the secure credential storage platform (think CyberArk or Hashicorp Vault) need to be explored, it may be necessary to have trace logging that dumps more to logs than one would otherwise want. Following such situations, it’s time to rotate passwords, keys, certs, OAuth2 client credentials, etc.

Some products I have worked with provide direct capability to mark which fields should be logged (not logged) or filtered under specific conditions The typical IT system has many layers and many logs. Effective monitoring is just as complex as the the underlying application being monitored. You can consider proper logging, log aggregation, and log security/sanitizing a part of that monitoring apparatus.

This discussion applies to both traditional web applications and REST APIs.

This section should be its own blog post. I’m veering away from the primary topic a little, but this is important.

Proxy Caching of HTTP GET requests

The comments made in the last section about which organization owns the proxy are relevant here. If a proxy cannot be categorized as trusted, steps should be taken to avoid it.

I haven’t encountered many situations recently where a proxy had generic response caching enabled for APIs. Where I have seen response caching was on something like a CDN in front of an API Gateway or built into an API Gateway. Enabling this feature was usually done with specific intent that made sense for the API responses being cached. When used, it adheres to the HTTP directives regarding how long something should be cached and whether it should be cached.

There is also the variations of caching based on HTTP Header, query parameters, full path, etc. This can vary widely.

I haven’t seen a situation where it was desirable to cache responses on intermediary HTTP proxies for REST APIs with sensitive information. I suppose it could happen, but it would be part of that trusted infrastructure discussion in the last section.

In theory, you could cache the response of any HTTP method, but this rarely makes sense for write operations. If it was necessary to cache something, trying to implement caching of read-only API requests that wrapped in POSTs would be challenging (it could be done with certainly fully featured products, but it would lead to nonsensical situations). This would be a reason to use GET for read-only API calls rather than POST.

This section applies to traditional web applications and REST APIs. For the REST APIs, response caching doesn’t come up that often, but it does sometimes.

Exposing information in the browser (Address Bar, History, Refresh, Cache, etc)

It is true, you can see GET requests with query parameters in the address bar and Browser History. You can also see the full contents of POST, PUT, and other HTTP methods in browser Developer Tools. One could always delete the history; though,this will typically require the cooperation (or at least permission) of the end user.

Hitting the browser refresh button will replay the same HTTP GET request for requests that are in the address bar.

For REST API calls that are made as AJAX calls in the background (let’s call this HTTP requests the browser makes that are not reflected in the address bar), the call is not recorded in the browser history or shown in the address bar (at least with the major browsers). For that reason, I’m not usually to concerned about the use of a GET call with a REST API because none of these concerns apply.

If a browser makes a GET call, the response is cached by the browser if the appropriate cache control headers are provided. This works whether the browser triggers the call or Javascript makes an AJAX call. Most APIs I’ve worked with don’t set cache control response headers to true, but it could happen.

If a machine/device or browser is compromised in some way, the attacker is going to be able to see the information anyway.

I don’t think anything is really lost here by using HTTP GET with REST APIs, with or without sensitive values as long as precautions are taken. The browser history can always be deleted.

This section applies to REST APIs if the API Consumer is using a browser.

HTTP Referer:

HTTP requests generated by a browser will usually include a request header called Referer (the spec misspelled the word back in the day). This will contain the URL of the page that generated it. If sensitive information is contained in a GET request URL, it is possible the same URL could end up in future request Referer headers. A POST request will not have this problem. So, another point in favor of POST requests.

However, for the AJAX REST API calls from a browser, the Referer header is going to have the URL of the javascript file that initiated the AJAX request. This isn’t going to be particularly interesting. For a system-to-system API call, there most likely won’t even be a Referer header. So, in most API-related cases, this isn’t going to be an issue.

Exposing information over the wire

If unencrypted data is passed over a network connection, that data will be viewable by actors with access on that network. On the public internet, this means potentially everybody. This is equally true whether it is HTTP or another clear text protocol or an HTTP POST request or an HTTP GET request.

If all requests are passed over HTTPS (TLS v1.2 or better), the information will not be exposed over the wire. Now, if your corporate, outbound HTTP(S) proxy that the browser is configured to interact with (trust) is inspecting HTTPS traffic, then you effectively have a man-in-the-middle that is reading your encrypted traffic, but this is generally part of a larger compliance and trusted infrastructure setup. So, it’s probably okay for the assurances that we are seeking.

Over the years, people have stated a concern that an HTTP GET URL is exposed over the wire when HTTPS is being used. There seems to be a false impression in some industry circles that TLS/HTTPS only encrypts the message body of an HTTP request. This is NOT how TLS/SSL works. All data that makes up the HTTPS request (including method, path, query parameters, headers, etc) are encrypted on the client and decrypted on the server with AES256 (or better) encryption. That is true regardless of what method is used.

The bottom-line is that the GET and POST requests are equally confidential on the wire when using HTTPS. So, this provides no impetus to use one over the other. This applies to traditional web applications and REST APIs.

Search Engine Spiders

All search engines have a component (a spider) that will periodically scan every reachable site on the internet (adhering to some basic rules). The search spiders (Google, Bing, less, warm-fuzzy, things you don’t really want crawling your site) will follow every link on your site (all the reachable paths that use GET), but will not submit random forms they find. If a GET request has some type of side effect, this could potentially create undesirable situations. For the well-behaved search engines, a robot.txt file will limit what what can be scanned. Likewise, well-behaved search engines aren’t going to follow form POST operations. But, that doesn’t mean someone or something else won’t.

Bottom-line, there are potential issues here with GET requests that wouldn’t otherwise exist with POST requests. As long as best-practices (no side-effects in GET calls, protect sensitive endpoints with proper authentication + authorization, etc) are applied, this isn’t necessarily a problem.

This probably isn’t a problem for a REST API where there isn’t going to be a link to “click” inline in a page that can be easily discovered, but depending on your web application architecture, this may not be true.

Web Accelerators

Web accelerators are software or hardware that aim to speed up the web experience for a human user. An example would be the web accelerator functionality in NGINX. This component may run on the user’s device or be part of a proxy on the network. While the basic idea may be well-meaning, this technology is able to “click” all the links (ie, the GETs) on a page / site within the context of an authenticated user. So, if an application is using GET requests to modify (or delete), it will delete everything the web accelerator tells it to.

This is an example of a “confused deputy attack”.

Most organizations I have worked with aren’t using this type of technology. Nevertheless, it is a vote in favor of using POST requests for any form submission that results in changes to the system, which you should be doing anyway based on the “side effects” discussion earlier.

Confused Deputy Attack

Per Wikipedia, a Confused Deputy “is a legitimate, more privileged computer program that is tricked by another program into misusing its authority on the system. It is a specific type of privilege escalation.” If the browser is the deputy in this story, both GET and POST requests can be involved in a confused deputy attack. If the attacker controls a website (either by owning it originally or taking control through some type of attack), GET and POST requests are equally easy to submit without the user’s consent or action. This is a Cross Site Request Forgery (CSRF), which is a type of Confused Deputy Attack.

This is a concern for traditional web applications and REST APIs, but most likely is going to require the use of a web browser for the API Consumer.

Cross-Site Request Forgery

From OWASP, a Cross-Site Request Forgery attack “is an attack that forces an end user to execute unwanted actions on a web application in which they’re currently authenticated. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker’s choosing. If the victim is a normal user, a successful CSRF attack can force the user to perform state changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrative account, CSRF can compromise the entire web application.”

Suppose a website allows images to be embedded as part of user input to a discussion forum, and is not under control of an attacker. If it allows images to be referenced via URLs on third-party websites, then GET requests will be used. This provides a way to create a CSRF attack by injecting an arbitrary GET request, in theory. There particular situation cannot happen with a POST request. There are, however, other approaches, that can result in a CSRF attack with a POST request.

CSRF attacks can impact POST and GET requests. It is true that it is easier to make this vulnerability happen with HTTP GETs (as noted above, for example) than with HTTP POSTs, but only slightly easier. There are mitigation strategies that can successfully mitigate this type of attack for both GET & POST requests. This is discussed briefly here and here.

This is a concern for traditional web applications and REST APIs, but most likely is going to require the use of a web browser for the API Consumer.

Cross-Site Scripting Attack

Cross-Site Scripting (XSS) attacks trick a user (or more accurately the user’s browser) into executing an arbitrary script (usually Javascript). This usually happens when unfiltered user input is allowed to be used in responses sent back to other users such as in a discussion forum. The unfiltered input could contain executable javascript that performs an undesirable action. This is discussed briefly here and here.

This type of vulnerability can impact both GETs and POSTs. There are mitigation strategies that can work across both GET & POST requests.

This is a concern for traditional web applications and REST APIs, but most likely is going to require the use of a web browser for the API Consumer.

Sensitive values in GET requests

Generally, the best practice would be to avoid putting sensitive values in the URLs (including query parameters). This discussed earlier in the “Relative security of HTTP POST vs. GET” Section.

Given the rich set of functionality available in HTTP-based REST APIs and the ease of working with HTTP GETs to retrieve data in a REST API, a wholesale ban seems counterproductive and unnecessary. Instead, through data classification standards, code reviews, and other techniques described in this post, avoid putting sensitive values in URL paths and query parameters. Consider how such mandates eliminate the use of HATEOAS (it makes it impossible).

Summary

This blog post covered a lot of territory. It is true there are some situations where the use of a POST request has security advantages over a GET request without taking additional precautions, but there are also situations where both are equally vulnerable to common attack vectors. It is critical that best-practices and mitigation strategies for common / known vulnerabilities be built into all web applications. Those small advantages the POST request have do not justify removing a GET request from our API design tool belt.

If we cannot use GET, then we aren’t taking advantage of CRUD design of APIs and the intent of the HTTP spec. The context of browser-based applications versus system-to-system applications impacts the security concerns. In fact, most of the caveats explored in this post are only relevant to browser-based applications. So, when building Single Page Applications (SPAs) or Javascript applications running in a browser it is critically important to follow best practices and only use GET for read-only requests that do not modify the systems. Even for system-to-system communication, there are scenarios where care must be taken with GET requests.

As long as GET requests are used appropriately with all best-practices applied, it can be used safely with REST APIs and web applications.

Image: Grain / Kamil Porembiński