📜 ⬆️ ⬇️

How to RELIABLY protect in-app purchase from scrapers

Most recently, I wrote an article How to protect in-App Purchase from scrapers . It took a little time, but hackers do not sit in place. That method of protection turns out to be circumvented, not very difficult. Under the cut method, which is much more reliable.

Before reading this topic, I recommend reading the previous one. How to protect in-App Purchase from scrapers , since this is a continuation of the topic.

In my application, after verification, the receipt is sent to my server, and I analyze it there, and save it in the logs. And I noticed that the JSON that comes is standard, and some modified.
Standard looks like this:
{ "receipt": { "original_purchase_date_pst": "2012-06-08 04:13:04 America/Los_Angeles", "purchase_date_ms": "1339153984956", "original_transaction_id": "430000009214053", "original_purchase_date_ms": "1339153984956", "app_item_id": "12312312323", "transaction_id": "430000009214053", "quantity": "1", "bvrs": "1.0", "version_external_identifier": "7809437", "bid": "xx.yyyyyy.zzzzzzz", "product_id": "xx.yyyyyy.zzzzzz.uuuuuu", "purchase_date": "2012-06-08 11:13:04 Etc/GMT", "purchase_date_pst": "2012-06-08 04:13:04 America/Los_Angeles", "original_purchase_date": "2012-06-08 11:13:04 Etc/GMT", "item_id": "123123123" }, "status": 0 } 


As a result, I noticed 3 types of hacking attempts:
')
1. The most frequent option. Brokerage does not fake the response of the Apple server, as a result of receipt looks like this:
 { "status": 21002, "exception": "java.lang.ClassCastException" } 

Our previous method coped with such hacking, because the status = 21002

2. More recently, new ways of hacking. I don’t know with what utilities this is done, but they fake the response of the Apple servers to the second request.
JSON looks like this then:
 { "status":0 } 

Such hacking is simple. You can check the presence of some variables, for example “product_id”, and everything will fall into place.

3. But not everything is so simple. Recently, another version of forged JSON has appeared:
 { "status": 0, "receipt": { "product_id": "xx.yyyyyy.zzzzzzzz.uuuuuuu", "purchase_date": 1339152660.383128, "quantity": 1, "transaction_id": "xx.yyyyyy.zzzzzzzz.uuuuuuu" } } 

If you check something again, then you need to find something that the messaging program cannot get from the request. After reviewing all the data, I realized that there is a fairly simple way, which will be very difficult to get around.
You need to check for “item_id” - this is the id that Apple assigns to each product, including the in-app purchase. You can view it in iTunesConnect by clicking on the “Manage In-App Purchases” button.

Then our code will look like this:

 kFeature1 = "xx.yyyyyy.zzzzzzzz.uuuuuuu"; kFeatureItemID1 = "123123123"; kFeature2 = "xx.yyyyyy.zzzzzzzz.uuuuuuu"; kFeatureItemID2 = "123123123"; kFeature3 = "xx.yyyyyy.zzzzzzzz.uuuuuuu"; kFeatureItemID3 = "123123123"; - (BOOL)verifyReceipt:(NSData*)receiptData { NSString *urlsting = @"https://buy.itunes.apple.com/verifyReceipt"; NSURL *url = [NSURL URLWithString:urlsting]; NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url]; NSString *st = [receiptData base64EncodedString]; NSString *json = [NSString stringWithFormat:@"{\"receipt-data\":\"%@\"}", st]; [theRequest setHTTPBody:[json dataUsingEncoding:NSUTF8StringEncoding]]; [theRequest setHTTPMethod:@"POST"]; [theRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; NSString *length = [NSString stringWithFormat:@"%d", [json length]]; [theRequest setValue:length forHTTPHeaderField:@"Content-Length"]; NSHTTPURLResponse* urlResponse = nil; NSError *error = [[NSError alloc] init]; NSData *responseData = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:&urlResponse error:&error]; NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; NSDictionary *dic = [responseString JSONValue]; NSInteger status = [[dic objectForKey:@"status"] intValue]; NSDictionary *receiptDic = [dic objectForKey:@"receipt"]; BOOL retVal = NO; if (status == 0 && receiptDic) { NSString *itemId = [receiptDic objectForKey:@"item_id"]; NSString *productId = [receiptDic objectForKey:@"product_id"]; if (productId && ([productId isEqualToString:kFeature1] || [productId isEqualToString:kFeature2] || [productId isEqualToString:kFeature3] )) { if (itemId && ( [itemId isEqualToString:kFeatureItemID1] || [itemId isEqualToString:kFeatureItemID2] || [itemId isEqualToString:kFeatureItemID3] )) { retVal = YES; } } } return retVal; } 

What is the plus of this code: the lomaker does not know the value of item_id, it is not transmitted anywhere at the request. That is why it will not be easy to fake such a receipt. Although, perhaps, it is possible.

And please, do not say that everything can be broken. The purpose of this protection is to avoid mass hacking by standard means.

Source: https://habr.com/ru/post/145524/


All Articles