Prevent replay attacks appStoreReceiptURL app receipts
Asked Answered
C

4

6

We have a server-side service that we only want to offer to valid users of our paid iOS app. (Note that this is a paid iOS app, not a free app with IAP.)

When we use appStoreReceiptURL to check the sandbox app receipt and send it to our server side, we see a receipt like this:

{
  "receipt_type": "ProductionSandbox",
  "adam_id": 0,
  "app_item_id": 0,
  "bundle_id": "com.example.myapp",
  "application_version": "1.1.1",
  "download_id": 0,
  "version_external_identifier": 0,
  "receipt_creation_date": "2018-04-16 23:53:58 Etc/GMT",
  "receipt_creation_date_ms": "1523922838000",
  "receipt_creation_date_pst": "2018-04-16 16:53:58 America/Los_Angeles",
  "request_date": "2018-04-17 03:25:42 Etc/GMT",
  "request_date_ms": "1523935542798",
  "request_date_pst": "2018-04-16 20:25:42 America/Los_Angeles",
  "original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
  "original_purchase_date_ms": "1375340400000",
  "original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
  "original_application_version": "1.0",
  "in_app": []
}

I'm concerned about replay attacks with this receipt. In a replay attack, one device purchases the app and submits a valid receipt, but a second unauthorized device stores and transmits an exact copy of the first receipt. Since the first receipt is signed by Apple, the duplicate appears valid.

Ideally, we'd defeat a replay attack by observing a unique identifier in the receipt; if someone attempts to retransmit the same receipt ID, we'd know that it's a duplicate. IAP receipts include a transaction_identifier field for exactly this reason.

But there appears to be no unique identifier that we can use to recognize replay attacks with paid-app receipts. Hackers can retransmit this receipt to us from different devices and we'll have no way of knowing whether it's a duplicated receipt or a new, original receipt.

Having said this, my eye is drawn to those _id numbers that are 0 in the sandbox receipt: adam_id, app_item_id, and download_id. Can we use any of those to recognize duplicate receipts? Or is there some other, better way of handling this?

Cordy answered 17/4, 2018 at 4:57 Comment(4)
Does this help? #17670247 and futurice.com/blog/validating-in-app-purchases-in-your-ios-appDiehl
Not as far as I can see. The problem is that there's a good approach to prevent replay attacks for IAPs (check the transaction identifier), but not for paid apps. The links there discuss protecting IAPs, not apps.Cordy
Yes, a replay attack would be possible in theory but I don't that it is worth worrying about this too much. To be successful the attacker needs a valid receipt and a way to place it in the correct place on his devices to be found and used by your app. this is possible of course but way to complicated for the normal user. Thus only attackers who really, really want to break your system will ever try this approach. How many of these attackers are out there and does it do any harm to your business if they succeed? The worst that could happen is that a hand full people use your app without paying.Masseur
Receipt base64 string can be extracted from POST request if you send it to your serverUnqualified
C
1

It's impossible to detect duplicate receipts for paid apps. The adam_id, app_item_id, and download_id are undocumented, and so developers can't rely on them for security purposes. This is unlike IAP receipts, which include a transaction_identifier that can be deduplicated.

There is a possible workaround, though. You can offer users non-consumable IAP that costs $0 (Free), and require users to "buy" it in order to access server-side functionality. Since it's a non-consumable IAP, each paid-app user can purchase it at most once.

It's a bit of a hassle for users to agree to the free "purchase" and to sign in to the App Store to access it, but once they do, they'll own an IAP tied to their paid-app receipt. The free IAP receipt includes a transaction identifier; the server can use the transaction ID to deduplicate purchases.

Cordy answered 14/5, 2018 at 19:45 Comment(0)
M
0

The unique identifiers you're looking for aren't available in the JSON format of the receipt, to my knowledge. However, the ASN.1 format of the original receipt payload is supposed to have them (i.e. the properties that attach uniqueness to the receipt). Specifically, Apple says to use the device GUID hash, provided in the receipt, as a validation point. This should be included in the ASN.1 receipt, but not in its JSON transformation.

(I'm sure you've seen this, but just in case) Apple's receipt validation guide

I believe that if all you have maintained server-side is the stripped-down JSON, you're kind of out of luck. Would love to be proven wrong, of course.

Minnich answered 8/5, 2018 at 2:20 Comment(3)
I don't think that will work for server-side validation. The GUID hash is based on the UIDevice identifierForVendor, which "changes when the user deletes all of that vendor’s apps from the device and subsequently reinstalls one or more of them." Users can delete and reinstall the app to generate an unlimited number of signed receipts with different GUIDs.Cordy
Ah, yes, too true, too true.Minnich
best way is to convert receipt base64 string into MD5 hash and store it in your DB and use it to check for future duplicates ...That way even if your POST request gets intercepted and sent out it will be still rejected ..Unqualified
I
0

According to Apple documentation your answer will be found here

This code will validate the app receipt-

 func validateAppReceipt(_ receipt: Data) {
    let base64encodedReceipt = receipt.base64EncodedString()
    let requestDictionary = ["receipt-data":base64encodedReceipt]
    guard JSONSerialization.isValidJSONObject(requestDictionary) else {  print("requestDictionary is not valid JSON");  return }
    do {
        let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
        let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt"  // this works but as noted above it's best to use your own trusted server
        guard let validationURL = URL(string: validationURLString) else { print("the validation url could not be created, unlikely error"); return }
        let session = URLSession(configuration: URLSessionConfiguration.default)
        var request = URLRequest(url: validationURL)
        request.httpMethod = "POST"
        request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
        let task = session.uploadTask(with: request, from: requestData) { (data, response, error) in
            if let data = data , error == nil {
                do {
                    let appReceiptJSON = try JSONSerialization.jsonObject(with: data)
                    print("success. here is the json representation of the app receipt: \(appReceiptJSON)")
                    // if you are using your server this will be a json representation of whatever your server provided
                } catch let error as NSError {
                    print("json serialization failed with error: \(error)")
                }
            } else {
                print("the upload task returned an error: \(error)")
            }
        }
        task.resume()
    } catch let error as NSError {
        print("json serialization failed with error: \(error)")
    }
} 

The advantage of asking the app store to validate is that it responds with data that you can easily serialize to JSON and from there pull out the values for the keys you want. No cryptography required.

As Apple describes in that documentation

device -> your trusted server -> app store -> your trusted server -> device
Intensive answered 10/5, 2018 at 18:34 Comment(5)
Thanks for writing in; I've updated my question to clarify. I'm specifically targeting a "replay attack," where one device buys the app, and then a second unauthorized device tries to send me the exact same receipt as the first device. The first receipt is valid, and therefore the second retransmitted receipt appears valid to Apple. I need a way to detect that the second receipt is a duplicate receipt.Cordy
Thanks for your update. You can see this link..might be helpful for ur question. LINK- github.com/crashoverride777/swifty-receipt-validator/blob/… . You need to detect the unauthorized device first then try to validate.Intensive
Link is no longer Active @Md Rashed PervezUnqualified
developer.apple.com/documentation/storekit/in-app_purchase/… - @MaksimKniazevIntensive
@DanFabulich "a second unauthorized device tries to send me the exact same receipt as the first device" You can prevent this by doing additional on-device receipt validation which checks whether or not the receipt is generated for current device.Tavis
D
0

And what is wrong to check uniqueness of receipt by its content? For example, attach MD5 hash of payment receipt to appropriate user. This field must be unique in your DB. In this way you easily detect duplicates.

Davedaveda answered 9/9, 2019 at 11:49 Comment(1)
That's perfect ideaUnqualified

© 2022 - 2024 — McMap. All rights reserved.