"Collaborateur anonyme", "en"=>"Anonymous contributor"}" />
Vulnerabilities

Analysis of a vulnerability in the VaxiCode Verif application

The VaxiCode Verif application recently launched by the Government of Québec was the subject of a vulnerability disclosure allowing fake codes to be validated. Here is our analysis.

Also available in French: Analyse d'une vulnérabilité de l'application VaxiCode Verif

A fake VaxiCode

Understanding SMART Health Cards

The SMART Health Card (SHC), which contains the information stored in your QR code, is based on a proven technology known as JSON Web Signature (explainer, JWS specification).

JWS allow, among other things, the cryptographic signing of arbitrary data (the payload), as well as its verification using information (the headers) contained within that very data, namely the public key and the signing method.

This same technology is used to build other technologies such as JSON Web Token, the Open ID suite of specifications, and others, which are very frequently used to store data outside of a controlled environment while ensuring its verifiable validity. For example, if you frequently use Google services, this same technology is used to connect you to them securely.

The concept of a signature involves a private key and a public key. The entity (the Government of Québec) that publishes the data (the QR code representing the SHC) uses the private key to sign it, and the applications that consume it use the public key (the VaxiCode Verif app) to validate that this data has not been modified by a malicious person. Without going into detail, the signed data is then considered safe and verifiable. For more information, we invite you to consult this article on signatures.

Now that we know the SHC data is tamper-proof, what does your QR code contain? It contains a few pieces of information, called claims.

A first claim, iss or issuer, identifies the entity that publishes this data. Another, iat or issued at, indicates the date on which it was published. Finally, a claim named vc or verifiable credentials contains the crucial information of the SHC.

Present in the latter are the type of data that the SHC contains (immunization data, COVID-19 context and others), and a list of entries.

One in particular describes the patient (you) who is the subject of the SHC. In it are found only your first name, last name, and your date of birth.

Then, for each dose of vaccine received, you will also find the information describing which vaccine was received, on what date, and the name of the place where the injection was performed.

The vulnerability

Let us now return to the main issue, namely the vulnerability contained in versions 1.0.1 and earlier of the VaxiCode Verif app.

It would allow the application to validate codes that were not issued by the Government of Québec. By this, we mean that a person would be able to issue and sign an SHC themselves, thereby succeeding in defeating the application’s verification that the data included in it is indeed truthful (signature) and endorsed by the only authority currently having the capacity to issue SHCs (claim iss).

This vulnerability was first made public on Friday, August 27, 2021, in an article by Radio-Canada.

Possible avenues of vulnerability

Before validating the vulnerability of the verification application, let us go through several common problems in the implementation of JWS validations and similar technologies that could allow such a result.

Since the vulnerability was found less than 24 hours after the application’s release, it is unlikely that the flaws are the result of very complicated operations. Let us start with these few basic checks:

  1. Are the public keys indeed used to validate the content of the JWS?
  2. Is the validation of the iss claim done correctly?
  3. Are the external JWK downloaded and used?
  4. Are the keys used properly?
  5. Could it be possible that insecure signing modes are allowed (specifically, alg: "none")?

Tools used

The following common tools were used for the majority of the tests:

To validate alg: "none", these were added to the list:

  • node-jwa to generate JWS with this type of algorithm.
  • pako to compress the JWS payload.

Checks

Use of the government’s public key and use of the iss claim

To begin, an existing QR was scanned to obtain the string of characters making up the SHC. The latter was opened in the Smart Health Card Scanner to quickly visualize its content.

Its content was copied into a script allowing a new SHC to be generated using SHC-SDK, but signed with a different JWK than the application’s. The payload was left identical.

At the same time, the iss claim is left identical to validate that this does not allow the signature validation to be ignored.

Result: VaxiCode Verif displays an error message: This QR code is not valid proof of vaccination. We assume that the signature was indeed validated and that the iss claim does not allow it to be bypassed.

This QR code is not valid proof of vaccination

External JWK

JWK is a format allowing the exchange of cryptographic keys that is used when validating the signatures of the SHC issued by the government. We speak of JWKS when it is a set of keys (JSON Web Key Set).

We attempt to import an external JWKS at the path .well-known/jwks.json. A very simple local HTTP server is set up behind a tunnel to make it publicly available with the HTTPS protocol. The iss claim is changed to the tunnel’s domain (for reference, let us call it Serveur.Local). A JWKS is also generated, then placed in the .well-known folder, then the SHC is generated and scanned. The JWKS is therefore accessible at the address https://Serveur.Local/.well-known/jwks.json.

Result: The proxy indicates having indeed received a call to GET /.well-known/jwks.json. At the time of the scan, VaxiCode Verif indicates an error: Certified by an unknown authority. Serveur.Local. We assume that the JWK were indeed imported. This is entirely normal up to this point.

Certified by an unknown authority. Serveur.Local

Usage of public keys

We now attempt to see whether the JWK that was imported is usable only by its issuer. We set the iss claim back to its initial value (https://covid19.quebec.ca/PreuveVaccinaleApi/issuer) and generate a new SHC, then scan it.

Result: The SHC is accepted as truthful. We have reproduced the vulnerability. We deduce that the JWK that was imported in the last step was not correctly marked as being usable only by the issuer Serveur.Local to validate signatures, and that the issuer https://covid19.quebec.ca/PreuveVaccinaleApi/issuer is now able to validate signatures with the keys of Serveur.Local, which should never happen.

SMART Health Card authorized

Insecure signatures

A compliant JWS similar to the SHC format was also created and scanned, but with the following header:

{
  "alg": "none",
  "zip": "DEF"
}

alg: "none" indicates to the signature verifier that there is no signature to verify.

Result: The VaxiCode Verif app is simply unable to recognize a QR code of this type. No vulnerability here.

Analysis

The problem causing the vulnerability is mentioned in the SHC specification. It is the responsibility of the group developing an application making use of the SHC standard to pay attention to it. The problem is described directly:

  1. in the SHC specification, sections Signing Health Cards and Determining keys associated with an issuer as well as
  2. in the JWS specification, section Key Origin Authentication, from which the former inherits.

“10.3. Key Origin Authentication: The key management technique employed to obtain public keys must authenticate the origin of the key; otherwise, it is unknown what party signed the message.”

Excerpt from the JWS specification

In clearer terms, one must ensure that the signature is verified only with keys originating from the issuer, but the implementation in the VaxiCode Verif application did not do so for versions <=1.0.1.

Are the QR codes compromised?

No. As mentioned, the QR codes that contain the SHC information are compliant with the specification and are entirely secure.

Here, the first part of the problem lies in the fact that the keys used to verify the codes are not only associated with their respective issuers. This is a regrettable omission, however (and fortunately), it is easy to fix.

In addition, the application should only validate trusted issuers, which seems to be the case, but remains to be verified following the correction.

Validation of the fix

On Sunday, August 29, 2021, a new version of VaxiCode Verif, 1.0.2, was published on the Apple App Store. The changes are described as follows:

  • Improvement of the proof verifier
  • Improvements for smaller screens
  • Accessibility improvements

We used the same procedure as the one described above to attempt to validate the fix.

Result: The vulnerability has indeed been fixed, and we are no longer able to exploit it.

Without access to the application’s code, it is difficult to describe exactly the fixes made. However, we are still able to make a few observations. The most important concerns the fact that the application no longer downloads the external JWKS at the issuer’s /.well-known/jwks.json path; it is therefore no longer able to perform either the import or the validation of signatures generated with keys other than the government’s. In addition, the error message displayed in this case is no longer Certified by an unknown authority. Serveur.Local, but rather This QR code is not valid proof of vaccination.

This QR code is not valid proof of vaccination