Tales of API Woes From a Security Professional Part 2

Looking at mobile app API examples and the shortcomings of their security posture.

The Mobile Security Guys
The Startup
9 min readSep 11, 2020

--

This is part 2 of our views on APIs. If you haven’t read part 1 then why are you still here? Go take a look :)

Leaking all app data

APIs generally have access to loads of user data; they are the middleman to the transactions between the client interface and the back-end databases. Things like search functionality or filtering will be done on data that the API receives from a back-end database query, however this should be done server side, not locally on the client with stored or received data as this risks exposing a lot of information.

Using Client-side Filtering

With this application, there was a call to api.example.com/call/data as a GET request, the response from the server was the entire listing of companies, emails, usernames, user IDs and real names held by the business; perfect for direct social engineering attacks. This data was then locally stored in a file to be used in the search bar to narrow down results based on pattern matching.

The entire data set was stored locally on the device as a cached file.

As this was a locally cached file, it meant that the data had to be updated frequently, cue numerous GET requests to this /call/data API call over the course of the user journey, all in clear text and without certificate pinning; a major issue.

The idea here is any search term required by the user should be sent as a parameter, the server does the heavy lifting and returns only the specific data required, not everything.

  • App sends search term as a parameter in either a GET or POST to the back-end to perform the lookup.
  • The Database performs the searching/filtering on the dataset using a query based on the parameter received by the app, and passes the filtered data back to the Server.
  • The Server then returns this filtered data back to the app which is relevant to the specifics that were requested.

Admittedly there are some overhead problems to overcome, especially when scaling this up widely, however fundamentally only the data that is specifically searched for would be obtained by the user and not the entirety of the database.

Server Leaks App Data via Response

This next application was seemingly fairly robust; the app itself was quite limited so made attack points difficult to obtain, however there were two instances found where the server response back to the application included the details of the entire user base on the platform. The first involved the use of the Cache.db file on iOS devices.

The idea of the Cache.db file is that it caches all outgoing requests and incoming responses for the application when used with NSURLSession/NSMutableURLRequest functions, this helps speed up loading times if the same request is repeatedly used, much like the cache for your desktop browser. The downside to this is that if the API shares too much, as in this example, and sends back everything for client-side use, then it is all cached. This file is stored in the app private Library directory, so cannot be browsed on standard stock devices.

The same API request URLs that were found cached were also found hardcoded in the applications codebase; which after breaking the SSL pinning and utilising an inline proxy tool allowed us to manually call these to obtain the correct headers.

With the correct headers we could then call /appApi/get_friends/ which resulted in a 200 OK response and the full user base details of the application; just like the cache file but no jailbroken device required to access the data this time. Yes it is hashed, but due to the nature of the parameter names, phone_hash, and matching known hashed data with known plaintext, it was simple enough to script a brute force PoC to obtain certain information in plaintext, included names and mobile numbers; enough for further social engineering or smishing.

Scripting a simple PoC allowed us to brute force hashes to obtain plaintext.

And…?

In both of these scenarios there was a massive leakage of personal user information, knowing a name, company and application builds information for the attack; knowing either the email address or a mobile number creates the attack vector. As seen here, if the entirety of the dataset is sent back to the client we risk putting a lot of our information, and potentially sensitive user information at risk as this can be intercepted, or obtained from the device itself from cached files.

The user should only be sent data that the specific user role is allowed to view based on a set of permissions and restrictions. The entire response to a call should never to sent to the client, especially not one containing so much sensitive information.

Enumeration of Data via API Behaviour

An enumeration attack is the act of abusing the intended behaviour of the system to obtain something that you didn’t previously have, in this case user data. In this context it involves trying different inputs into certain areas to notice subtle differences in how the app or API responds to valid and invalid data. Once we find the particular nuance to identify the difference between the two, we can increment our starting value to enumerate as much data as possible to gain an awareness of what is stored. We’ll explain this a bit more with some examples.

Enumeration involves abusing the system to locate small nuances in the result to confirm or deny the presence of certain data.

The easiest example here is a login form taking an email and a password. The credentials are then checked against the user table in the database and results with either ‘incorrect email’ if the user doesn’t exist or ‘incorrect password’ if the user does exist but the password is wrong. Although both tell us we are wrong, they imply totally different things and allow us to make assumptions about the presence of data.

GET a Forgotten Password

This issue demonstrates enumeration of users based off their email address using subtle differences in the GET request URL. The idea here was to build up a list of valid users from email addresses. The authentication mechanism here wasn’t as option as responses to both valid and invalid email addresses, and an incorrect password, the error message was always ‘Login failed. Incorrect email or password’, so this didn’t help or provide any indication to which was wrong.

However, targeting the Forgotten Password form /pass.html, was more interesting. Entering both a valid and an invalid email gave a stock generic error message about password reset being sent.

However, the subtle difference here was in the GET request. When entering an email address of a user who did not exist the URL changed to append: pass.html?sent=n

The URL suggests that the reset password email has not been sent as the user doesn’t exist.

When the email of a user who did exist in the system was entered, this changed to echo out the email and show that the password reset email has been sent; a different string was appended, pass.html?email=bob.hope@test.com&sent=y

URL changed to reflect that this email is of a registered user.

By abusing the change in the URL, it was possible to build up a list of valid users registered on the platform.

Using this difference in how the app and API responded to the browser meant it was possible to identify which email address related to a valid user, and from this we can expand the number of attempts to obtain a longer list; this list could then be used for social engineering or phishing for a full credential set. Although browser based enumeration is becoming harder to find, little differences like this are still able to be found and something developers generally overlook, or don’t realise it could be used against them.

Response Status Messages

Another way to find enumeration is via an inline proxy tool and the responses that are seen via certain API requests. The differences could be a number of different things here such as HTTP status codes, API error messages or custom status messages, but trying different inputs to notice these variances starts to build an image of the behind the scenes working to help figure out what the back-end is expecting.

The following example shows a few different requests, all to the same API call but with different inputs to test. The format and length of the ID is unknown, and the responses received from the back-end greatly aid in the blind trial and error.

In the first request we can see that a customer ID of AAAAAAA* is invalid and the response, despite being 200 OK, provide a negative status for that input. Compared with the request of 1111111*, provides us with a completely different status code. Based on these two different status codes we are now aware of the correct value, and length for customer ID’s; we can continue this attack using brute force enumeration to locate further customer accounts with automated scanning and increasing the value with a step of 1.

* To get to this stage progressive requests were sent with 4, 5, 6 and 7 characters until the status code changed from input_error to OK.

A customer ID of AAAAAAA is not the input the server was expecting, and provides a negative status code.
1111111 is a valid customer ID and the response is OK along with other details.

Now we have a valid customer ID, we can progress deeper into the app to investigate the authentication mechanism to perhaps enumerate valid usernames. Once again, we would be looking for subtle differences in the response from the back-end server to aid in our progression through the API.

A request for login showing the API status as no data, therefore the user doesn’t exist.

And…?

These examples have provided an insight into a couple of the numerous methods that enumeration can be done via APIs. Two totally different techniques were able to be used to an attacker’s advantage to gain knowledge of the users, these techniques were likely not even thought about by the developers. Enumeration isn’t just limited to customer IDs, emails or usernames, it could be any item of data that would be useful for an attacker to have.

APIs are now all around us for the majority of today’s technology. However, out of sight, out of mind won’t work here as whether its a security professional, or a mischievous user, these differences in how requests are handled leaves the door ajar.

Responses from APIs should be generic and not provide any positive or negative outcomes based on any set of data. Custom error codes could also be designated for certain responses which are then documented internally for troubleshooting. The typical end user will never see the API, or any of these return codes, so using certain words or language that allows the inference of how the server reacts can be a great early foothold into your system for those curious enough to be looking.

Securing the API and ensuring security is embedded into its development is just as important as implementing a secure web or mobile app. Forgetting to do so, or not understanding the ramifications of not having a secure API may lead to trouble further down the line.

--

--

The Mobile Security Guys
The Startup

A diverse collection of posts on mobile and API-based security and testing techniques, contributed by a group of mobile professionals in our spare time.