ラベル jws の投稿を表示しています。 すべての投稿を表示
ラベル jws の投稿を表示しています。 すべての投稿を表示

2024年3月2日土曜日

なぜSAMLの脆弱性は今でも報告されるのか。そしてOIDCやVCは大丈夫なのか

こんにちは、富士榮です。

故Craig Burtonが”SAML is Dead”という名言を放ってから永らく経つわけですが、まだまだ現役のSAMLには今でもたまに脆弱性のレポートが出てきます。

* SAML is Dead


昨日もSilver SAML Attackに関するレポートが出ていました。

(図は記事より)

非常にざっくり要約すると、以下のようなことが書かれています。

  • 例としてEntra IDを挙げている
  • Entra IDではSAML Responseへの署名を行う秘密鍵として外部で生成した鍵を利用することができる(BYOK)
  • この鍵が漏洩するなどして不正に利用されるとSAML Responseの偽造ができてしまう
  • 署名に使う鍵はIDシステムの内部で発行したものを使った方が良い
当たり前じゃん。秘密鍵が奪われているんだから。

と言いつつ、何でこう言うことが起きるのかをちゃんと見ていきたいと思います。

脆弱性の原因

SAMLに限らず、ではありますがこの手の仕組みの基本は「デジタル署名を施したデータ(SAML AssertionやOpenID Connectにおけるid_tokenなど)」をIdentity ProviderからRelying Partyからの要求等に応じて送出する、という仕組みになっています。
こうなってくると当然のことながらデジタル署名を確実に実施・検証する、そして先日から順番に読んでいるOAuth2.0 Security Best Current Practiceにも要所要所でててくる、送信元・送信先をいかに限定するか、やり取りの過程の中でCSRFなど攻撃者によるインジェクションをいかにして防ぐか、などがポイントになってきます。
  • 署名・検証の不備を防ぐ
  • 通信過程における攻撃者の関与を防ぐ
後者についてはOAuth2.0 Security Best Current Practiceで見ていくとして、今回は先のSAMLの件もあるのでデジタル署名について見ていきたいと思います。

デジタル署名の前提

デジタル署名を安全に行うための前提は言うまでもなく署名に使う鍵を安全に管理すること、につきます(もちろんアルゴリズムの安全性の話は言うまでもありません)。
今回挙げたケースは鍵を適切に管理できない状態が起きると危ないですよ、というレポートなので、改めて秘密鍵の管理の重要性を説いています。
(もちろん、安全に管理してくださいね、と言ったところで安全に管理できない人たちが多いのは理解していますが、管理方法について深掘りするのはここでは避けます)

なお、今回は鍵管理の話にフォーカスが置かれていましたが、実際の脆弱性はデジタル署名する対象となるデータの生成に依存することが多いと思います。いわゆる「正規化」の問題です。

実際過去に当ブログでも紹介したDuo SecurityのレポートはSAML Assertionを生成する際のXMLの正規化がポイントでした。

正規化の問題

SAMLにおけるXMLは非常に柔軟なデータ表現である一方で「どうやって同一性を担保するか」という問題を抱えています。
例を挙げると、
  • 空白値のトリミング
    • <attribute name="email">test@example.jp</attribute>
    • <attribute name="email"> test@example.jp </attribute>
    • を同一のものとして扱うか
  • コメントの取り扱い
    • <!-- これはコメントです -->
    • <attribute name="email">test@example.jp</attribute>
    • <attribute name="email">test@example.jp</attribute>
    • を同一のものとして扱うか

    と言う問題です。
    これらを解決するために行われるのが「正規化」です。

    XMLの正規化はW3CのCanonical XML Version 2.0で定義されています。

    実際に正規化をする際はこの仕様に従いXMLを処理していくことになるのですが、この過程においてバグが混入することがある、というのが問題の原因になっています。
    実際、先に挙げたDuoのレポートでは値の間にコメントがあった場合の処理に問題があり、別のユーザで認証され生成されたSAML Assertionを使って別のユーザになりすますことができてしまう、というものでした。


    OIDCやVCではどうなのか?

    こう考えるとJSONを使うOpenID Connectや、JSONやJSON-LDを使うVerifiable Credentialsはどうなのか?という疑問が湧いてきます。

    まずOpenID Connectですが署名付きJSONトークンにはRFC7515 JWS(JSON Web Signature)を使っています。

    崎村さんが経緯をブログで紹介されていますが、SAMLの正規化との戦いの経験から「JWSでは正規化を行わない」のがポイントの一つになっています。

    Verifiable Credentialsについてはどうなのか、というとIETFのSD-JWT VCをパターンではOpenID Connectと同じくJWSなので正規化は行いません。
    しかしながらW3C VCはJSON-LDも許容するのでこちらは考えないといけません。JSON-LDはRDF(Resource Description Framework)に基づくLinked Dataを表現するフォーマットですので、正規化をするには、
    を使っていく必要があります。
    しかしながらこの正規化で使われるアルゴリズムであるUniversal RDF Dataset Normalization Algorithm 2015(URDNA-2015)をJSON-LDに適用する際の注意点についてレポートが上がっていたりしますので、実装者はかなり気を遣う必要がります。
    実際、昨年のIIWでもJSON-LDのプロパティ名を変えても署名が崩れないというデモ(Linked Dataの性質を考えると当然の動きではありましたが)もありましたが、VCとして使うにはこのようなことが起きないように注意深くデータ構造を設計する必要があります。

    しかし、この辺りの議論を見ているとJWSが「正規化を行わない」という判断をしたのは非常に重要なことだったことがわかりますね。


    2021年12月21日火曜日

    ワクチン接種証明のVerifiable Credentialsを覗いてみる

    こんにちは、富士榮です。


    20日にデジタル庁がリリースしたワクチン接種証明アプリが話題ですね。内容的にはSMART Health Cardの仕様に沿った証明データが出てきているという話だったので中身を紐解いてみようかと思います。何しろSMART Health Cardの中身はW3CのVerifiable Credentials(VC)なので。



    参考1)

    ・海外用:ICAO VDS-NCとSMART Health Cards(SHC)

    ・国内用:SMART Health Card(SHC)のみ

            https://www.digital.go.jp/policies/vaccinecert/faq_06 より


    参考2)

    This document describes how clinical information, modeled in FHIR, can be presented in a form based on W3C Verifiable Credentials (VC).

            https://spec.smarthealth.cards/credential-modeling/ より



    ちなみに今年のWWDCで発表された通りiOS15のHealthアプリはSMART Health Cardをネイティブで扱えるようになっているので、実はこの接種証明アプリがなくても直接iPhoneのWalletに接種証明を入れることもできます。今はマイナンバーカードを使った本人確認〜接種証明の発行までをワンストップで実行させるには今回のようなアプリの形になっている方が扱いやすいとは思いますが、今回対象外になった自治体の住民や新旧の姓を併記にしているような方達はセルフサービスでの発行ではなく窓口等でQR発行〜iPhoneのWalletへの読み込みができるようになると便利だと思います。

    ちなみに現在も接種証明アプリで表示したQRコードを他のiPhoneで読み込むとWalletへ接種証明を入れることもできます。どこぞの記事には他人の接種証明が入れれちゃうのは問題だ、、みたいな意見も書かれていましたが、本来的には接種証明と本人確認は別の話だと思いますし、どっちにしても検証側が本人確認+接種確認をセットでやるのが筋だと思うので、なんだかなぁ、と思ってみてます。




    ちなみに、海外版のQRを解くとちゃんと英字氏名も入っているのですが、Walletに入れるとofficialではなくusualのtypeになっているname属性の値(日本語)が使われてしまうのがちょっと残念ですね。海外で見せた時に読めないだろう、、、という。


    ということで中身を読んでみます。

    1. 国内用と海外用

    先のFAQを見ると国内用と海外用があることがわかりますし、アプリを使うと国内用、海外用のどちらか、あるいは両方を発行することができます。国内用ではマイナンバーカードでの本人確認、海外用では加えてパスポートの確認が必要です。(ICAO用ですね。SMART Health Card版ではパスポート情報は不要なので)

    今回はVerifiable Credentialsを採用しているSMART Health Cardを解いていこうと思うので、国内用、もしくは海外用でもSMART Health Card版(QRコードを出すところで「SHC」を選択)を使います。


    2. QRコードを単純に読んでみる

    アプリにQRコードを画像ファイルとしてダウンロードさせる機能があるのでダウンロードして入っている文字列を読み出してみます。

    この辺りのサイトを使うとMacでもできるので便利です。


    shc:/〜という形でnumericなデータが見えます。

    これは仕様を読むと、JWS Compact SerializationでエンコードしたレコードをQRにエンコードする際はChunkをdecimalで表現した文字列を作ることになっているようです。具体的にはOrd(c)-45をすることでBase64UrlEncodeされたJWSの各文字を数値に変換しています。

    ということで、逆に文字列に戻してあげるにはJWSchars.map(num => String.fromCharCode(parseInt(num, 10) + 45)).join('');と言った感じで+45をして文字コードにしてあげればいいってことです。

    うまく繋げばいつもの「eyJ〜」が見えてきますので、decodeしてあげましょう。


    3. payloadはinflateRawする

    ここまで来れば単純にjwt.ioとかでdecodeすれば済むかな、と思ったらpayloadがバケバケでした。headerをよくみると"zip": "DEF"とあるのでpayloadがdeflateされてますね。

    これも仕様をみるとサイズを小さくするためにraw deflateがかかっているようです。仕方がないのでzlibなどを使ってinflateRawしてあげます。これでようやくまともに読めるpayloadができるのでtoString()して文字列としてJSONを取得してあげましょう。

    こんな感じでとれました。

    {"iss": "https://vc.vrs.digital.go.jp/issuer", "nbf": 1639958323.321147, "vc": {"type": ["https://smarthealth.cards#health-card", "https://smarthealth.cards#immunization", "https://smarthealth.cards#covid19"], "credentialSubject": {"fhirVersion": "4.0.1", "fhirBundle": {"resourceType": "Bundle", "type": "collection", "entry": [{"fullUrl": "resource:0", "resource": {"resourceType": "Patient", "name": [{"use": "usual", "family": "\u5bcc\u58eb\u69ae", "given": ["\u5c1a\u5bdb"]}, {"use": "official", "family": "NAOHIRO", "given": ["FUJIE"]}], "birthDate": "1974-09-28"}}, {"fullUrl": "resource:1", "resource": {"resourceType": "Immunization", "status": "completed", "vaccineCode": {"coding": [{"system": "http://hl7.org/fhir/sid/cvx", "code": "207"}]}, "patient": {"reference": "resource:0"}, "occurrenceDateTime": "2021-07-20", "performer": [{"actor": {"display": "MHLW_Gov_of_JAPAN"}}], "lotNumber": "3003190"}}, {"fullUrl": "resource:2", "resource": {"resourceType": "Immunization", "status": "completed", "vaccineCode": {"coding": [{"system": "http://hl7.org/fhir/sid/cvx", "code": "207"}]}, "patient": {"reference": "resource:0"}, "occurrenceDateTime": "2021-08-17", "performer": [{"actor": {"display": "MHLW_Gov_of_JAPAN"}}], "lotNumber": "3004734"}}]}}}}

    あとは整形してあげればOKなので、じっくり中身を見れるようになります。ちなみにコードを書けばいいんでしょうけど面倒なのでこの辺りのツールを使ってみやすくしてます。


    4. じっくり覗き込む

    とりあえず整形したpayloadがこれです。


    {
        "iss": "https://vc.vrs.digital.go.jp/issuer",
        "nbf": 1639958323.321147,
        "vc": {
            "type": [
                "https://smarthealth.cards#health-card",
                "https://smarthealth.cards#immunization",
                "https://smarthealth.cards#covid19"
            ],
            "credentialSubject": {
                "fhirVersion": "4.0.1",
                "fhirBundle": {
                    "resourceType": "Bundle",
                    "type": "collection",
                    "entry": [
                        {
                            "fullUrl": "resource:0",
                            "resource": {
                                "resourceType": "Patient",
                                "name": [
                                    {
                                        "use": "usual",
                                        "family": "富士榮",
                                        "given": [
                                            "尚寛"
                                        ]
                                    },
                                    {
                                        "use": "official",
                                        "family": "NAOHIRO",
                                        "given": [
                                            "FUJIE"
                                        ]
                                    }
                                ],
                                "birthDate": "1974-09-28"
                            }
                        },
                        {
                            "fullUrl": "resource:1",
                            "resource": {
                                "resourceType": "Immunization",
                                "status": "completed",
                                "vaccineCode": {
                                    "coding": [
                                        {
                                            "system": "http://hl7.org/fhir/sid/cvx",
                                            "code": "207"
                                        }
                                    ]
                                },
                                "patient": {
                                    "reference": "resource:0"
                                },
                                "occurrenceDateTime": "2021-07-20",
                                "performer": [
                                    {
                                        "actor": {
                                            "display": "MHLW_Gov_of_JAPAN"
                                        }
                                    }
                                ],
                                "lotNumber": "3003190"
                            }
                        },
                        {
                            "fullUrl": "resource:2",
                            "resource": {
                                "resourceType": "Immunization",
                                "status": "completed",
                                "vaccineCode": {
                                    "coding": [
                                        {
                                            "system": "http://hl7.org/fhir/sid/cvx",
                                            "code": "207"
                                        }
                                    ]
                                },
                                "patient": {
                                    "reference": "resource:0"
                                },
                                "occurrenceDateTime": "2021-08-17",
                                "performer": [
                                    {
                                        "actor": {
                                            "display": "MHLW_Gov_of_JAPAN"
                                        }
                                    }
                                ],
                                "lotNumber": "3004734"
                            }
                        }
                    ]
                }
            }
        }
    }


    5. 見えてくること

    なるほど、ということで読んでいくと以下のことがわかります。

    • Verifiable Credentialsですね
    • CredentialTypeは3種類
      • https://smarthealth.cards#health-card" 
      • "https://smarthealth.cards#immunization"
      • "https://smarthealth.cards#covid19"
    • CredentialSubjectにはFHIRの仕様でデータが入っている
    • resource:0がpatient情報、つまり接種を受けた人の情報(と言っても名前と生年月日)
    • resource:1/2がImmunization情報、つまり接種情報の1回目と2回目
    • vaccineCodeがワクチンの種類
      • https://build.fhir.org/valueset-vaccine-code.htmlをみると、207は「SARS-COV-2 (COVID-19) vaccine, mRNA, spike protein, LNP, preservative free, 100 mcg/0.5mL dose」とあるのでCOVID-19のワクチンっぽい。
      • しかしFHIRのバージョンが4.0.1とCredentialSubjectには書いてあるけど、4.0.1のvaccineCodeにはCOVID-19の定義がないのは間に合ってないってことなのかな?上記のコードは4.6.0のもの。4.0.1のvaccineCodeはこちら
    • performerは「MHLW_Gov_of_JAPAN」とあるので日本の厚生労働省
    • lotNumberがワクチンのロットですね。ちなみに鉄粉が入ってたっていう件、私はビンゴでした・・・


    と、ざっくりですがQRコードからJWSを復元、decodeしてみました、という話でした。

    他にもJWSの署名を深堀をしたり、と興味は尽きない接種証明アプリですが、総じてこの短期間で使いやすいアプリが出てきたっていうのは素晴らしいことだな、と思っています。(個人の感想)