Back

Write-up of a reMarkable security vulnerability

Your vote is:
5.00 of 102 votes

This is a write-up on an integer overflow vulnerability that enabled me to zero balance any order at reMarkable. I was looking to buy a reMarkable tablet as a gift to my boss (for putting up with me) and noticed they had a VDP. Unfortunately, they don’t offer bug bounty rewards, but curious about their ecommerce setup, I figured I’d take a look at the application checkout logic to see how it works.

The first step was to create a new order:

reMarkable create a new order

Observing the raw HTTP traffic I could see that adding this to cart naturally creates a PUT request to the https://api.store.remarkable.com/v2/carts/77b5a997-500d-4ea6-9f53-2958a9c99cb2/items endpoint (where 77b5a997-500d-4ea6-9f53-2958a9c99cb2 represents a uniquely generated 128-bit value UUID for the cart):

reMarkable PUT request

In the request body we can see the sku value represents the item code and the quantity value for the selected item is represented as an integer.

We can tell this is an integer by the fact it’s not encased in quotation marks. Nothing unusual about this, it’s just important to note the difference between an integer and a string, as depending on how the backend handles these values there might be differences in how the application behaves.

Where the quantity value is a string:

reMarkable quantity string

Where the quantity value is an integer:

reMarkable quantity integer

When the natural request is processed by the server, we observe a 200 response where we can see the arithmetic operations performed on the quantity are consistent with what we expect. They’re clearly being stored in the database as an integer.

reMarkable PUT response

Now let’s try modifying the request integer value to 999999999999999 (15 digits):

reMarkable quantity 15 digits request

We observe another 200 response:

reMarkable quantity 15 digits response top

And here is the bottom part of the same response:

reMarkable quantity 15 digits response bottom

And if we refresh the checkout page in the browser, we can see the quantity has been updated.

reMarkable quantity 15 digits response frontend

The backend has processed the given quantity integer value with the expected arithmetic operations, which calculates the total value amount by simply multiplying the price of an item by its represented quantity.

Why is this interesting?

Well firstly, it’s possible that adding this many items to a unique cart ID could temporarily affect stock analytics. For example, if there are only 1,000 units of a particular item in stock, an attacker adding an amount beyond this to their basket may render an “out-of-stock” error for other legitimate customers attempting to make purchases, but this really depends on how the application is configured to handle stock control.

The more interesting element is the fact that there doesn’t appear to be any server-side input validation limiting the quantity integer in requests to a maximum value.

Remember we submitted 15 digits before? What response might we get if we submit 16 digits?

reMarkable quantity 16 digits response bottom

And if we now refresh the checkout page in the browser?

reMarkable quantity 16 digits response frontend

So to recap:

  • 999999999999999 (15 digits) produces a total of £33,597,677,788,712,864

  • 9999999999999999 (16 digits) produces a total of £0

To understand why this happened, it’s important to understand how integer values are stored by a database. Integer values are stored in databases using a specific data type that determines the range of values the integer can hold. The storage size and range depends on the database system and the specific integer type chosen.

In this example, the backend is likely using a 64-bit signed integer to store the quantity values. When the quantity value is increased to 16 digits, it exceeds the maximum value that can be stored in a 64-bit integer, resulting in an integer overflow.

This vulnerability exists because the server is attempting to store 16 digits inside an integer variable value which is higher than the maximum value the variable can hold. When an arithmetic operation results in a value that exceeds the maximum value the integer type can store, the value wraps around to the minimum value and continues counting up from there. Similar to how a seven-digit odometer within a car works to display the mileage - it can only store seven-digits, so if the counter hits 9999999 miles then the next integer up will roll it back to 0.

7-digit odometer example

To mitigate this vulnerability, the application should ideally perform input validation checks to ensure that submitted quantities are within acceptable ranges and use appropriate data types for database objects that can handle the range of values the application expects to process.

This was reported in-gratis and in good faith to reMarkable within 48 hours of discovery, in accordance with their VDP. My report was well received and they have since provided recognition by linking to my blog from their website at the bottom of the Vulnerability Disclosure Policy page.

I have since purchased my boss a Type Folio bundle (which was wrapped spectacularly I might add).

Boss Unwrapping reMarkable Gift

He takes a lot of notes in the course of his work, so expect it will serve him well, and perhaps persuade him to read more of my write-ups!

ABOUT THE AUTHOR

Jacob Riggs

Jacob Riggs is a Security Lead based in the UK with almost a decade of experience working to improve the cyber security of media and third sector organisations. His contributions focus on expanding encryption tools, promoting crypto-anarchist philosophy, and pioneering projects centred on leveraging cryptography to protect the privacy and political freedoms of others.

E3FE 4B44 56F5 69BE 76C1 E169 E3C7 0A52 9AEF DB6F


Subscribe to my Blog


I agree with the Privacy Policy terms.
Loading...
.