raphting.dev

Secure updating is hard

Some systems have higher security requirements for auto-updates than others. Think about cars, airplanes and wherever physical harm can result. For updates, secure or not, there is a common pattern:

  1. Check a remote endpoint for available updates
  2. Retrieve the update
  3. Apply the update

Number 3 is environment and application specific, so it won’t be covered in this text. If you think, infrastructure will never be compromised, then this text is not for you. The approach here is: “Trust the people, don’t trust the infrastructure”. The following paragraphs are written under the assumption that update hosts could be compromised.

Attacks on updates

To have a realistic case, imagine you wrote an App and want to apply the above three-step pattern for updates. The App knows an endpoint for updates. This could be an HTTPS endpoint. There, the client finds the link for the update, called target and downloads the target.

Arbitrary data

If an attacker controls the update host, she has the option to change the target link for the update to arbitrary sources. The client would blindly download these compromised sources and apply them 💥 A provided hash, cryptographically identifying the target, does not help, as the attacker could change the hash to her liking too.

Let’s sign the data on the server. The client is (pre-)loaded with a public key and able to check the signature. So now we are safe?

Roll back

The attacker watched us already for a while and has a history of all signed update files. She knows that one of the older versions was vulnerable. She puts the old signed file on the update server. Our client trusts the signature and downloads and applies the vulnerable version 💥

Alright, this one was easy. We add a timestamp to the updates. If the local version is older than the remote version, the client can safely download and apply data. We are done, right 😅?

Indefinite freeze

We achieved so far the update’s authenticity and freshness. The attacker could block us from new updates without our clients ever noticing though. The attacker gives us the same update file over and over again. This could give her an advantage to find security bugs, wait for a responsive disclosure, letting cars drive with old traffic rules, letting planes fly with old airspace data etc. 💥

We add an expiration field to our update file. This field needs to be signed frequently with an online-key (a cryptographic key that is stored on a server in contrast to other keys that should reside on an offline device). Even if the attacker prevents us from downloading updates, at least the clients can detect the expiration and give a warning.

Wow, that was more work than expected to secure the update process, don’t you think? You guessed it. There is more.

Endless data

The attacker gets a little frustrated about our good security measures, so she thinks about doing harm otherwise. She controls the update target as well. Instead of letting the client download a normal file, she puts a few TB of randomness there. Enough to fill any of our clients drive. In the worst case, overfull storage will brick our clients forever 💥

We could have thought about it before, but with this deterrent example it is clear. On the signed update file we add a field for file size and tell our client to stop downloading when this amount of data was received.

Hey, what a fun ride. The update process is secure now, right?

Wrong software

We forgot that we run similar clients but with a whole different software on it. It runs a few years longer already, so the update version numbers are higher than for our current App. I think you think what the attacker thinks: Hell’s bells, the attacker could present the other software’s update data. It was signed with the same key. Our clients will just download another software 💥 Who knows how they react to it?

This was a tricky one, but we got it. Together with the other information, we store an App identifier, for example the product name and platform. We ask the client to always compare the given App identifier to a hard coded App identifier. Only if they match, the update is applied.

Compromised online key

Did I write earlier we should work with an online key to sign the expiration date? We don’t know if the attacker could access the secret online key. So expiration dates will be vulnerable forever 💥? No good, no good. What we need are multiple keys for multiple roles. The secret root key should never get lost, but it’s okay to store it offline somewhere.

In a separate file on the update server, we store the needed public keys. These entries are signed by the secret root key. The client is able to retrieve public keys through this file, for example the expiration date signing key. If that one is compromised or should be refreshed, just update and sign the root file. From that moment on, the client can trust the new keys again.

Summing it up

A lot can go wrong when a client tries to update itself, especially if you cannot exclude a compromise of the update infrastructure. Most scary attacks can be prevented by the use of cryptography and logic. The Update Framework is a great resource for exploring the space of secure updates more.

By Raphael Sprenger licensed under CC BY-NC 4.0