Reverse engineering the Costa App bean QR codes

3
Reverse engineering the Costa App bean QR codes

Reverse engineering the Costa App bean QR codes was always going to be a challenge but here goes!

What we know

  • That this exercise is highly unlikely to lead to free Coffee as the QR codes are likely generated at the point of sale and stored in a database centrally somewhere as the machines are Internet-enabled
  • In any event it does lead to free Coffee this would be reported to Costa as a bug immediately and any further investigation would cease.
  • That the price of Medium or Large drinks varies from machine to machine
  • That the QR codes continue to work days and even weeks after the initial purchase even if they have been collected, meaning the app must be checking either that the data held on the QR codes matched given criteria, a checksum for example, or more likely that the QR codes are being matched against an entry in a database which doesn’t clear out used QR codes.
  • That when ordering the same drink from the same location there are similarities between the QR codes as shown below.

The Green shaded areas are the same with the differences shown in light brown.

2022 01 11 08 24 06 WinMerge Untitled left Untitled right
QR code data for the same drink ordered from the same machine within a few minutes of each other
2022 01 11 08 24 11 WinMerge Untitled left Untitled right
QR code data for the same drink ordered from the same machine within a few minutes of each other
  • That the data held on the QR codes look to possibly be some form of base64 encoded string separated by full stops with = as padding.
  • That when the data from the QR Code is split up by full stop and is passed through a base 64 decoder you get back JS Data as below, JS Data then formatted with https://www.prettifyjs.net/
6VHKntKNERSLLW8qMw+eTUqZyvG6Ii7B8EbnIK0aL8Y=.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtYWNoaW5lTm8iOiIxMzI2NTEwOSIsInNpdGVJZCI6IjYyNTU4MDAxIiwia2V5SWQiOiIxMDkzMDEwMTgxMjM4MjgiLCJkcmlua0lkIjoiTEFUVC1MQzEtSC0xQ0FSLUMtU0MwMSIsImFtb3VudCI6MjgwLCJjdXJyZW5jeSI6IkdCUCIsInRpbWVzdGFtcCI6IjIwMjItMDEtMDZUMTY6MDE6MjgiLCJndWlkIjoiNzk1ZTQwNDMtZTkzNy0zMWU3LTJhNWMtZGM5MjZjMTBlYjhmIn0=.DlUEnnCuK+4qdy7MIFWG7i5u4dErBeUEIh55wsVOgNk=

{
“alg”: “HS256”,
“typ”: “JWT”
}

{
“machineNo”: “13265109”,
“siteId”: “62558001”,
“keyId”: “109301018123828”,
“drinkId”: “LATT-LC1-H-1CAR-C-SC01”,
“amount”: 280,
“currency”: “GBP”,
“timestamp”: “2022-01-06T16:01:28”,
“guid”: “795e4043-e937-31e7-2a5c-dc926c10eb8f”
}

The JS Code looks to contain all the information from the point of sale, i.e. the machine unique ID, the unique ID of the site, the drink chosen, the price paid, currency paid in, a date/time and a GUID that I would suspect would be unique to each drink purchase.

The drink in question looks to be a large ( “amount”: 280 (Large drinks sell at either £2.75 or £2.80 and Medium drinks sell at either £2.40 or £2.50 from the machine I have used)) Caramel Latte ( “drinkId”: “LATT-LC1-H-1CAR-C-SC01″ )

It crossed my mind to wonder how many drinks could be sold using the GUID and for them to not have duplicated. It turns out its a LOT. Providing Costa are doing some form of checking when they create the GUID to ensure a duplicate value is not generated on the off chance the total number of unique GUIDs calculated as there are 122 random bits (128 – 2 for variant – 4 for version) so this is 2^122 or 5,316,911,983,139,663,491,615,228,241,121,400,000 possible combinations or five undecillion three hundred sixteen decillion nine hundred eleven nonillion nine hundred eighty-three octillion one hundred thirty-nine septillion six hundred sixty-three sextillion four hundred ninety-one quintillion six hundred fifteen quadrillion two hundred twenty-eight trillion two hundred forty-one billion one hundred twenty-one million four hundred thousand cups of Java!

  • That when looking at the first few lines of the JS Code we can see that part (or more likely all) of the data held on the QR Code looks to be a JSON Web Token (with bits being base64 encoded). We can bob this code into a JSON Web Token decoded and get the same info as we get from decoding the separate base64 parts, this can be seen here.
  • Unfortunately as we do not have the 256bit HMACSHA256 secret Costa used to sign the QR Code data we aren’t able to re-sign new payload data so that the Costa App recognises it as being a legitimate claim. Well done Costa on a truly ingenious way of passing data in plan sight but fully secure 10/10!
2022 01 18 11 22 10 JSON Web Tokens jwt.io and 4 more pages Work Microsoft​ Edge
  • This still leaves the first part of the QR Code data shown in green, it cannot be decoded from base64 so unsure as to what this part is. Investigations will continue….
6VHKntKNERSLLW8qMw+eTUqZyvG6Ii7B8EbnIK0aL8Y=.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtYWNoaW5lTm8iOiIxMzI2NTEwOSIsInNpdGVJZCI6IjYyNTU4MDAxIiwia2V5SWQiOiIxMDkzMDEwMTgxMjM4MjgiLCJkcmlua0lkIjoiTEFUVC1MQzEtSC0xQ0FSLUMtU0MwMSIsImFtb3VudCI6MjgwLCJjdXJyZW5jeSI6IkdCUCIsInRpbWVzdGFtcCI6IjIwMjItMDEtMDZUMTY6MDE6MjgiLCJndWlkIjoiNzk1ZTQwNDMtZTkzNy0zMWU3LTJhNWMtZGM5MjZjMTBlYjhmIn0=.DlUEnnCuK+4qdy7MIFWG7i5u4dErBeUEIh55wsVOgNk=

Known URLs / Ports

https://costa-platform.com / 23.55.6.191 (Akamai Technologies, Inc.)

costalimited.d3.sc.omtrdc.net

Collected QR Codes

19/01/2022 - Costa Jct 41 M1 - Regular Mocha 
iWgoFdiNztSrkda1LdV8OrkpUjzF0YZgYLpuNLUkPPI=.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtYWNoaW5lTm8iOiIxNTQ4MTA4MiIsInNpdGVJZCI6IjYwNDk3MDA2Iiwia2V5SWQiOiIxMTMyNjAzMTkxNDM3NTYiLCJkcmlua0lkIjoiTU9DQy1NQzEtSC0wMDAwLUMtQ0IwMSIsImFtb3VudCI6MjUwLCJjdXJyZW5jeSI6IkdCUCIsInRpbWVzdGFtcCI6IjIwMjItMDEtMThUMTY6MDE6MjEiLCJndWlkIjoiZGNiMTQzOGMtZTNiYS02NzNhLTVhYmEtYTVhNjZhMGZlMGU2In0=.L0NOQpu27V/KGHagEp+g5oD+ZqF11Pv0vItTHAktdKI=

{
“alg”: “HS256”,
“typ”: “JWT”
}

{
“machineNo”: “15481082”,
“siteId”: “60497006”,
“keyId”: “113260319143756”,
“drinkId”: “MOCC-MC1-H-0000-C-CB01”,
“amount”: 250,
“currency”: “GBP”,
“timestamp”: “2022-01-18T16:01:21”,
“guid”: “dcb1438c-e3ba-673a-5aba-a5a66a0fe0e6”
}

06/01/2022 - Costa at end of parkway - medium caramel latte
6VHKntKNERSLLW8qMw+eTUqZyvG6Ii7B8EbnIK0aL8Y=.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtYWNoaW5lTm8iOiIxMzI2NTEwOSIsInNpdGVJZCI6IjYyNTU4MDAxIiwia2V5SWQiOiIxMDkzMDEwMTgxMjM4MjgiLCJkcmlua0lkIjoiTEFUVC1MQzEtSC0xQ0FSLUMtU0MwMSIsImFtb3VudCI6MjgwLCJjdXJyZW5jeSI6IkdCUCIsInRpbWVzdGFtcCI6IjIwMjItMDEtMDZUMTY6MDE6MjgiLCJndWlkIjoiNzk1ZTQwNDMtZTkzNy0zMWU3LTJhNWMtZGM5MjZjMTBlYjhmIn0=.DlUEnnCuK+4qdy7MIFWG7i5u4dErBeUEIh55wsVOgNk=

{
“alg”: “HS256”,
“typ”: “JWT”
}

{
“machineNo”: “13265109”,
“siteId”: “62558001”,
“keyId”: “109301018123828”,
“drinkId”: “LATT-LC1-H-1CAR-C-SC01”,
“amount”: 280,
“currency”: “GBP”,
“timestamp”: “2022-01-06T16:01:28”,
“guid”: “795e4043-e937-31e7-2a5c-dc926c10eb8f”
}

2022 01 13 15 33 03 QR Code Generator Create Your Free QR Codes and 2 more pages Work Microsof
04/01/2022 - Rontec garage near Kings School - caramel latte
RuxGSlzz1dNziTyuCZ0iKF4pMR8WxzJld1r+U/ZxjkA=.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtYWNoaW5lTm8iOiI4MjAwNTMyNCIsInNpdGVJZCI6IjYzMDg2MDQ5Iiwia2V5SWQiOiIxMTQxNDEyMjAxMjUyMzIiLCJkcmlua0lkIjoiTEFUVC1NQzEtSC0xQ0FSLUMtVFAwMSIsImFtb3VudCI6Mjc1LCJjdXJyZW5jeSI6IkdCUCIsInRpbWVzdGFtcCI6IjIwMjItMDEtMDRUMDc6MjA6MjYiLCJndWlkIjoiMDM5OGZkZmQtZTYyYS04ZDg4LWI2M2QtNTA1OTY5NGNlODRhIn0=.0uRep1JpShAubc2zelgAcYx0PEwbwlIBYl4y/1NSs8c=

{
“alg”: “HS256”,
“typ”: “JWT”
}

{
“machineNo”: “82005324”,
“siteId”: “63086049”,
“keyId”: “114141220125232”,
“drinkId”: “LATT-MC1-H-1CAR-C-TP01”,
“amount”: 275,
“currency”: “GBP”,
“timestamp”: “2022-01-04T07:20:26”,
“guid”: “0398fdfd-e62a-8d88-b63d-5059694ce84a”
}

2022 01 13 15 33 41 QR Code Generator Create Your Free QR Codes and 2 more pages Work Microsof
11/1/2022 - Spar Manvers - 2 x caramel latte
sf12FtQsb0281Lb3Hkr8rh9ORBDdNroAQ/mI/oC3HdY=.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtYWNoaW5lTm8iOiIxNDA0NzU5OSIsInNpdGVJZCI6IjYwMDA4MDcxIiwia2V5SWQiOiIxMDcyMTA5MjAxMDQ1NTMiLCJkcmlua0lkIjoiTEFUVC1NQzEtSC0xQ0FSLUMtU0MwMSIsImFtb3VudCI6MjUwLCJjdXJyZW5jeSI6IkdCUCIsInRpbWVzdGFtcCI6IjIwMjItMDEtMTFUMDc6MTU6MzMiLCJndWlkIjoiYTA2MDIyYTktMmQ5NS1lMTc4LTUwZWMtNDk0ZjY1ZTVmMmM5In0=.Y4b55t5X4z3p/0Jt4HqbKFZ2JkKOKoNvdsrCI4kCieo=

{
“alg”: “HS256”,
“typ”: “JWT”
}

{
“machineNo”: “14047599”,
“siteId”: “60008071”,
“keyId”: “107210920104553”,
“drinkId”: “LATT-MC1-H-1CAR-C-SC01”,
“amount”: 250,
“currency”: “GBP”,
“timestamp”: “2022-01-11T07:15:33”,
“guid”: “a06022a9-2d95-e178-50ec-494f65e5f2c9”
}

2022 01 13 15 34 18 QR Code Generator Create Your Free QR Codes and 2 more pages Work Microsof
1ChphnZRKePjf14USDRICuEABbI8AN0O3iuHnki97yQ=.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtYWNoaW5lTm8iOiIxNDA0NzU5OSIsInNpdGVJZCI6IjYwMDA4MDcxIiwia2V5SWQiOiIxMDcyMTA5MjAxMDQ1NTMiLCJkcmlua0lkIjoiTEFUVC1NQzEtSC0xQ0FSLUMtU0MwMSIsImFtb3VudCI6MjUwLCJjdXJyZW5jeSI6IkdCUCIsInRpbWVzdGFtcCI6IjIwMjItMDEtMTFUMDc6MTc6MDEiLCJndWlkIjoiZmI0ZWIwZTQtOTQ3Ny03ZjNjLTQyM2UtOWZjOTE0ZGM2MGU5In0=.b3Fb+SkuCqTjcFQ5BXC+4YqEa1dWdloi4FifBiDNMiA=

{
“alg”: “HS256”,
“typ”: “JWT”
}

{
“machineNo”: “14047599”,
“siteId”: “60008071”,
“keyId”: “107210920104553”,
“drinkId”: “LATT-MC1-H-1CAR-C-SC01”,
“amount”: 250,
“currency”: “GBP”,
“timestamp”: “2022-01-11T07:17:01”,
“guid”: “fb4eb0e4-9477-7f3c-423e-9fc914dc60e9”
}

2022 01 13 15 35 23 QR Code Generator Create Your Free QR Codes and 2 more pages Work Microsof

 

Found priceless insights in this blog? Support the author’s creativity – buy them a coffee!

3 thoughts on “Reverse engineering the Costa App bean QR codes

  1. The green part is exactly 32bytes / 256 bits of binary data when base64 decoded. Im assuming that this is a generator key that is used to create the real key that is used to sign the JWT. Without writing a console app that cycles through all the obvious choices (xor with the guid+guid, or machine id, or site id, or a combination of those).

    Given the multiple different QR codes you have and the ones i have taken myself when also looking into this, i cant seem to see any sort of patern, even with QR codes from the same machine within seconds of each other.

    1. Hey,

      It seems you’ve already done an impressive amount of research and investigation into this. The 32 bytes / 256 bits of binary data that appears after base64 decoding is the generator key in question.

      Given the absence of an apparent pattern and the uniqueness of the QR codes, it’s impossible to deduce a definitive answer without further analysis. It is plausible that the key generation process employs a combination of multiple factors such as GUID, machine ID, site ID, or even other unknown parameters. Analysing more data samples or employing various cryptographic techniques would be necessary to obtain a more comprehensive understanding.

      If you’re looking to explore this further, consider the following steps:

      1. Collect more QR code samples from different machines and at various time intervals to increase the dataset for analysis.
      2. Investigate the key generation process in detail to understand the specific factors and techniques employed.
      3. Employ cryptographic algorithms or techniques to aid in analysing the generator key, for instance, leveraging statistical methods, frequency analysis, or more advanced cryptographic techniques to uncover potential patterns.

      While these steps might not guarantee a definitive answer, they should help gain more insight into the key generation process and the factors at play.

      1. Ive been looking through the decompiled code (its kotlin rather than basic java, which has made it a nightmare)… and i have found this:

        androidx.work.b var2 = (new androidx.work.b.a()).h(“card_number_key”, var1.c()).h(“qr_code_key”, var1.d()).h(“status_key”, var1.e().name()).g(“timestamp_key”, var1.f()).a();

        sort of feels like they are creating a hash of your own costa loyalty hard number, followed by the key from qr code, followed by some status, and some timestamp, and this is code found inside ExpressQRCodeWorker.

        That is all i could find in the way of anything that does any sort of validation against the QR code or any data within the QR code. I havnt tried anything with it yet myself, because its late at night here now, and I am far too tired from looking at java/kotlin bytecode all night 😁

Leave a Reply

Your email address will not be published. Required fields are marked *