ワクチン接種証明用QRコードのリバースエンジニアリング

画像


ケベック州が、添付の QR コードを使用して予防接種を受けたすべての人に予防接種の確認メールを送信すると発表したとき、私の膝は少し震えました。私はそれを分解して、その過程で間違いなく明らかになるであろう個人の健康情報の量に頭を抱えていました.



ワクチン接種の確認がようやく届きましたが、結果は・・・全然悪くありません。ただし、ゼロ知識ハックには常に楽しいことがあるので、とにかく自分の経験についてブログに書くことにしました。



第一印象は「うわぁ、これは必要以上に大きなQRコードだな」というものでした。QRコードの下に記載されている情報が少ないので、おそらく私の知らないうちにあらゆる種類の個人情報を暗号化しています。運転免許証の裏面にあるバーコードのように



当然、私が最初にしたことは、QRcode アプリを使用してコードをスキャンすることでした。



結果
shc:/567629000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000413774



面白い。バイナリ形式の古き良きJSONがあると思っていましたが、違いました。 base64 で大量の数字をエンコードするのは効率が悪いようですが、すべてを 1 つの QR コードに詰め込むことができました。



残念ながら、プロセスのゼロ知識部分はここで終わります。なぜなら、次に進むべき場所のかなり明確な指標、つまり URI スキームがあるからです。これは、このスキームを処理するために登録するコードを検証する人のデバイス上のアプリケーションと通信するためのものであることは明らかです shc :



。しかし、このスキームは何ですか?



少し検索すると、 IANA の Big Book O 'URI スキームにたどり着きましたshc



SMART Health Cards Framework という名前で事前登録済みとしてリストされています。つまり、これはケベック州政府が外出先で思いついたものではなく、実際のプロジェクトの一部なのです!これは勇気づけられ、予想外です。



このフォーマットには、 広範なドキュメントと非常に賢明な 設計目標があることがわかりました。これは、そのようなコードの所有者にとっては安心できると同時に、誰かがコード全体を解析しようとしているときには少しイライラするものでもあります。しかし、それは関係ありません!コードと従うべき文書があるので、ふたを外して中を見てみましょう。



ドキュメントによると、数値モードを使用して QR コード データをエンコードすると、バイナリ モードを使用するよりもデータ密度がわずかに高くなります。最初の謎が解けた。



数字の長い文字列は、ASCII 文字列からエンコードされているように見えます。数字の各ペアは、文字コードである 10 進数です。さらに混乱させるために、出力はOrd © -45を使用して計算され ます。このプロセスを逆にするスクリプトを作成する時が来ました。



php -r '$o = ""; foreach (str_split(preg_replace("/[^0-9]/", "", file_get_contents("php://stdin")), 2) as $c) $o .= chr($c + 45); echo $o;' <input.txt | xxd

00000000: 6579 4a72 6157 5169 4f69 4a73 4d33 6c79  eyJraWQiOiJsM3ly
00000010: 5254 4632 526a 646d 6157 5270 6257 5649  RTF2RjdmaWRpbWVI
...
000003b0: 3561 6876 5265 336d 6368 7335 7836 4e49  5ahvRe3mchs5x6NI
000003c0: 4669 3556 5277                           Fi5VRw
      
      





このことから、いくつかのことがわかります。まず、PHP が今でも私の高速プログラミング言語であることは明らかです。残念ながら、この個人的な啓示は、さらなる考察のために脇に置いておきます。



技術的な観点からは、すべてが base64 でエンコードされた文字列のように見えます。そしてもちろん、ドキュメントには、JWS、つまり JSON 署名された Web トークンを検討する必要があると書かれています。



ちょっと立ち止まって、これが実際に JWT の優れた使用例であると言いたいと思います。基本的に、JWT の概念は、意味のないトークンや機密データの巨大なブロックの代わりに、発行者 (この場合、ケベック サンテet Services sociaux)。



このモデルの良い点は、インターネットに接続していなくても、対応する公開鍵があれば誰でも検証できることです。また、「この人は飛行機に乗る権利、コンサートに行く権利、高齢者の家を訪問する権利はありますか?」という質問に対する答えは、独自の API やワクチンのロット番号などに関連する一連の秘密フィールドを介して暗黙的に暗示されるのではなく、インラインで直接答える必要があります。



現在、対応する公開鍵のコピーはありませんが、本文は暗号化ではなく署名する必要がありますだから今でも読めます。



おそらく、リバース エンジニアリングの精神で、JWS を手動で逆アセンブルする必要がありますが、これはかなり文書化された (そして重要なことに、適切に実装された) 仕様です。私は怠け者になり、そのためにweb-token / jwt-framework Composer パッケージを使用し ます。



$ composer require web-token/jwt-framework
      
      







<?php
require_once(__DIR__.'/vendor/autoload.php');

use Jose\Component\Signature\Serializer\JWSSerializerManager;
use Jose\Component\Signature\Serializer\CompactSerializer;

$serializerManager = new JWSSerializerManager([
    new CompactSerializer(),
]);

$input_raw = file_get_contents('php://stdin');
$input_token = implode(
    array_map(
        function ($ord) { return chr($ord + 45); },
        str_split(preg_replace('/[^0-9]+/', '', $input_raw), 2)
    )
);

$jws = $serializerManager->unserialize($input_token);
var_dump($jws);
      
      





$ cat input.txt | php parse.php
object(Jose\Component\Signature\JWS)#5 (4) {
  ["isPayloadDetached":"Jose\Component\Signature\JWS":private]=>
  bool(false)
  ["encodedPayload":"Jose\Component\Signature\JWS":private]=>
  string(772) "hVNhb9..."
  ["signatures":"Jose\Component\Signature\JWS":private]=>
  array(1) {
    [0]=>
    object(Jose\Component\Signature\Signature)#6 (4) {
      ["encodedProtectedHeader":"Jose\Component\Signature\Signature":private]=>
      string(106) "eyJraW..."
      ["protectedHeader":"Jose\Component\Signature\Signature":private]=>
      array(3) {
        ["kid"]=>
        string(43) "l3yrE1..."
        ["zip"]=>
        string(3) "DEF"
        ["alg"]=>
        string(5) "ES256"
      }
      ["header":"Jose\Component\Signature\Signature":private]=>
      array(0) {
      }
      ["signature":"Jose\Component\Signature\Signature":private]=>
      string(64) "�Q�..."
    }
  }
  ["payload":"Jose\Component\Signature\JWS":private]=>
  string(579) "�Sao..."
}
      
      





したがって、ヘッダーは正常にデコードされますが、本文は到着しません。ここでのヒントは、仕様にも記載されているように、ヘッダーの "zip": "DEF" です。



ペイロードは署名前に DEFLATE アルゴリズム (RFC1951 を参照) を使用して圧縮されます (注、これは zlib または gz ヘッダーなしの生の DEFLATE 圧縮である必要があります)




やってみよう:



echo json_encode(json_decode(gzinflate($jws->getPayload())), JSON_PRETTY_PRINT);
      
      





注意: JSON_PRETTY_PRINT 定数を指定することで、JSON オブジェクトをデコードしてから再コーディングし、読みやすくするために空白を追加します。



{
    "iss": "https:\/\/covid19.quebec.ca\/PreuveVaccinaleApi\/issuer",
    "iat": 1621476457,
    "vc": {
        "@context": [
            "https:\/\/www.w3.org\/2018\/credentials\/v1"
        ],
        "type": [
            "VerifiableCredential",
            "https:\/\/smarthealth.cards#health-card",
            "https:\/\/smarthealth.cards#immunization",
            "https:\/\/smarthealth.cards#covid19"
        ],
        "credentialSubject": {
            "fhirVersion": "1.0.2",
            "fhirBundle": {
                "resourceType": "Bundle",
                "type": "Collection",
                "entry": [
                    {
                        "resource": {
                            "resourceType": "Patient",
                            "name": [
                                {
                                    "family": [
                                        "Paulson"
                                    ],
                                    "given": [
                                        "Mikkel"
                                    ]
                                }
                            ],
                            "birthDate": "1987-xx-xx",
                            "gender": "Male"
                        }
                    },
                    {
                        "resource": {
                            "resourceType": "Immunization",
                            "vaccineCode": {
                                "coding": [
                                    {
                                        "system": "http:\/\/hl7.org\/fhir\/sid\/cvx",
                                        "code": "208"
                                    }
                                ]
                            },
                            "patient": {
                                "reference": "resource:0"
                            },
                            "lotNumber": "xxxxxx",
                            "status": "Completed",
                            "occurrenceDateTime": "2021-xx-xxT04:00:00+00:00",
                            "location": {
                                "reference": "resource:0",
                                "display": "xxxxxxxxxxxxxxxxxx"
                            },
                            "protocolApplied": {
                                "doseNumber": 1,
                                "targetDisease": {
                                    "coding": [
                                        {
                                            "system": "http:\/\/browser.ihtsdotools.org\/?perspective=full&conceptId1=840536004",
                                            "code": "840536004"
                                        }
                                    ]
                                }
                            },
                            "note": [
                                {
                                    "text": "PB COVID-19"
                                }
                            ]
                        }
                    }
                ]
            }
        }
    }
}
      
      





名前と生年月日を写真付き ID と組み合わせるのは賢明なプロセスだと思いますが、厳密に必要な量よりも少し多くの個人情報が含まれています。彼らはまた、私が望んでいた特定の承認ではなく、ワクチンに関する特定の情報を提供します。繰り返しになりますが、これにより、法域全体でさらに使いやすくなり、ポリシーが変更されるたびに JWS を再リリースする必要がなくなります。ケベック州の場合は、週に約 2 回行われます。



この分析を通して、私は、他人のワクチン接種の完全に有効な証拠を単に提示することを妨げるものがあるのではないかと考えました。全身が暗号で署名されているため、他人の予防接種証明書を変更して自分の名前を追加することはできません。つまり、予防接種証明書と写真付き身分証明書を組み合わせるのは非常に合理的な計画です。これは確かに空港ではそうですが、スポーツ会場などではそうではないと思います。 E. 2 番目の ID を要求されます。 QR コードをスキャンするだけで、デバイスにチェックマークが表示され、次のデバイスに移動します。



1 つの別れの考え: 私のプロセスは、どの個人データが QR コードにエンコードされているかを把握することに向けられていましたが、JWT モデルは、データを解析する前に検証することを忘れたり、署名なしを許可したりすることで、めちゃくちゃになりやすいことで有名です。 トークン...実装が承認された署名者の中央ホワイトリストを尊重しない場合、独自のキーで署名する完全に有効なトークンを簡単に作成できます。いつものように、モデルのセキュリティは、証明書利用者が標準をどれだけ厳密に実施するかにかかっています。



ただし、個人情報は、予防接種に関する完全な PDF ドキュメントに含まれている情報だけであることが判明しました。名前、生年月日、性別 (何らかの理由で)、および予防接種の日付と特定の用量に関する情報オーナー様、本日受け取りました。バーで運転免許証を提示することによるプライバシーへの影響に慣れたら、予防接種の証明書の提示を求められることを心配する必要はもうありません。



コードはゴミだらけですが、自分の QR コードの内容を確認したい場合は、この投稿のGitHub リポジトリチェックしください



All Articles