apple / app-store-server-library-node

MIT License
157 stars 29 forks source link

No way to detect fraudulent receipts. #172

Closed fushihara closed 1 month ago

fushihara commented 1 month ago

After receiving a receipt from an app with a base64 string beginning with “MII...” as a receipt, there is no way to verify that it is a legitimate receipt.

If only the last character of a legitimate receipt is altered and POSTed to the following API, an error will occur and tampering can be detected. However, this API has been deprecated https://sandbox.itunes.apple.com/verifyReceipt

In the new library, the base64 of the receipt is only used to get the transactionID, which is only about 10 digits in the following code. const transactionId = new ReceiptUtility().extractTransactionIdFromAppReceipt(receiptString) In fact, falsifying the last character of base64 did not generate an error and returned the same transactionID. It is noted in the comments that this method does not validate.

How can I trust the base64 string received from the app, starting with “MII...”, to assign the billing information? If I can get the transactionID, can I trust that it is legitimate?

alexanderjordanbaker commented 1 month ago

@fushihara Please see this example from the README https://github.com/apple/app-store-server-library-node?tab=readme-ov-file#receipt-usage, where Get Transaction History is used to fetch the data for the user from the App Store Server API.

fushihara commented 1 month ago

@alexanderjordanbaker Thank you for your reply.

Even in the sample you showed, the long base64 string of the receipt is not used at all after extracting the transactionID through the extractTransactionIdFromAppReceipt method.

https://github.com/apple/app-store-server-library-node/blob/main/receipt_utility.ts#L17 I have checked the processing of the contents of the extractTransactionIdFromAppReceipt method, and it only shows the value of the ORIGINAL_TRANSACTION_IDENTIFIER_TYPE_ID key by running getVbyList parseInt etc. I am just displaying the value of the ORIGINAL_TRANSACTION_IDENTIFIER_TYPE_ID key.

Anyone can create a byte string that outputs an arbitrary transactioID when passed to the extractTransactionIdFromAppReceipt method, right? I just need to create it according to the ASN.1 protocol.

Since the verifyReceipt API verifies Apple's signature, it can detect fake receipts created this way and make an error.

Also, if you only need to retrieve the transactionID, there is no need to send a long base64 receipt from the application side to the server, and you can simply send a transactionID of about 10 characters to confirm the billing. That doesn't sound right, does it?

I'm wondering if there is no way to determine if the string passed to the extractTransactionIdFromAppReceipt method is really a base64 string created by the iOS StoreKit.

This is a question I have before using Get Transaction History.

fushihara commented 1 month ago

For example, even if an attacker obtains a valid TransactionID, he/she cannot forge a receipt because it is impossible to have it signed with an Apple certificate. The verifyReceipt API will not approve the receipt and will tell you of the error.

However, in the current sample, the extractTransactionIdFromAppReceipt method returns a valid transactionID even if the receipt is not signed by Apple, as long as it complies with the ASN.1 protocol. This would cause the server to approve an invalid receipt.

I agree that the TransactionID leakage is a problem, but the current verifyReceipt API is designed to prevent fraud even if the TransactionID is leaked. Also, in the Android billing system, the receipt is readable json, but it is sent to the server as a set with a certificate signed with Google's private key, and the server checks the certificate.

alexanderjordanbaker commented 1 month ago

@fushihara We recommend all developers use the StoreKit 2 framework, in which all transactions are signed from device to server and can be verified and decoded directly using the SignedDataVerifier verifying the certificate chain and signature Example https://developer.apple.com/documentation/storekit/verificationresult/3868429-jwsrepresentation

These legacy receipts and verifyReceipt are both deprecated

fushihara commented 1 month ago

@alexanderjordanbaker Thank you very much! I am using flutter and cannot use StoreKit2 yet. I will learn about jwsRepresentation and ask again!