Hacker News new | ask | show | jobs
by Osiris 4478 days ago
Could someone with a solid security background provide a example of how to properly handle the issues that this API fails so badly at?

While some developers may be able to clearly identify bad practices, best practices may not always be so clear.

I'd love to know what a best practice would be for things like authentication to an API and some of the other issues brought up here.

5 comments

I wouldn't say I have a solid security background, but there are four best-practices I can think of that would have prevented the security vulnerabilities outlined by this post:

1. Hash the secret API token/key given to each client that is sent to the server with each API request. This will prevent attackers from being able to find out your secret token.

If you only hash the secret token though, this still won't help, as attackers could just send other API requests along with the hashed token. Instead, you will want the client to hash other data unique to the specific API request as well. For example:

    http://api.example.com/api_function_name?app_id=my_app&hash={hash_value}&other=stuff
Where the hash_value is computed by the client with something like:

EDIT: Clarified hash_function parameters, thanks @eru.

    hash_function( secret_key, api_function_name)
The API server will then receive the request, look up the client's secret_key based on the app_id, and run the same hash_function to make sure it matches hash_value in the request.

This would mean attackers couldn't reuse the hashed value to send other API calls. But they'd still be able to send calls to that function and just change the parameters to the call to get other information from that API call. So, you could also include the other API call parameters in the hash_function as well, which would mean attackers could only replay that exact API call and not change any of the parameters.

You might notice, this is still not good. So, to prevent this "replay attack", you would also generally include the current datetime in the API call as well:

    hash_function( secret_key, api_function_name, datetime )
Now, attackers can't even replay the exact request, because by the time they do, the datetime will have changed and so the API server would reject the request if it was replayed later. And since the attacker still doesn't know the unhashed secret_key (since it's never been transmitted in plain text), they can't change the datetime without invalidating the hash_value.

This is theoretical though, because in reality the above wouldn't work well if the clocks on the client and server were at all out of sync (and they probably will be). So, usually, you'd also have to include the current datetime of the request as another parameter in the call to let the server know exactly what datetimestamp was used in the hash_function, and the server will simply make sure the datetime is within an acceptable window of the current datetime on the server. Of course, the bigger the window, the easier to get the API working with clients, but the larger the window for allowing replay attacks.

    http://api.example.com/api_function_name?app_id=my_app&hash={hash_value}&other=stuff&time=datetime
And lastly, the chosen hash_function for the API should be something not easy to brute-force (meaning don't make it easy for attackers to listen to a few API calls and be able to reverse-engineer the secret_key, since they'll already know what hash_function is from the API documentation).

OK, 1 was longer than I anticipated, but the others are pretty short.

2. Another more full-proof way to prevent attackers from getting secret tokens, hashed or unhashed, would be to make all API requests work only via HTTPS.

3. Don't provide API (or any) access to user passwords.

4. Don't store user passwords in plain-text, or even via simple hashes. Instead use a cryptographically secure hashing function with salts.

Sorry for being harsh, but your "solutions" are mostly useless in the context of providing an API for phone apps.

Use OAuth or similar and make sure every user has their own account. That's the only answer. Don't roll your own! Especially don't roll your own when you don't have a solid security background. You have obviously heard some of the right terms, but how and where you can apply them is at least as important as using them at all.

1. Doesn't protect anything at all. Hash functions don't do anything when people have access to the program code. No matter how fancy you go with time limited hashes (and there are smarter ways to create those). At most it adds a few minutes to the reverse engineering.

2. Again, this doesn't protect against anything. HTTPS stops intercepts on the wire, not someone who has access to your app, people can still lift the secret keys and the hashing scheme from the app binary.

3.& 4. Both true, and would protect against mass stealing of the passwords like happened here, but it wouldn't prevent abuse of the API.

There are very many people who use techniques like the ones you suggest in 1. and 2. and the same very many people have vulnerable apps that usually expose all users' data to the world. There are a lot of apps that store e.g. user files or some sort of configuration not on a per user OAuth protected storage like OneDrive, DropBox or Google Drive, but either there but on just the account of the developer, or on another storage that is only authenticated with the developer's credentials. People who do that allow anyone to read and modify the data of all users, exactly the same as people are allowed to do to their own data, or more if the credentials aren't properly limited, even if it's blocked in the app.

When it comes to API security, you can't be too harsh! I guess I was trying to explain it more conceptually, since it sounded like the person asking didn't understand the concepts.

But I stopped short on #1, as your post points out. You're absolutely right when it comes to designing an API for consumption by apps that will be distributed as packages.

I can't update my original post with a clarification, so here's what the end of #1 should have said:

1 (continued)

If either the clients' source or compiled code can be inspected by attackers (which is true for distributed, i.e. native mobile or desktop, apps), you don't want to make client app developers include their secret authentication key directly in their app. In this case, consider using OAuth 2.0 for authentication instead.

With OAuth, the client app has the user authenticate with their own credentials, where the API will respond with a per-user access token (that can easily be revoked per user if necessary), which the client app will then use for subsequent authorized requests.

You can also include an extra random number in the hash, and require that within the window of acceptable timestamps the random numbers have to be unique.

By the way, be aware than hash(string1 + string2) constructions are often vulnerable. hash(hash(string1) + string2) is better for most hashes, I believe. But you shouldn't roll these primitives yourself, either. Just use a proper library.

You're absolutely right about the hash constructors. I was trying to make it clear what's happening conceptually, but that's why I included the note to choose a cryptographically secure hashing function. HMAC is generally a good hash constructor pattern to use.

EDIT: Just realized it'd probably be clearer to replace the pluses with commas in my examples, to avoid implying the relation between the parameters of the hash_function.

   hash_function( secret_key, api_function_name, datetime )
Do you have specifics on why strcat would be vulnerable? Or is it just because "abc"+"def" == "abcd" + "ef"?
That, and length extension attacks (https://en.wikipedia.org/wiki/Length_extension_attack).
HMAC.
I think you're describing an HMAC[1] authentication code here.

Certainly signing your requests as actually having come from your application is a legitimate means of security. But there are much lower hanging fruit (in terms of available security measures). Specifically, using SSL to handle API traffic. That should absolutely always be step 0. In instance of this specific post, had the API provider enforced using SSL as their transport protocol we probably wouldn't even be having this discussion.

There's a ton of other security measures/protocols that should be taken and followed, most of which are already talked about in other parts of these comment threads. I just wanted to really point out that what you're describing above sounds an awful lot like an HMAC code.

[1] http://en.wikipedia.org/wiki/Hash-based_message_authenticati...

You're correct; if you see my reply here [1], you'll notice I mentioned it by name.

See #2 in my original response concerning SSL. And notice the difference in length and complexity between #2 and #1; it was written this way intentionally to highlight the complexity of doing security authentication properly, in order to encourage the use of SSL, given that it is both more "full-proof" and simpler.

[1] https://news.ycombinator.com/item?id=7371259

EDIT: Please also see the point @Rizz pointed out, in that you'd still want to use something like OAuth, since HTTPS doesn't solve the issue of an attacker knowing your client's API key by inspecting its distributed code.

I think your efforts on #1 are moot because I don't think there's a way to include the API key in a mobile app such that it can be used by the app but not extracted by an attacker.
#1 is a fairly standard security concept used by protocols like oAuth or JWT. It requires an API key pair (public and secret key).

The secret key is only used for signing and is never passed in the request. Used in combination with nonces and time stamps you can make a secure API that isn't susceptible to replay attacks.

Doesn't https take care of the same issue, though? And it doesn't solve the problem that if you're shipping an app the secret key can be found. So what does it solve?
You're correct - you shouldn't ship an app with a secret key embedded. That would be a flawed implementation.

It's hard to be specific without knowing what you're doing. If you have an app that connects to a third party API like Twitter, that's one situation. If you have an API that other app developers will connect to - that's a second scenario. And third is if you have an API and you write your own app to connect to it.

OAuth handles all three of these scenarios but in #1 you are a consumer, in #2 you are a provider and #3 you are both.

Check out 3-legged oAuth for an example of how to allow apps to talk to your API on behalf of a user, without that user having to give their password to the app. It's actually pretty interesting, clever and simple all at once!

HTTPS encrypts the traffic - making it difficult to sniff. It doesn't actually provide authentication though.

Even if your api uses oAuth I don't see how can you prevent the client app to steal the password. At some point the user is going to have to give his password to someone. Can't the app ask the user for his password, keep it, and internally give it to oAuth to allow to use your api?
Even if it's not passed in the request it's still in the app so isn't it vulnerable to reverse-engineering?
There shouldn't be a key baked into the app. Each user gets their own unique key. so the worst you could do reverse engineering the app is to steal your own key. There should never be any "master" key used for all users.
What's the advantage of using your hand-rolled hashing scheme instead of just https?
There's not really any advantage as far as I know. In fact, as I stated in my explanation, using HTTPS is more full-proof. So why wouldn't you do that instead?! That's the point.

If I had to think of an advantage though, it'd be for the sake of any developer who needs to design their first API, and is inspired by yours. If you just used #2 then with plain-text authentication, and a developer copies your API without using SSL, then they'll have a horrible security problem. If you used #1 as well, then they'll still have an okay API, it just won't be suitable for use by packaged/distributed client apps.

In fact, this is purely conjecture, but that could be what happened here in the case of criticker. Who knows?

You could just run fiddler on windows and trust the fiddler certificate. This would allow it to MITM the https session.
Not if they pin the certificate.
First of all you need to make a decision. Do you require your users to entrust the client software with their passwords or not.

If you can do that, the solution is pretty simple. Do it as you would do it with any website. Simply use https (TLS) to transmit username and password and return a session cookie to use in subsequent requests. Run your API over https only.

If you don't want your users to entrust client software (i.e. apps) with their passwords then use https with OAuth.

The reason why using https alone works well for web apps is that users can trust their browsers. Browsers can know the password and they probably won't steal it.

However, if you provide an API and you expect many different client apps, including some dubious ones, to use that API on behalf of your users then users cannot trust the client software and hence you should use OAuth.

The decision doesn't depend on whether or not your application stores sensitive data, because users often use the same password for different sites. So if you like your users and you provide an API for mobile apps to use, you should use OAuth.

Usually with an APIkey, you have a corresponding "Secret" Key.

This is called a shared secret.

Using the shared secret, you can come up with a unique signature, that only yourself and the host can generate.

You also want to use some sort of TTL for the signature, to prevent replay attacks.

Passwords should never be stored in plaintext. They should be hashed using a cryptographically secure hashing function (bcrypt is easy enough).

Password hashes shouldn't ever be exposed to anyone.

If you need to provide login functionality, provide a method that takes a username and password.

Make sure that username and password method has a backoff time to prevent someone from partying on that api (calling it with username and password combinations)

As the password has to be sent in clear text, make sure your login api is over SSL.

When I have a simple app send a password to the server, I like to generate a hash on the client side and then rehash again on the server for storing.

As far as APIs, the good ones will hash your secret key together along with other data unique to your HTTP request, in particular the headers and the datetime. This is a good idea because: 1. you are not sending your secret key in clear text 2. it makes it difficult for a man-in-the-middle attack because they cannot just take your hash from one of your requests since it will be invalid after received or if some time has elapsed.

For an example, see how Knox sets up requests for Amazon AWS: https://github.com/LearnBoost/knox

> I like to generate a hash on the client side

I'm confused. What do you do with that hash then?

It is sent to the server, instead of the clear-text password. This isn't really necessary if you're using HTTPS, however.
Sending a straight hash of the password is no more secure than sending the password in cleartext - an attacker can just replay the hash they sniffed off the network.

As skyebook said, use HTTPS. There's no excuse.

This practice is more to prevent the user's password from being revealed than it is to prevent others from logging in as you. In a trusted environment, you may not care that someone there has access to your account, but you don't want them to know what password you chose.

Having said that, do use HTTPS when possible, but keep in mind some corporate environments force proxies that can see your traffic anyway.

I agree there's no excuse not to use (and force) HTTPS, but the parent did say:

> hash your secret key together along with other data unique to your HTTP request, in particular the headers and the datetime

So that isn't a straight hash and you can't just trivially replay. It does require you store the secret in the clear (or at least reversibly) on the server, but I see a lot of APIs do that...

For anyone reading: please just use HTTPS!
Using a standard library for authentication like oAuth or similar is generally a better idea than creating your own. It's also usually easier since you don't have to re-invent the wheel.

Aside from that, I don't see a reason for an API to be able to retrieve passwords. If passwords need to be reset then the API could maybe issue a pass reset request that would email a confirmation link.

The plain text password is something that's beyond the API design, but a one way hash is generally better with an algorithm that is recognized as being secure (not MD5).

Basically, to repeat, simply not designing your own security but using recognized libraries will typically be a better idea.