Why We Write POST-only APIs
By Aditya Geria
The modern internet-based application predominantly runs on HTTPS over TLS/SSL, transferring bits and bytes between Internet-connected devices over an encrypted channel during transport of packets. So why are all of PCAP APIs restricted to POST requests, as opposed to also utilizing GET requests?
Having to handle our users’ personal financial data, we take information security seriously. To facilitate this, all our APIs are only allowed to communicate over HTTPS using the “POST” method to keep data in transit as secure as possible against information leaking, replay attacks, and request forgery.
The major difference between POST and other HTTP methods with regards to security is that other methods can include sensitive data in the URL whereas a POST method sends data in the request body.
Here are examples of a GET and POST request in transit. Note that these would normally be encrypted by TLS.
GET /test?param1=hello¶m2=world HTTP/1.1
Host: subdomain.test.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.1) Gecko/20100101 Firefox/10.0.1
Accept: image/png,image/*;q=0.8,*/*;q=0.5
Accept-Language: en-gb,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
POST /test HTTP/1.1
Host: subdomain.test.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.1) Gecko/20100101 Firefox/10.0.1
Accept: image/png,image/*;q=0.8,*/*;q=0.5
Accept-Language: en-gb,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
param1=hello¶m2=world
Note that the parameters for the request live in the URL for a GET request, and in the body for a POST request. This difference is critical for the risk scenarios below:
Logging Risk
- It’s common for server processes to log the whole URI of client requests (e.g. https://google.com/?q=how+to+get+rid+of+flabby+arms). These logs are usually not encrypted and are often made accessible to diagnostics/monitoring teams. Using POST HTTP methods helps prevent API request bodies from being distributed since we can make better guarantees that they are encrypted and not logged.
- Similarly, URIs are logged in end users’ browser history which can lead to similar exposure in a shared computer scenario.
Replay Attacks and CSRF Vulnerability
- A URL with a GET request can be sent to another user maliciously and replayed, for example: https://fakebank.com/transfer?account=123456&value=1000 or https://www.fakebank.com/account/changepassword.php?newpass=hacked takes advantage of a user who has already logged into this bank (and has a cookie which keeps them logged in), to send a GET request to this bank transferring funds to a specified account.
- A URL which performs an action via a GET can be spread more easily and replayed (often unintentionally) by an unsuspecting user.
- POST requests don’t inherently defend against this either! A POST request cannot be forced by a link, but can still be replayed easily Chrome’s “Replay XHR” or any request-capturing tool such as Postman.
- A common POST request parameter is a CSRF token, which is validated by the server to make sure that the request coming in isn’t part of a replay attack. A CSRF token is used in Personal Capital’s login flow, to ensure that Bob cannot use Alice’s CSRF token and vice versa when logging in as part of a CSRF attack.
Phishing Risk
- As phishing is a core risk for consumers as well as employees of corporations, minimizing the ability for Personal Capital users to be successfully phished helps their security overall.
- The risk with phishing that relates to using the HTTP GET method is that a service that allows URI query parameters allows for a clickable URL (e.g.: in an email) to be constructed and distributed to fool users and lead to their account being compromised or taken over. This is especially dangerous when combined with a cross-site scripting (XSS) vulnerability.
Browser Behavior
- A GET request, because the parameters are in the URI, can be bookmarked and “replayed” easily by re-visiting the URI. A POST request cannot be replayed as easily because the parameters are in the request body.
- For POST requests, the parameters are not stored in a browser’s history. Browsers do store URLs in history, so GET requests would be stored.
- A POST request also allows binary data (which means you can upload files, Unicode characters, etc.) with no restriction on length, whereas a GET request only allows ASCII characters and is generally limited to a “safe URL length” to 2048 characters.
- GET request parameters can be visible to browser plugins because the information is present in the URL.
Wrapping up
POST offers a greater level of confidentiality than GET for sensitive information in transit, like the data we consume in the Personal Capital app. One key reason for using POST requests is to trivialize the risk of sensitive information like PII (Personal Identifiable Information) being leaked via logging to external tools and visible to 3rd party services. POST requests are also harder (but still not impossible) to replay because they cannot be saved or sent to users maliciously via a URL, and require forging request parameters — which is much harder than forging a URL. And finally, POST requests do not show up in browser history and cannot be sniffed by browser extensions, which could possibly lead to information being leaked. For these key reasons, Personal Capital requires all of our web APIs to use the POST HTTP method.