15

I am designing a e-commerce based website, part of which will be a order summary page. This order summary page, will contain the address and name of the person who ordered a product.

My plan was to encrypt the order id, and pass it to the checkout success page like like so: https://www.example.com/success?id=AES-128ENCRYPTED_ID

This would mean that the customer can 'refresh' the success page and still get the order confirmation.

The length of the encrypted ID would mean it should be too difficult for anyone to guess, however the URL would be stored in logs etc, and although no PII is in the url itself, anyone would be able to click that url and see the PII if they were able to see someone elses url recorded in some logs.

What is the best practice around this? Is this a fundamentally bad design, if so, what is the alternative? Keep the data in a session maybe?

13
  • 4
    It is unclear what you mean by "the customer can 'refresh' the success page and still get the order confirmation". What exactly do you expect to gain? Why not use /success?order={orderId} and regular session authentication instead of a public page "secured" by a query parameter token?
    – Bergi
    Commented Jul 8 at 22:31
  • 2
    @Bergi I assume OP is referring to instances of POST data redirecting to order page where a page refresh would pop up the "Are you sure you want to resubmit the form" alert. Guess, he is trying to avoid that by making it a GET page.
    – asprin
    Commented Jul 9 at 3:21
  • 4
    @U.Windl: Which external link? The mysite.com URL is just an example, not a real URL.
    – Ja1024
    Commented Jul 9 at 13:32
  • 4
    @Bergi "Why not use [...] regular session authentication..." - That assumes you have an authenticated session, which requires some way to authenticate / log in. Many web shops allow you to order without having an account, so I think it's a legitimate question.
    – marcelm
    Commented Jul 9 at 14:15
  • 2
    @U.Windl one might imagine that "AES-128ENCRYPTED_ID" is encrypted via some variant of AES-128. But yes, it's unclear how this encryption would add any security over just making the ids unguessable in the first place, unless order IDs are public info too.
    – Cubic
    Commented Jul 10 at 13:57

5 Answers 5

11

My plan was to encrypt the order id, and pass it to the checkout success page like like so: https://www.mysite.com/success?id=AES-128ENCRYPTED_ID

It seems like you want integrity, and not confidentiality. Encryption does not necessarily enforce integrity. That something is encrypted does not necessarily mean that it cannot be manipulated.

The correct encryption primitive is to sign the order identifier. Calculate a hash HMAC(secret, orderId) and create an URL such as:

https://www.mysite.com/success?id=plaintextOrderId&hmac=calculatedHmacSignature

Alternatively, you can use an AEAD (authenticated encryption with additional data) scheme.

The length of the encrypted ID would mean it should be too difficult for anyone to guess, however the URL would be stored in logs etc, and although no PII is in the url itself, anyone would be able to click that url and see the PII if they were able to see someone elses url recorded in some logs.

It seems like you have a correct view on the risks involved. It is generally not a good idea to store sensitive information in URLs (or behind URLs), because these leak in browser caches and logs etc.

What is the best practice around this? Is this a fundamentally bad design, if so, what is the alternative? Keep the data in a session maybe?

If you just want to show the confirmation after ordering, you can save the order identifier is the session.

If you want to send a link to the order confirmation in an email, there's little that you can do. One thing is to reduce the time the order confirmation page is valid. Perhaps you can only serve order confirmation pages for orders placed in the last week. This way, it would be impossible to enumerate all orders.

23

The problem of using encrypted order IDs (or the HMAC of the order ID) is that the entire scheme depends on the secrecy of the key. If you have a secure key store (ideally a hardware security module), then this risk may be acceptable, otherwise you should avoid it.

Additionally, rotating the key or revoking access to individual orders is a problem with your scheme. Keys shouldn't be used forever but updated after a while. If you do this, then you'll either have to invalidate all URLs that still depend on the old key (not great for your customers), or you have to use multiple keys in parallel (which makes the application more complex). The same problem occurs when you find out that a particular order ID has been leaked – if you change the key, this will affect other orders as well (potentially all of them).

A much better approach would be to generate a secure random token per order, include it in the URL and store the hash of the token in the database. The URL will look like this:

https://www.mysite.com/success?order_id=465&token=d5dfa49c24d79bd7ad714999d9c0e08b

The token shouldn't be stored as plaintext. Instead, hash it with something like SHA-256 and store the result in the database. When you receive a token, you also hash and then try to look up the hash in the database.

This has several major benefits compared to encryption or an order HMAC:

  • There's no “master secret” which the server has to protect. If the token hashes in the database get leaked, this isn't a problem, because they're immune to brute-force attacks as long as you use a secure random number generator for the tokens.
  • You can easily revoke access to individual orders by simply changing the token. This doesn't affect any other order.
  • There's no need to implement key rotation.
9
  • 3
    Using random tokens instead of encrypting identifiers is also the recommendation of Paragon.
    – Sjoerd
    Commented Jul 8 at 15:27
  • 2
    Using UUIDs as random identifiers solves a lot of problems basically for free. Commented Jul 9 at 7:52
  • 1
    @marcelm: The point of hashing is that the tokens are safe even if the server is compromised. Without hashing, the attacker would immediately get all plaintext tokens.
    – Ja1024
    Commented Jul 9 at 15:04
  • 1
    @Ja1024 If the server is compromised, the attacker can see all the PII anyway. So I still don't see the point.
    – marcelm
    Commented Jul 9 at 17:22
  • 1
    @marcelm: If an attack against the server immediately reveals the PII, there’s something wrong with the server configuration. The PII itself should of course be protected as well, e.g., by restricting access to a separate database account which isn’t affected by attacks against other components of the application.
    – Ja1024
    Commented Jul 9 at 18:37
14

One additional step I would personally take as an extra precaution: partially censor the name and address on the document shown (so karate code becomes k*** c***, and 11 Wall Street becomes 11 W******), and then allow the user to enter an email address or phone number that belongs to the person making the purchase to show these details. I don't think it's a good idea to have data like that publicly accessible without at least a partial confirmation. As you said, these URLs end up in logs, in the browser history and other locations.

2
  • 1
    This seems like the right answer to me. I assume the use-case you have in mind is the user wanting to return to the order confirmation page at some later date without the friction of logging in, perhaps to view tracking information or similar? In that case, show the relevant info, which probably isn't PII, and obscure (or better, exclude) the PII. If they want to check the address they entered, then require them to identify themselves. Commented Jul 9 at 11:50
  • 7
    +1 For this. There are billions of parcels with public URLs containing nothing more than the parcel id. An interested user enters the recipient's postal code to view the parcel's details. Tried and tested. Commented Jul 9 at 19:39
5

This is an authentication/authorization problem. The question is, "Does having a URL with a long, unguessable part provide sufficient identification of a user?" You have posed several situations where it may not, although, of note, this is a sometimes used pattern in e-commerce.

Generally, this problem is solved using sessions, where a user's browser is given a session token of some sort it can send with requests to the confirmation page. The server then verifies that the token is not expired and that it goes with that order ID. When the token expires, if you want the user to be able to go back after a while, you might offer another way for the user to prove their identity, such as clicking a link in a fresh email or text. A less infrastructure-heavy verification might be to have the user verify a detail like a phone number or shipping address.

There is no way to replace authentication with cryptography on the order ID because, eventually, it is just a URL that maybe anyone on Earth has.

3
  • A phone number is also just a number that maybe anyone on Earth has, how would that be better? Commented Jul 10 at 13:13
  • 1
    @JacobisonCodidact It is not super good, but it addresses the threat model of someone snooping on the server logs or anonymously obtaining the URL. It is less likely that a person would have the link and the phone number. Texting a code is much better. Commented Jul 10 at 21:31
  • The number would still need to be sent somewhere and still presumably logged. If you can mask the number in the "number verification" payload, you can mask the ID in the query parameter - although separating ID from verification might help in some (not security related) cases. Texting a code is indeed a big improvement - emailing even better. Commented Jul 11 at 12:42
-2

Yes, it is a problem. The size of the problem depends on what you sell, but even if you're not selling anything exciting having semi-verified names and addresses linked is just asking for people to scrape all of that data. The people scraping this won't care about how many not-valid ids they have to go through to get a valid id because they will be using botnets and this will be all kinds of bad.

As other answers have said, publicly available information needs to be exceptionally useless without other knowledge (order 123 is pending fulfillment...but no names or addresses, personally I wouldn't even put obfuscated info) and require authentication to see more details. If you don't want to force user accounts then you can do authentication as-needed using one-time ids (verify your identity by entering the number we have just sent to you).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .