39

We're building a game for Android, which needs access to web services - so we wrote a RESTful API in PHP that runs on our own server. What the API offers is: creating user, logging in, downloading games, retrieving game list, submitting score... etc. Now I'm thinking, if some experienced user gets the URL format of the API - s/he will be able to trash the system in many ways:

  • Create a script & run it to create automatic users - I think I can prevent it by CAPTCHA or someting like that. But again, captcha will annoy game players.
  • Malicious user logs in using his browser, downloads game & then submits score as he wish - all via calling the API by simply typing it from his browser. I assume malicious user somehow knows API urls to call - by sniffing when the application was making HTTP requests.
  • I need to ensure that requests are made only from Android device that installed the game. (The game will be free)

Now How do I prevent such abuses?

Shafiul
  • 2,832
  • 9
  • 37
  • 55

5 Answers5

45

I think you will never be able to hide the urls being called by the application (if I am running a root-ed android phone, I should be able to spy on all network traffic)

But your real problem is that you need to authenticate your api in some way.

One way would be to implement OAUTH, but maybe this'd be overkill.

If you want a simple mechanism, how about this;

  1. create a secret key
  2. build the api request (eg. https://my.example.com/users/23?fields=name,email)
  3. hash this request path + plus your secret key (eg. md5(url+secret_key) == "a3c2fe167")
  4. add this hash to your request (now it is https://.....?fields=name,email&hash=a3c2fe167)
  5. on the api end, do the same conversion (remove the hash param)
  6. check the md5 of the url and the secret key

As long as the secret remains secret, no one can forge your requests.

Example (in pseudo-code):

Android side:

SECRET_KEY = "abc123"

def call_api_with_secret(url, params)
  # create the hash to sign the request
  hash = MD5.hash(SECRET_KEY, url, params)

  # call the api with the added hash
  call_api(url+"&hash=#{hash}", params)
end

Server side:

SECRET_KEY = "abc123"

def receive_from_api(url, params)
  # retrieve the hash
  url_without_hash, received_hash = retrieve_and_remove_hash(url)

  # check the hash
  expected_hash = MD5.hash(SECRET_KEY, url_without_hash, params)

  if (expected_hash != received_hash)
    raise our exception!
  end

  # now do the usual stuff
end
Matthew Rudy
  • 16,724
  • 3
  • 46
  • 44
  • 1
    Thank you very much, @matthew-rudy :) Do I create `secret key` per user basis? If then, how do I distribute it? If I send the secret key to the application after registration as a response, one can simple make API call from his desktop browser & get the secret key as response. – Shafiul Oct 03 '11 at 04:48
  • 11
    In Matthew's approach, the secret key is never sent over the wire. It's a "salt" that's appended to the url to create a hash. Since the hash is a one-way function, the user can't recover the secret key from it. It would require the user to decompile the APK and look through the source code to find the key. You can use code obfuscation techniques to make this more difficult, but it will always be possible for someone to find the key, given enough time and motivation. – Jason LeBrun Oct 03 '11 at 04:52
  • 3
    indeed @JasonLeBrun, I've added a pseudo-code example. But, giga, I suggest you search for an existing framework in PHP, rather than creating your own. I'm sure there must be many existing solutions. – Matthew Rudy Oct 03 '11 at 05:07
  • 1
    Great answer. I am a new here, 1 query. Since you said "if I am running a root-ed android phone, I should be able to spy on all network traffic". So any hash I am sending on network can be found as a part of URL. So anyone can request this URL once it is found. Hence anybody with https://.....?fields=name,email&hash=a3c2fe167 can request my server. Please suggest. – Suyash Dixit Jul 17 '16 at 14:11
  • Replay attack is still open here. – Hamzeh Soboh Nov 22 '16 at 07:32
  • @SuyashDixit : how did you work around this problem ? – j10 Jun 06 '17 at 06:08
  • This will most likely work for your game. The one thing I want to call out is this security won't work for something with more sensitive information. Someone could disassemble your code and get the secret key and then they could easily fake requests to your API. The way to truly lock is down is when your user logs in, the server needs to generate a GUID token for that account and return it to the app. The app then needs to provide that token on every request and the server validates it, and once the token expires the app asks the user to login again and generate a new token. – Cory Nov 18 '18 at 18:11
  • Why would an attacker need secret key ? md5 hash for a url eg https://my.example.com/users/23 will be same every time its hashed with same secret. Attacker will just need hash for the urls which he wants to target. Attacker can capture ur request (through proxy etc), and notice the hash being sent... as long as attacker sends the same hash for same url, it will pass authentication – Sudhir N Feb 28 '23 at 14:54
  • @SudhirN Then make the hash more complicated. Add stuff such as current timestamp over the secret key, into the string before hashing. Yes, this is called security by obscurity, which is not that great; but since this is just a video game, nobody would soo keen to reverse engineer your APK this far, just to cheat your game. Also you can implement extra logic on your game server backend, to detect cheating. For example, level 10 player can't simply send HTTP request that he has just killed a level 100 boss, it doesn't make any sense. Also they can't possibly get 10 million coins from mobs. – Daniel Wu Mar 01 '23 at 08:30
  • @DanielWu how would server grab the current time stamp to calculate hash itself, so tht it can compare both hashes.... send the time stamp as in header etc? – Sudhir N Mar 02 '23 at 16:36
  • @SudhirN Yes, include the timestamp in API call as some non conspicous Header. If the POST operation has some JSON body, we can also include it into the string to be hashed. It might disrupt game performance though if you make it too complicated . – Daniel Wu Mar 03 '23 at 05:48
11

Solutions that others have presented here are called security through obscurity. Basically they are trying to obscure the protocol and hide the implementation. This might work until someone capable enough disassembles the app and reverse-engineers the protocol. Hackers are very capable at that.

The question is if your app is worth cracking? Schemes like iTunes, DVD or Sony PS3 network were obviously worth the effort. The obscurity approach might work if no one capable of cracking cares. Just don't fool yourself that it is not doeable.

Since you can not trust the device or your app, you must trust the user. In order to trust the user, you need user identification and authorization system. Basically a login to your app. Instead rolling you own indentification system (login with confirmation emails, etc..), use a 3rd party system: OpenID (google accounts) or OAuth (facebook, twitter). In case of facebook use the server-side auth scheme.

What I'd do:

  1. Allow users to freely play the game until they want to "save" the results on server.
  2. Before saving their results have them login via above mentioned method.
  3. Use HTTPS to send the data to your server. Buy a ssl certificate from trusted CA, so you don't have to deal with self-signed certs.
Peter Knego
  • 79,991
  • 11
  • 123
  • 154
3

You mentioned users faking the high scores. This could still happen if your users are authenticated. When the game is uploading the high scores you may want to have it also upload a proof of the score. For example Score 20100 from 103 bugs squished, 1200 miles flown, level 3 reached, and 2 cherries were eaten. This is by no means perfect but would cover the low hanging fruit.

The first you should do is have authenticated users. Userid/password/session token etc., see if you can find some already existing frameworks. Once you have user authentication make sure you can do it securely with TLS or similar.

As far as I know there is no way your server can be certain that the request is coming from your application (it's all just bits in packets) but you can at least make it hard for someone to be malicious.

Community
  • 1
  • 1
goldsz
  • 348
  • 2
  • 7
  • Thanks for your comment. Instead of uploading the total score, splitting it into parts sound less harmful. – Shafiul Oct 03 '11 at 18:50
  • @giga It's not just the splitting up. You want them to submit the score, and proof that the submitted score was generated by something that knows how scores work (your application would know this). If you just split it, then a malicious user could still just submit arbitrary values. You want to force them to have to reverse engineer your application and not just snoop the communications. – goldsz Oct 03 '11 at 21:12
2

If you really want to secure the connection then you'll have to use public key cryptography, e.g. RSA. The device will encrypt the log in information using the public key and in the server end you will have to decrypt using the private key. After login the server will send a token/encryption key (the response will be an encrypted JSON or something) and the device will store that. From then as long as the session is not expired the device will send all the information encrypted using that token. For this requests you should not use RSA cause that will take more time. You can use AES256 (which is a popular private key encryption) with that encryption key received from server to encrypt your requests.

For sake of simplicity you can drop RSA altogether (If you are not sending payment information) and do everything using AES256 with a private key. The steps should be -

  • Encrypt every outgoing request with a private key.
  • Convert the encrypted string to a base 64 string.
  • URL encode the base 64 encoded string.
  • Send it over.

On the server end

  • Do base 64 decode
  • Decrypt using the private key.

Your request should carry a signature (e.g. the encryption key appended as a salt) so that it becomes possible to identify it after decrypting. If the signature is not present simply discard the request.

For sending responses do the same.

Android SDK should have methods for Encrypting with AES256 and Base 64 encoding.

rushafi
  • 863
  • 10
  • 19
  • How would that be better then HTTPS? – Peter Knego Oct 03 '11 at 05:36
  • I never told that it would be. It's an workaround if you don't have a HTTPS Certification. – rushafi Oct 03 '11 at 05:40
  • You can have a self-signed certificate with HTTPS, if you dont wont to buy a cert from a CA for a few bucks. You are also proposing a private key solution so this is technically the same, just much harder to implement. – Peter Knego Oct 03 '11 at 05:49
  • I implemented it in iOS devices and didn't seem much hard to me. You don't have to write codes for AES encryption and decryption. There are available codes for free. And AFAIK android sdk have native functionality for this. Anyway there are multiple solutions for securing a connection. Any solution that works are acceptable. – rushafi Oct 03 '11 at 05:58
  • In my world the least-cost (= least effort) is the acceptable one. – Peter Knego Oct 03 '11 at 06:22
1

Follow these guidelines from the Android team to secure your backend, by using Oauth tokens provided through Google's APIs.

skunkwerk
  • 2,920
  • 2
  • 37
  • 55