8

Apple provides an endpoint to validate receipts: https://buy.itunes.apple.com/verifyReceipt and warns against calling the endpoint from the app

It is not possible to build a trusted connection between a user’s device and the App Store directly because you don’t control either end of that connection, and therefore can be susceptible to a man-in-the-middle attack.

It is stated, that the safe way is to send the receipt to "my own" server firstly, then communicate with Apple endpoint from the own server.

Honestly, I do not understand how does it help to increase the level of security. Yes, I do not control the /verifyReceipt endpoint, but Apple hopefully does. And why

phone <-> my server <-> Apple server is better than phone <-> Apple server?

Could you elaborate on this from point of view of the hacker (or man-in-the-middle)? How would he tamper the receipt/response in the latter case and what makes it difficult for him in the former case?

Nick
  • 3,205
  • 9
  • 57
  • 108

4 Answers4

9

It's relatively easy to change an app binary to alter a string within the app. If your binary contains a string "https://buy.itunes.apple.com/verifyReceipt" then an attacker can change the string to "https://example.com/verifyReceipt" (a server that's under their control) and have the server return the receipt JSON that makes it seem like a purchase was successful. This is an easy attack to automate, so you just run a binary through a script which replaces all instances of the Apple URL with another URL.

When you communicate with your own server to Apple you can be (relatively) sure that that connection is secure. There are also ways to secure the communication between the app and your server such as a certificate in the app binary with a public key, the private key on your server. (I'm not an expert at this part so the details about keys might not be 100% correct).

All this being said there are other ways an attacker can make it look like a purchase went through. They can search your app binary for common receipt parsing libraries and flip known booleans (e.g. a userCompletedPurchase boolean flag). These attacks are easily scriptable.

The best way to make sure that your app isn't attacked is to remove the low-hanging fruit and make it that much more difficult that the common scripts won't open your app to attack. One way to do that is to not verify receipts directly with Apple.

nevan king
  • 112,709
  • 45
  • 203
  • 241
  • 1
    Let's suppose that I, instead of sending request to https://buy.itunes.apple.com/verifyReceipt, I am sending it to my server say https://my.secure.server.com/verifyReceipt. The attacker can substitute (in app binary) https://my.secure.server.com/verifyReceipt to https://his.hacking.server.com/verifyReceipt, can't he? Does it mean that the only difference is that Apple's server URL is widely known and my URL is not so known, so it's harder for attacker to find it in the app's binary code? – Nick Sep 25 '19 at 17:54
  • Yes, the biggest problem is that Apple's URL is well known and the attack is well known. It's a very basic attack and it's likely to be in app cracking toolkits. Using your own server you could have other checks that used a different path and URL (for example a simple call to a URL that always responds with some specific text). – nevan king Sep 25 '19 at 21:04
  • 5
    My personal point of view is that using Apple's URL is not the worst mistake. Setting up a server of your own is time-consuming and costs money. You have to decide if it's worth it for the number of sales you might lose through piracy. For a small app it's not likely to be worth it except as a learning experience. Apple always says that you should never use the URL directly in the WWDC sessions but I think if you're aware of the risks you can decide what to do yourself. – nevan king Sep 25 '19 at 21:04
  • Just as an idea (without knowing much about that technique at all), what about implementing a simple string check to prevent a string exchange? 'let url = "buy.itunes.apple.com/verifyReceipt"; if url[2] != "y" || url[5] == "t" || ... then reject". At least at a first glance that looks like it should block a simple string-replace attempt from working. Or does binary replacement can handle this? – Leo Mar 05 '21 at 12:28
2

I think that the key part to the section from Apple documentation that you cited, but didn't include, was the sentence before.

Using your own server lets you design your app to recognize and trust only your server, and lets you ensure that your server connects with the App Store server.

In particular the first half of that sentence (emphasis mine). I see this as a bit of nudge from Apple that the app should be designed as such to "recognize and trust only your server".

In the simplest case (ie. minimal), one could use their server as a proxy to reach Apple's server. The server could simply pass Apple's payload back to the app, or maybe send some simple JSON like

{
    success: true
}

Or maybe even it's something as lazy as just sending a 200 response.

Using MITM, it's easy to change the response as needed. So even though you are using your server, the only barrier at this point is the attacker would need to determine what a valid response looks from your server.

They may be able to figure it out by simply sending a bad receipt and then seeing the response. Or maybe a hacker will TOFFT and just make a purchase to get the valid response and then provide others so they can profit.

This is "one step" above going to Apple directly, because everyone knows what the Apple payload is. Thus if the app goes directly to Apple it's easier for someone to be able to run a MITM and replicate a valid receipt response.

Going back to what Apple wrote, they are encouraging you to have a design that is more robust to help mitigate potential fake purchases from occurring. This robustness is what protects you more.

This can take the form in many ways.

For example, perhaps your server sends back the response in an encrypted format. Or replies with a hash. Even better if your app relies on your server through the course of usage, so the server can act as the truth. Or maybe the app periodically "phones home" to get a config of privileges. There are numerous ways one can make it more difficult for a hacker.

Apple is leaving to you just how you determine that trust with your server is established. It is after all, your "money".

As an example, the latest game I've made is specially built to use the server as the truth. So all moves go through the server (game is simulated on the server). While someone could certainly modify the app, in the end the server should theoretically correct any badness introduced.

Note, that it still is possible that someone can bypass even more robust security measures.

Mobile Ben
  • 7,121
  • 1
  • 27
  • 43
1

You can validate the iOS receipt on the iOS device. But you cannot be sure that the receipt is actually valid. The user could have hacked the device to make you think the receipt is valid.

Your users can edit the executable code of your app, and they can edit the operating system. With common/shared APIs like Apple's purchasing system, there are publicly available tools a user can run on their own phone to avoid paying.

Your server, however, is controlled by you. Your customers do not have physical access to it. And therefore your server (hopefully!) cannot be hacked. This means your server can be trusted, unlike the device. When your server establishes an SSL connection to Apple's server, you know you really are talking to Apple's server. Not one that your user-installed to bypass in-app purchasing.

UPDATE

Let's say you have got your Receipt after a user purchased your consumable product. So now you want to validate it and you have two options:

1. Receipt validation locally(from a user device).

2. Receipt validation through your server.

Local validation: When you receive the receipt, you then read the contents of the receipt file by passing it through the verifyReceipt endpoint from your app. And you will get a response include a readable JSON body from this endpoints. Based on this response you can validate the receipt and control user action.

And all this happened in your app[user device]. After publishing your app you can't modify your app(if you need) without another release. Also, the user has more control of their device than you.

Validation through your server: After getting Receipt data you need to encode the data in Base64. Send this Base64-encoded data to your server.

On your server, create a JSON object with the receipt-data, password (if the receipt contains an auto-renewable subscription), and exclude-old-transactions keys detailed in requestBody. Submit this JSON object as the payload of an HTTP POST request. In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt as the URL. In production, use https://buy.itunes.apple.com/verifyReceipt as the URL.

Now you will get a JSON object response from App Store's that contains the keys and values detailed in responseBody. According to the response, you can validate receipt and send a response to the app for further actions.

So, if the user buys something in your app you don't need to store the thing being purchased inside the app. You want to store it on a server, and that server only sends the purchased data to the device after it has verified the receipt with Apple's server.

And you have more control in your server than your app also you can change any mechanism about receipt validation at any time(don't need to update your app) and control your user from the server.

Hope you will get it. Now it depends on you which one you prefer for you.

Jakir Hossain
  • 3,830
  • 1
  • 15
  • 29
  • 1
    Let's suppose that I, instead of sending request to https://buy.itunes.apple.com/verifyReceipt, send it to my server say https://my.secure.server.com/verifyReceipt. The attacker can substitute (in app binary) https://my.secure.server.com/verifyReceipt to https://his.hacking.server.com/verifyReceipt, can't he? Does it mean that the only difference is that Apple's server URL is widely known and my URL is not so known, so it's harder for attacker to find it in the app's binary code? – Nick Sep 25 '19 at 17:54
0

I will try to put my understanding here as I am deeply involved in auto-renewable subscription workflows.

Why is that endpoint there? what is the purpose?

The purpose of that API is to verify the purchase that user made using your app. So why do you want to call that API from the mobile(phone). In that environment, you already know that user has made the purchase.

But if you want to call that API to decode the encoded receipt inside the app(to read important values), then consider the other recommended approach. That encoded receipt is PKCS7 encoded and Apple itself provide lib to decode that.

Hope this helps

Yogesh Kaushik
  • 440
  • 3
  • 14