Hatena::ブログ(Diary)

知らないけどきっとそう。 RSSフィード

 

2017-03-06 5 月末に公開される SHAttered のコードを予想して新しい PDF を作る このエントリーを含むブックマーク

前回に引き続き https://shattered.it/ の件です。

Following Google’s vulnerability disclosure policy, we will wait 90 days before releasing code that allows anyone to create a pair of PDFs that hash to the same SHA-1 sum given two distinct images with some pre-conditions.

https://security.googleblog.com/2017/02/announcing-first-sha1-collision.html

Google 曰く「誰にでも、いくつかの前提条件を満たすふたつの異なる画像から、同じ SHA-1 ハッシュPDF のペアが作成できるコードをリリースする」とのことです。ただし、脆弱性公開ポリシーに従い、リリースは発表から 90 日後です。

現在公開されている情報から、このコードがどのようなものか予想し、実証しようと思います。

縦に長くなったので、最初に成果物へリンクしておきます。

さて、SHA-1 ハッシュが同じで違う画像が表示される GooglePDF 1, PDF 2 にはどのような技巧が用いられているでしょうか。

%PDF-1.3
%....


1 0 obj
<</Width 2 0 R/Height 3 0 R/Type 4 0 R/Subtype 5 0 R/Filter 6 0 R/ColorSpace 7 0 R/Length 8 0 R/BitsPerComponent 8>>
stream
[...]
endstream
endobj

2 0 obj
1024
endobj

3 0 obj
740
endobj

4 0 obj
/XObject
endobj

5 0 obj
/Image
endobj

6 0 obj
/DCTDecode
endobj

7 0 obj
/DeviceRGB
endobj

8 0 obj
421385
endobj

[...]
%%EOF

PDF ファイルなので、一般的な PDF の構造があります。ここに特殊な箇所は見つかりません。この構造部分は PDF 1PDF 2 で完全に一致しています。

"1 0 obj" における stream [...] endstream 間のデータが、幅 1024 高さ 740 の Image として DCTDecode されて(つまり JPEG 画像)表示されます。強いて不自然な点をあげると、"/Width 1024/Height 740" と書かず "/Width 2 0 R/Height 3 0 R" のように obj への参照となっていることです。これは後述する理由により、endstream より後方にあるデータは任意に書き換えが可能なため、画像のサイズを変更できるようにする工夫と窺えます。

stream 内の JPEG データを詳しく見る前に、通常の JPEG の構造を簡単に理解しておきましょう。

JPEG は、複数のセグメントで構成されています。セグメントは、マーカーとペイロードからできていて、マーカーがセグメントがどのような種類の情報かを表します。重要なマーカーは以下のものくらいです。

  • SOI(イメージ開始)
  • SOF(フレーム開始)
  • DHT(ハフマンテーブル定義)
  • DQT(量子化テーブル定義)
  • SOS(画像データ開始)
  • EOI(イメージ終了)

例えば https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Lichtenstein.jpg/256px-Lichtenstein.jpg の構造は下記のとおりです。

SOI  0000
APP0 0010
DQT  0043
DQT  0043
SOF0 0011
DHT  001c
DHT  0040
DHT  001b
DHT  0030
SOS  000c
SCAN 4983
EOI  0000

マーカーの後ろにある数字は、ペイロードのバイト長です。SCAN はマーカーではなく画像データです。詳細は JPEG - Wikipedia を参照してください。

それでは、件の PDF に含まれる JPEG の構造を分析していきます。

PDF 1        PDF 2

SOI  0000    SOI  0000
COM  0024    COM  0024
    V            V
COM  0173    COM  017f    <--- (A)
[   |   ]    [   |   ]    <--- (B) collision blocks
    V            |
COM  00fc        |        <--- (C)
    |            V
    |        APP0 0010    <--- (D)
    |        DQT  0043
    |        DQT  0043
    |        SOF2 0011
    |        DHT  001e
    |        DHT  001d
    V        COM  0006
COM  27f4        V
    |        SOS  000c
    |        SCAN 27a6
    |        DHT  0038
    V        COM  0006
COM  218d        V
    |        SOS  0008
    |        SCAN 210b
    |        DHT  0070
    V        COM  0006
COM  9f9a        V
    |        SOS  0008
    |        SCAN 9f1e
    |        DHT  006a
    V        COM  0006
COM  659a        V
    |        SOS  0008
    |        SCAN 6519
    |        DHT  006f
    V        COM  0006
COM  ae2e        V
    |        SOS  0008
    |        SCAN adee
    |        DHT  002e
    V        COM  0006
COM  36d2        V
    |        SOS  0008
    |        SCAN 36c2
    V        COM  0006
COM  11b5        V
    |        SOS  000c
    |        SCAN 1172
    |        DHT  002d
    V        COM  0006
COM  2c5b        V
    |        SOS  0008
    |        SCAN 2c1b
    |        DHT  002e
    V        COM  0006
COM  2dce        V
    |        SOS  0008
    |        SCAN 2d8e
    |        DHT  002e
    V        COM  0006
COM  37d2        V
    |        SOS  0008
    |        SCAN 37c0
    |        EOI  0000
    V            X
APP0 0010        X
APP1 0040        X
APP13 0038       X
SOF0 0011        X
DHT  001f        X
DHT  00b5        X
DHT  001f        X
DHT  00b5        X
DQT  0043        X
DQT  0043        X
DRI  0004        X
SOS  000c        X
SCAN 39104       X
EOI  0000        X

COM というマーカーが頻出しますが、これはコメントを表します。コメントマーカーが現れると、そのペイロードの長さ分だけ、データが無視されるため、矢印でその範囲がスキップされることを表現しています。また、EOI の後ろにあるデータも、同様に無視されるので X とします。見ての通り、PDF 1 の COM に PDF 2 のデータが、PDF 2 の COM に PDF 1 のデータが入っているという、カドゥケウスの杖のような形をしていることが判明しました。

https://commons.wikimedia.org/wiki/File:Caduceus.svg

PDF 1PDF 2 で異なる部分は (A) と (B) のみで、残りは完全に一致しています。(A) において、コメントとみなす長さを 0x0173 か 0x017f かに変えることで、続きが (C) か (D) かに分かれ、異なる画像が表示されるという原理です。

(A) でコメント長を示す数値が異なるので、当然 SHA-1 ハッシュも違う値が計算されます。ですが、その直後の SHAttered による (B) collision blocks でそれが解消され、再びハッシュが等しくなります。そして (B) 以降は、どのようなデータを追加しても、ふたつのファイルの SHA-1 ハッシュは同一のままです。"1 0 obj" の stream が、後方の obj を参照していたのは、こういった理由からでした。

結論として、本稿の目的であるコードとは、(C) 以降にあるようなデータと同様の構造を持つ JPEG を生成するコードと言えるでしょう。

ということで、そのようなコードを(Perl で)実装し、それを使って作成した PDF のペアが https://asannou.github.io/shatterized-1.pdfhttps://asannou.github.io/shatterized-2.pdf です。

無論、ハッシュが一致しています。

$ shasum shatterized-1.pdf shatterized-2.pdf
5135c8373be5ceb5763406b307cd17d179fafbe2  shatterized-1.pdf
5135c8373be5ceb5763406b307cd17d179fafbe2  shatterized-2.pdf

また、画像のみの PDF だけでなく https://asannou.github.io/d.hatena.ne.jp-asannou-20170226-1.pdfhttps://asannou.github.io/d.hatena.ne.jp-asannou-20170226-2.pdf のように、一部の画像(ブックマーク数)が改ざんされているものも作ることができます。

$ shasum d.hatena.ne.jp-asannou-20170226-1.pdf d.hatena.ne.jp-asannou-20170226-2.pdf
6a2cd0570bc6d05cd4777a17925a2095e928582d  d.hatena.ne.jp-asannou-20170226-1.pdf
6a2cd0570bc6d05cd4777a17925a2095e928582d  d.hatena.ne.jp-asannou-20170226-2.pdf

残念ながら、この手法には欠点があります。それは COM のペイロードの長さの最大が 0xffff であるため、それより長いデータが存在すると、収まりきらないことです。例として https://upload.wikimedia.org/wikipedia/commons/e/e8/Lichtenstein.jpg の構造を示します。

SOI  0000
APP0 0010
DQT  0043
DQT  0043
SOF0 0011
DHT  001c
DHT  004a
DHT  001a
DHT  0032
SOS  000c
SCAN 79009
EOI  0000

SOS マーカーの後の画像データの長さが 0x79009 もあって、コメントに収まりません。おそらく、冒頭の「いくつかの前提条件を満たすふたつの異なる画像」というのは、このことを示唆しているのではないかと推測します。

しかしまだ、画像データを分割して、段階的に表示するプログレッシブ JPEG に変換する方法が残っています。やってみましょう。

$ jpegtran -progressive Lichtenstein.jpg > Lichtenstein_p.jpg
SOI  0000
APP0 0010
DQT  0043
DQT  0043
SOF2 0011
DHT  001b
DHT  0019
SOS  000c
SCAN c486
DHT  0034
SOS  0008
SCAN f1f2
DHT  002c
SOS  0008
SCAN 1203
DHT  002d
SOS  0008
SCAN 2110
DHT  0037
SOS  0008
SCAN 7195
DHT  002c
SOS  0008
SCAN 1729d
SOS  000c
SCAN 3718
DHT  0027
SOS  0008
SCAN 358d
DHT  0029
SOS  0008
SCAN 4499
DHT  002a
SOS  0008
SCAN 2c3fb
EOI  0000

確かに 10 分割されましたが、まだ 0xffff 以上の画像データが残っています。スキャンの方法をテキストファイルで詳細に指定し、分割数を増やすことができるので、それを試します。テキストファイルの仕様は https://github.com/mozilla/mozjpeg/blob/master/wizard.txt を参照してください。

$ cat <<EOD > scans.txt
0: 0-0,   0, 0;
1: 0-0,   0, 0;
2: 0-0,   0, 0;
0: 1-1,   0, 0;
0: 2-2,   0, 0;
0: 3-3,   0, 0;
0: 4-5,   0, 0;
1: 1-63,  0, 0;
2: 1-63,  0, 0;
0: 6-7,   0, 0;
0: 8-9,   0, 0;
0: 10-12, 0, 0;
0: 13-17, 0, 0;
0: 18-63, 0, 0;
EOD
$ jpegtran -scans scans.txt Lichtenstein.jpg > Lichtenstein_pp.jpg
SOI  0000
APP0 0010
DQT  0043
DQT  0043
SOF2 0011
DHT  001c
SOS  0008
SCAN b243
DHT  001a
SOS  0008
SCAN 1ffa
DHT  0019
SOS  0008
SCAN 1b35
DHT  0021
SOS  0008
SCAN 87b7
DHT  0020
SOS  0008
SCAN 8b6f
DHT  0021
SOS  0008
SCAN 5ac4
DHT  0029
SOS  0008
SCAN cf74
DHT  0038
SOS  0008
SCAN 6474
DHT  0032
SOS  0008
SCAN 4647
DHT  0027
SOS  0008
SCAN 9426
DHT  0026
SOS  0008
SCAN 9464
DHT  002a
SOS  0008
SCAN a9a7
DHT  002e
SOS  0008
SCAN bf73
DHT  003b
SOS  0008
SCAN dcd2
EOI  0000

無事成功しました。それでも画像データが巨大な場合は、最大個数に分割しても収まらないことがあると思います。その時は、画像の品質を下げる等を検討する必要があります。

In order to prevent this attack from active use, we’ve added protections for Gmail and GSuite users that detects our PDF collision technique.

https://security.googleblog.com/2017/02/announcing-first-sha1-collision.html

最後に Gmailhttps://asannou.github.io/shatterized-1.pdfhttps://asannou.github.io/shatterized-2.pdf が検出されるかを確認しました。

f:id:asannou:20170306002735p:image:w535

手法で作られたファイルも、Gmail に添付して送ろうとするとエラーになりました。

Google がコードを公開したら、答え合わせをします。

2017-02-26 SHAttered で Git の SHA-1 ハッシュを衝突させられるか試す このエントリーを含むブックマーク

https://shattered.it/ のリリースを受けて、Git において、違うファイルをコミットしたにも関わらず、それらのコミットを参照する SHA-1 ハッシュが同じである状態を実現できるかを試しました。


同じ SHA-1 ハッシュを持つ PDF ファイルで可能か


https://shattered.it/ で公開されている、SHA-1 ハッシュが同じ PDF ファイルを、それぞれ空のレポジトリにコミットします。

$ wget https://shattered.it/static/shattered-1.pdf https://shattered.it/static/shattered-2.pdf
$ diff shattered-1.pdf shattered-2.pdf
Binary files shattered-1.pdf and shattered-2.pdf differ
$ shasum shattered-1.pdf shattered-2.pdf
38762cf7f55934b34d179ae6a4c80cadccbb7f0a  shattered-1.pdf
38762cf7f55934b34d179ae6a4c80cadccbb7f0a  shattered-2.pdf
$ cp shattered-1.pdf shattered.pdf
$ git --git-dir=.git-1 --work-tree=. init
Initialized empty Git repository in /path/to/.git-1/
$ git --git-dir=.git-1 --work-tree=. add shattered.pdf
$ GIT_AUTHOR_DATE='Fri Feb 24 15:00:00 JST 2017' GIT_COMMITTER_DATE='Fri Feb 24 15:00:00 JST 2017' git --git-dir=.git-1 --work-tree=. commit -m 'test'
[master (root-commit) e95789a] test
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 shattered.pdf
$ git --git-dir=.git-1 --work-tree=. log --pretty=fuller
commit e95789af5bf00006938d8ab048ab51c9b68711a6
Author:     asannou <asannou@example.com>
AuthorDate: Fri Feb 24 15:00:00 2017 +0900
Commit:     asannou <asannou@example.com>
CommitDate: Fri Feb 24 15:00:00 2017 +0900

    test

shattered-1.pdf をコミットしたときの SHA-1 ハッシュは e95789af5bf00006938d8ab048ab51c9b68711a6 です。

$ cp shattered-2.pdf shattered.pdf
$ git --git-dir=.git-2 --work-tree=. init
Initialized empty Git repository in /path/to/.git-2/
$ git --git-dir=.git-2 --work-tree=. add shattered.pdf
$ GIT_AUTHOR_DATE='Fri Feb 24 15:00:00 JST 2017' GIT_COMMITTER_DATE='Fri Feb 24 15:00:00 JST 2017' git --git-dir=.git-2 --work-tree=. commit -m 'test'
[master (root-commit) ded44e8] test
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 shattered.pdf
$ git --git-dir=.git-2 --work-tree=. log --pretty=fuller
commit ded44e864ff901c3bb6367f13ad6aeb0b6c0cfa0
Author:     asannou <asannou@example.com>
AuthorDate: Fri Feb 24 15:00:00 2017 +0900
Commit:     asannou <asannou@example.com>
CommitDate: Fri Feb 24 15:00:00 2017 +0900

    test

全く同じ日時を指定したのですが shattered-2.pdf をコミットしたときは ded44e864ff901c3bb6367f13ad6aeb0b6c0cfa0 となり、違うハッシュでした。原因を知るために、コミットの SHA-1 ハッシュがどのように計算されるかを調べましょう。

Git のコミットは、コミットオブジェクトというもので管理されています。コミットオブジェクトは、下記のように確認することができます。

$ git --git-dir=.git-1 --work-tree=. cat-file -p e95789af5bf00006938d8ab048ab51c9b68711a6
tree 8004c8a7b6fce1452539556bb4c4c91b92b5c2bc
author asannou <asannou@example.com> 1487916000 +0900
committer asannou <asannou@example.com> 1487916000 +0900

test

コミットの SHA-1 ハッシュというのは、このコミットオブジェクトの内容の先頭に "commit <size>\0" を付加して SHA-1 ハッシュを計算したものです。

$ git --git-dir=.git-1 --work-tree=. cat-file -p e95789af5bf00006938d8ab048ab51c9b68711a6 > commit-1
$ wc -c commit-1
     163 commit-1
$ printf "commit 163\0" > commit-header-1
$ cat commit-header-1 commit-1 | shasum
e95789af5bf00006938d8ab048ab51c9b68711a6  -

実は、これをやってくれるコマンド git hash-object が既にあります。

$ git hash-object -t commit commit-1
e95789af5bf00006938d8ab048ab51c9b68711a6

ここで shattered-2.pdf のコミットオブジェクトを覗くと、tree という値だけが異なっていることがわかります。

$ git --git-dir=.git-2 --work-tree=. cat-file -p ded44e864ff901c3bb6367f13ad6aeb0b6c0cfa0
tree 32a3329d74097e4a877b1180106e65d3b9f76848
author asannou <asannou@example.com> 1487916000 +0900
committer asannou <asannou@example.com> 1487916000 +0900

test

tree もツリーオブジェクトなので、内容を確認します。

$ git --git-dir=.git-1 --work-tree=. cat-file -p 8004c8a7b6fce1452539556bb4c4c91b92b5c2bc
100644 blob ba9aaa145ccd24ef760cf31c74d8f7ca1a2e47b0  shattered.pdf
$ git --git-dir=.git-2 --work-tree=. cat-file -p 32a3329d74097e4a877b1180106e65d3b9f76848
100644 blob b621eeccd5c7edac9b7dcba35a8d5afd075e24f2  shattered.pdf

ツリーオブジェクトも似たような方法で SHA-1 ハッシュが求められます。

$ printf "100644 shattered.pdf\0%s" $(echo ba9aaa145ccd24ef760cf31c74d8f7ca1a2e47b0 | xxd -r -p) | git hash-object -t tree --stdin
8004c8a7b6fce1452539556bb4c4c91b92b5c2bc
$ printf "100644 shattered.pdf\0%s" $(echo b621eeccd5c7edac9b7dcba35a8d5afd075e24f2 | xxd -r -p) | git hash-object -t tree --stdin
32a3329d74097e4a877b1180106e65d3b9f76848

つまり blob という値が一致しないため、ハッシュが異なると言えます。そして同様に blobブロブオブジェクトです。

$ git --git-dir=.git-1 --work-tree=. cat-file -p ba9aaa145ccd24ef760cf31c74d8f7ca1a2e47b0 > blob-1
$ git --git-dir=.git-2 --work-tree=. cat-file -p b621eeccd5c7edac9b7dcba35a8d5afd075e24f2 > blob-2

ブロブオブジェクトの内容は、コミットしたファイルそのものです。

$ diff blob-1 shattered-1.pdf
$ diff blob-2 shattered-2.pdf
$

ブロブオブジェクトSHA-1 ハッシュが求められます。

$ git hash-object -t blob blob-1
ba9aaa145ccd24ef760cf31c74d8f7ca1a2e47b0
$ git hash-object -t blob blob-2
b621eeccd5c7edac9b7dcba35a8d5afd075e24f2

ここで git hash-object がどのような処理をするかを思い出すと、ハッシュが異なる理由がわかります。

$ wc -c blob-1
  422435 blob-1
$ printf "blob 422435\0" > blob-header-1
$ cat blob-header-1 blob-1 | shasum
ba9aaa145ccd24ef760cf31c74d8f7ca1a2e47b0  -

要するに、ブロブオブジェクトハッシュには、コミットされたファイルそのままの SHA-1 ハッシュが使われず、ファイルに "blob <size>\0" ヘッダが付加されたものの SHA-1 ハッシュが使用されるため、それをもとに計算される、ツリーオブジェクト、コミットオブジェクトハッシュも一致しないという真相でした。


f:id:asannou:20170227184909p:image:w640


後日リリースされる、同じハッシュPDF のペアを作成するコードを使えば可能か


90 日後に、ふたつの異なる画像から、同じ SHA-1 ハッシュを持つ PDF のペアを作ることができるコードがリリースされるそうです。

Following Google’s vulnerability disclosure policy, we will wait 90 days before releasing code that allows anyone to create a pair of PDFs that hash to the same SHA-1 sum given two distinct images with some pre-conditions.

https://security.googleblog.com/2017/02/announcing-first-sha1-collision.html

これを用いて、先頭が "blob <size>\0" となっていて、内容が異なり、同じハッシュのファイルを作れるでしょうか。


f:id:asannou:20170226200904p:image:w640

https://shattered.it/static/pdf_format.png


おそらく PDF に含まれる JPEG の部分のみを差し替えられるコードと思われますので prefix (pre-determined) となっている PDF Header の前に "blob <size>\0" を挿入するのは無理だろうと予想します。

このコードを予想する記事 を書きました。


prefixブロブオブジェクトにして同じ手法を用いれば可能か


https://shattered.it/static/shattered.pdf では prefixPDF にしていますが、Gitブロブオブジェクトのヘッダを prefix として、書かれている通りにすれば、同じハッシュブロブオブジェクトを作れるでしょうか。

多分可能でしょうが 6,500 年の CPU 時間と 110 年の GPU 時間が必要だそうです。

This attack required over 9,223,372,036,854,775,808 SHA1 computations. This took the equivalent processing power as 6,500 years of single-CPU computations and 110 years of single-GPU computations.

https://shattered.it/

既存のコミットと同じハッシュのコミットを作ることは可能か


SHAttered は、prefix が同じで内容が異なるふたつのファイルを調整して、同じ SHA-1 ハッシュにするというアプローチなので、既にあるコミットと同じハッシュのコミットを作ることには使えません。

ただし、同じハッシュを持つ、正常なコミット A と不正なコミット B を作り、まず A を信頼させてから B にすり替えるというシナリオはありえます。


コミットに GPG による署名があれば信頼してもよいか


Git には、コミットに GPG で署名する機能があるので、そのコミットオブジェクトを確認します。

$ git --git-dir=.git-3 --work-tree=. add shattered.pdf
$ git --git-dir=.git-3 --work-tree=. commit -S -m 'test'

You need a passphrase to unlock the secret key for
user: "asannou (Git signing key) <asannou@example.com>"
2048-bit RSA key, ID 05CFBEA7, created 2017-02-26

[master (root-commit) e82463b] test
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 shattered.pdf
$ git --git-dir=.git-3 --work-tree=. cat-file -p e82463b
tree 8004c8a7b6fce1452539556bb4c4c91b92b5c2bc
author asannou <asannou@example.com> 1487916000 +0900
committer asannou <asannou@example.com> 1487916000 +0900
gpgsig -----BEGIN PGP SIGNATURE-----
 Comment: GPGTools - https://gpgtools.org

 iQEcBAABCgAGBQJYsq5mAAoJEMxRxTEFz76nLr8IALmPtkI9ZgvHMtKqQOcLl51l
 YOFoMu4k2fQ65DJyJFaj/HXhcdbw21rUkf1OAsxcpewFWZV2udfJUWt3LItNKbXf
 YWM/Z074VPBIdJlme7jMfdq96Q4fJwX7Lf5ypRgzOYswIj2Yd+2viuKZjwx5yujt
 pC/H4Gc08hmOhKpVNXlmDNd6IO8McBOLAGD3NvA8xsXFlSoLquVwcaq3vWTwKpT1
 DMKG18aDYr7LRjXS3417r3zn2a2rQaZl7F6gBKy9+qH+e9gfZa/wNrzRxYxZ+lJw
 tNOA/rLflylROK+k6TtISTJXRAhIUCafbD8WMLaD9KAxxR6gKO2huGFH0yxFq9I=
 =tbnx
 -----END PGP SIGNATURE-----

test

前述までのコミットオブジェクトに gpgsig という署名が付加された形です。この署名の対象はなにかというと、コミットオブジェクトの内容のみのようです。

$ git --git-dir=.git-3 --work-tree=. cat-file -p e82463b > commit-3
$ gpg --detach-sign commit-3

You need a passphrase to unlock the secret key for
user: "asannou (Git signing key) <asannou@example.com>"
2048-bit RSA key, ID 05CFBEA7, created 2017-02-26

$ gpg --verify commit-3.sig commit-3
gpg: Signature made 日  2/26 19:33:48 2017 JST using RSA key ID 05CFBEA7
gpg: Good signature from "asannou (Git signing key) <asannou@example.com>" [ultimate]

つまり、署名によって tree, author, committer とコミットメッセージの正しさしか保証されないということです。tree には SHA-1 ハッシュしか書かれていないので、同一のハッシュを持つツリーオブジェクトがあれば、それにすり替えることが可能と考えます。


f:id:asannou:20170227191711p:image:w640


したがって、署名されていたとしても、すり替えが可能な状態にあるレポジトリのファイルを信頼するのはやめましょう。

2017-02-13 Amazon EC2 SSH 救命索 このエントリーを含むブックマーク

これは Amazon EC2 Run Command Advent Calendar 2016 の 75 日目の記事です。

皆さんは、アドベントカレンダーですか?

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

なんらかの理由(キー紛失、設定ミス、ユーザ不在、人類滅亡など)で Amazon EC2SSH 接続できなくなったとき インスタンス再作成 を強いられることがありますが、そうならないように Run Command - Amazon EC2 Systems Manager | AWS で保険をかけておくことができます。

Run Command は EC2 インスタンス上に SSM エージェントのインストール - Amazon Elastic Compute Cloud をしておくことで、AWS CLI などでリモートからシェルコマンドの実行ができるフレンズです。

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

最初に、EC2 インスタンスSSM サービスと通信するための IAM ロール "EC2RoleforSSMRunShellScript" をアタッチした、インスタンスプロファイル "EC2RoleJaparipark" を Terraform で作成します。

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_iam_policy" "ec2-ssm" {
  name = "EC2RoleforSSMRunShellScript"
  path = "/"
  policy = <<EOD
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:DescribeAssociation",
        "ssm:GetDeployablePatchSnapshotForInstance",
        "ssm:GetDocument",
        "ssm:GetParameters",
        "ssm:ListAssociations",
        "ssm:ListInstanceAssociations",
        "ssm:PutInventory",
        "ssm:UpdateAssociationStatus",
        "ssm:UpdateInstanceAssociationStatus",
        "ssm:UpdateInstanceInformation"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2messages:AcknowledgeMessage",
        "ec2messages:DeleteMessage",
        "ec2messages:FailMessage",
        "ec2messages:GetEndpoint",
        "ec2messages:GetMessages",
        "ec2messages:SendReply"
      ],
      "Resource": "*"
    }
  ]
}
EOD
}

resource "aws_iam_role" "ec2-ssm" {
  name = "EC2RoleJaparipark"
  path = "/"
  assume_role_policy = <<EOD
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOD
}

resource "aws_iam_role_policy_attachment" "ec2-ssm" {
  role = "${aws_iam_role.ec2-ssm.name}"
  policy_arn = "${aws_iam_policy.ec2-ssm.arn}"
}

resource "aws_iam_instance_profile" "ec2-ssm" {
  name = "${aws_iam_role.ec2-ssm.name}"
  roles = ["${aws_iam_role.ec2-ssm.name}"]
}

用意されている AmazonEC2RoleforSSM でも同じことができますが、権限が強すぎるため、必要がなさそうなものを削りました。それについては EC2’s most dangerous feature を読むと、圧倒的なわかりを得ます。

対象の EC2 インスタンス i-xxxxxxxxxxxxxxxxx に "EC2RoleJaparipark" をアタッチします。以前はインスタンス作成時にのみ可能だったのですが New! Attach an AWS IAM Role to an Existing Amazon EC2 Instance by Using the AWS CLI | AWS Security Blog にて、作成済みインスタンスでもできるようになりました。すごーい!

$ aws ec2 associate-iam-instance-profile --instance-id i-xxxxxxxxxxxxxxxxx --iam-instance-profile Name=EC2RoleJaparipark

そして EC2 インスタンス i-xxxxxxxxxxxxxxxxx に SSM エージェントをインストールしましょう。

$ sudo yum install amazon-ssm-agent
$ sudo start amazon-ssm-agent

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

さて、リモートからコマンドを送信するための、IAM ポリシー "SSMLuckyBeast" を作成します。AWS 管理ポリシーの AmazonSSMFullAccess なども使えますが、その場合 "EC2RoleforSSMRunShellScript" をアタッチしているすべてのインスタンス送信可能になります。

provider "aws" {
  region = "ap-northeast-1"
}

data "aws_caller_identity" "aws" {}

resource "aws_iam_policy" "ssm" {
  name = "SSMLuckyBeast"
  path = "/"
  policy = <<EOD
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ssm:SendCommand",
      "Resource": [
        "arn:aws:ssm:ap-northeast-1::document/AWS-RunShellScript",
        "arn:aws:ec2:ap-northeast-1:${data.aws_caller_identity.aws.account_id}:instance/i-xxxxxxxxxxxxxxxxx"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "ssm:ListCommandInvocations",
      "Resource": "arn:aws:ssm:ap-northeast-1:${data.aws_caller_identity.aws.account_id}:*"
    }
  ]
}
EOD
}

最後に、適当な IAM ユーザに "SSMLuckyBeast" をアタッチして準備は終わりです。

$ aws iam attach-user-policy --user-name kaban --policy-arn arn:aws:iam::$(aws sts get-caller-identity --output text --query Account):policy/SSMLuckyBeast

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

それでは、やっていきます

$ aws ssm send-command --output text --document-name AWS-RunShellScript --instance-ids i-xxxxxxxxxxxxxxxxx --parameters commands="uname -a"
COMMAND	8020f507-8adc-4536-80af-d18dd2dafa5f		0	AWS-RunShellScript	0	1486890312.52	50	0			1486886712.52	Pending	Pending	1
INSTANCEIDS	i-xxxxxxxxxxxxxxxxx
NOTIFICATIONCONFIG
COMMANDS	uname -a

command-id で結果

$ aws ssm list-command-invocations --output text --details --command-id 8020f507-8adc-4536-80af-d18dd2dafa5f
COMMANDINVOCATIONS	8020f507-8adc-4536-80af-d18dd2dafa5f		AWS-RunShellScript	i-xxxxxxxxxxxxxxxxx		1486886712.64			Success	Success
COMMANDPLUGINS	aws:runShellScript	Linux ip-172-31-28-103 4.4.30-32.54.amzn1.x86_64 #1 SMP Thu Nov 10 15:52:05 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
			ap-northeast-1	0	1486886713.22	1486886713.22			Success	Success
NOTIFICATIONCONFIG

がおー

$ aws ssm send-command --output text --query "Command.CommandId" --document-name AWS-RunShellScript --instance-ids i-xxxxxxxxxxxxxxxxx --parameters commands="echo $(cat ~/.ssh/id_rsa.pub) >> /home/ec2-user/.ssh/authorized_keys"
7b410797-1d73-4cc9-a29c-e845e6f0621a
$ aws ssm list-command-invocations --output text --details --command-id 7b410797-1d73-4cc9-a29c-e845e6f0621a
COMMANDINVOCATIONS	7b410797-1d73-4cc9-a29c-e845e6f0621a		AWS-RunShellScript	i-xxxxxxxxxxxxxxxxx		1486888202.22			Success	Success
COMMANDPLUGINS	aws:runShellScript				ap-northeast-1	0	1486888202.66	1486888202.66			Success	Success
NOTIFICATIONCONFIG

よかったですね

$ ssh ec2-user@203.0.113.1
Last login: Thu Feb  9 19:08:31 2017 from 198.51.100.1

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2016.09-release-notes/
No packages needed for security; 3 packages available
Run "sudo yum update" to apply all updates.

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

Run Command をバックドアっぽく使いましたが、玄関にしてもよさがあります。そうすると、権限を IAM で管理でき AWS CloudTrail (AWS API の呼び出し記録とログファイル送信) | AWS でコマンド送信が記録されるため、監査にも便利です。

Run Command を対話的にする GitHub - koshigoe/aws-ssm-console があり asannou/aws-ssm-console - Docker Hub したので、こうなります。

$ docker run -it --rm -v ~/.aws:/root/.aws asannou/aws-ssm-console --instance-ids i-xxxxxxxxxxxxxxxxx
>> uname -a
Running uname -a
[i-xxxxxxxxxxxxxxxxx]    Success: uname -a
	Linux ip-192-168-1-5 4.4.41-36.55.amzn1.x86_64 #1 SMP Wed Jan 18 01:03:26 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

たっのしー!

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

なお、お分かりですが ssm:ListCommandInvocations の Resource が適切ではないので、ことごとく実行結果が見放題です。カスタムロールおよびポリシー使用したアクセス設定 - Amazon Elastic Compute Cloud にあるのもガバガバだし、そうか、アマゾンは、、

2016-10-10 I am 最小権限でアクセスキーを発行したいマン このエントリーを含むブックマーク

ここまでは、一般的なウェブサービスのユーザ発行フローとほぼ同じ。

利用者が AWSリソースにアクセスするためには、アクセスキーを取得する必要がある。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iam:*LoginProfile",
        "iam:*AccessKey*",
        "iam:*SSHPublicKey*"
      ],
      "Resource": "arn:aws:iam::account-id-without-hyphens:user/${aws:username}"
    },
    {
      "Effect": "Allow",
      "Action": [
        "iam:ListAccount*",
        "iam:GetAccountSummary",
        "iam:GetAccountPasswordPolicy",
        "iam:ListUsers"
      ],
      "Resource": "*"
    }
  ]
}
      • ユーザの一覧画面を経由するマネジメントコンソールの設計上、本来は不要な権限を許可しなければならない問題がある(Statement の 2 要素目)

最小権限の原則 - Wikipedia に従って、下記のポリシーのみでアクセスキーを発行する方法を考える。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iam:*AccessKey*"
      ],
      "Resource": "arn:aws:iam::account-id-without-hyphens:user/${aws:username}"
    }
  ]
}
  1. FirefoxChromehttps://console.aws.amazon.com/iam/home にアクセス
  2. アドレスバーに "j" を入力
  3. 続けて下記のブックマークレットを入力して、アクセスキーが書かれたファイルをダウンロード
avascript:$.ajax({url:'/iam/service/proxy/CreateAccessKey',type:'POST',contentType:'application/json',data:JSON.stringify({userName:UserInfo.name})}).done(function(data){a=document.createElement('a');document.body.appendChild(a);a.download=UserInfo.name+'.accesskey.txt';a.target='_blank';a.href=window.URL.createObjectURL(new Blob([data],{type:'text/plain'}));a.click()}).fail(function(data){alert(JSON.stringify(data))})

2016 年も終盤というのに、未だにブックマークレットなんて書いている(あとはてな記法)。javascript スキームが対策されていたり、Microsoft Edge では全く使えなかったりして時の流れを感じた。

2016-01-31 ダブルクリック TLS このエントリーを含むブックマーク

f:id:asannou:20160131105405j:image

  • 熱海に来ています
  • 前回 に引き続き Let's Encrypt の話題です

f:id:asannou:20160131115144p:image

    • 入力を求められたら、その環境のドメイン名を入力します
      • 文字列の場合は、逆引きでドメイン名を解決するおまけ機能付き
    • あとはファイアウォールダイアログなどが出るかもしれませんが、適宜許可してください
    • 問題なければ、証明書 example.com.cert.pem と秘密鍵 example.com.key.pem が生成されます
    • このへんで時間切れ

2015-12-04 無限無料 SSL/TLS 証明書 このエントリーを含むブックマーク

f:id:asannou:20151204221948j:image

By Nicolas Raymond - Cosmic Rose(2014) / CC BY 2.0

※ただしドメイン認証(DV)証明書に限る

  • では、実際に asannou.0t0.jp の証明書を発行してみましょう
$ sudo docker run -it --rm -p 443:443 -v $(pwd)/letsencrypt:/etc/letsencrypt \
> quay.io/letsencrypt/letsencrypt certonly \
> -a standalone \
> -m asannou@example.com \
> -d asannou.0t0.jp \
> --standalone-supported-challenges tls-sni-01
  • ポート 443 が使えない場合は 80 を使ってください
$ sudo docker run -it --rm -p 80:80 -v $(pwd)/letsencrypt:/etc/letsencrypt \
> quay.io/letsencrypt/letsencrypt certonly \
> -a standalone \
> -m asannou@example.com \
> -d asannou.0t0.jp \
> --standalone-supported-challenges http-01

Please read the Terms of Service at

https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf. You

must agree in order to register with the ACME server at

https://acme-v01.api.letsencrypt.org/directory

  • Terms of Service に Agree した後、下記のメッセージが出れば成功です
IMPORTANT NOTES:
 - If you lose your account credentials, you can recover through
   e-mails sent to asannou@example.com.
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/asannou.0t0.jp/fullchain.pem. Your cert will
   expire on 2016-03-03. To obtain a new version of the certificate in
   the future, simply run Let's Encrypt again.
 - Your account credentials have been saved in your Let's Encrypt
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Let's
   Encrypt so making regular backups of this folder is ideal.
 - If you like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

  • カレントディレクトリの letsencrypt/ にファイル群が生成されています
$ sudo tree -A letsencrypt
letsencrypt
├── accounts
│     └── acme-v01.api.letsencrypt.org
│         └── directory
│           └── 5718fec7af7ec2b3783d69300cfc0789
│               ├── meta.json
│               ├── private_key.json
│               └── regr.json
├── archive
│   └── asannou.0t0.jp
│       ├── cert1.pem
│       ├── chain1.pem
│       ├── fullchain1.pem
│       └── privkey1.pem
├── csr
│   └── 0000_csr-letsencrypt.pem
├── keys
│   └── 0000_key-letsencrypt.pem
├── live
│   └── asannou.0t0.jp
│       ├── cert.pem -> ../../archive/asannou.0t0.jp/cert1.pem
│       ├── chain.pem -> ../../archive/asannou.0t0.jp/chain1.pem
│       ├── fullchain.pem -> ../../archive/asannou.0t0.jp/fullchain1.pem
│       └── privkey.pem -> ../../archive/asannou.0t0.jp/privkey1.pem
└── renewal
    └── asannou.0t0.jp.conf

11 directories, 14 files
  • 証明書は letsencrypt/live/asannou.0t0.jp/cert.pem にあります
$ sudo openssl x509 -text -noout -in letsencrypt/live/asannou.0t0.jp/cert.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            01:aa:6c:81:85:99:b9:f9:e9:e5:f6:f7:4d:78:df:a9:fb:d3
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X1
        Validity
            Not Before: Dec  4 05:49:00 2015 GMT
            Not After : Mar  3 05:49:00 2016 GMT
        Subject: CN=asannou.0t0.jp
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    ...
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier:
                C5:85:E4:49:C5:3F:10:DD:C7:43:C6:47:33:2A:72:50:A6:4A:8E:EC
            X509v3 Authority Key Identifier:
                keyid:A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1

            Authority Information Access:
                OCSP - URI:http://ocsp.int-x1.letsencrypt.org/
                CA Issuers - URI:http://cert.int-x1.letsencrypt.org/

            X509v3 Subject Alternative Name:
                DNS:asannou.0t0.jp
            X509v3 Certificate Policies:
                Policy: 2.23.140.1.2.1
                Policy: 1.3.6.1.4.1.44947.1.1.1
                  CPS: http://cps.letsencrypt.org
                  User Notice:
                    Explicit Text: This Certificate may only be relied upon by Relying Parties and only in accordance with the Certificate Policy found at https://letsencrypt.org/repository/

    Signature Algorithm: sha256WithRSAEncryption
         ...
$ sudo openssl s_server \
> -cert letsencrypt/live/asannou.0t0.jp/cert.pem \
> -key letsencrypt/live/asannou.0t0.jp/privkey.pem \
> -CAfile letsencrypt/live/asannou.0t0.jp/chain.pem \
> -www \
> -accept 443
  • このままでは 90 日で有効期限が切れてしまうため、60 日経過した時点で更新します
$ sudo docker run -it --rm -p 443:443 -v $(pwd)/letsencrypt:/etc/letsencrypt \
> quay.io/letsencrypt/letsencrypt certonly \
> -a standalone \
> -m asannou@example.com \
> -d asannou.0t0.jp \
> --standalone-supported-challenges tls-sni-01 \
> --renew-by-default

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/asannou.0t0.jp/fullchain.pem. Your cert will
   expire on 2016-04-03. To obtain a new version of the certificate in
   the future, simply run Let's Encrypt again.
 - If you like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

  • この作業自動化可能なので、毎月繰り返すことにより無限に無料で SSL/TLS サーバが維持できます
  • ドメイン認証証明書を置き換えたい方、HTTPS 化をおこないたい方はご相談くださいませ

ここから蛇足

Error: urn:acme:error:rateLimited :: There were too many requests of a given type :: Error creating new cert :: Too many certificates already issued for: 0t0.jp
    • 個数は有限だった
    • ドメインごとにリミットがある
An unexpected error occurred:
The request message was malformed :: Error creating new authz :: Name is blacklisted

2014-12-22 Kindle Cloud Reader の本を Kindle DX で読みたかった このエントリーを含むブックマーク

これは Kindle DX Advent Calendar 2014 の 22 日目の記事です。

f:id:asannou:20141221190423j:image


タイトル: ブラックジャックによろしく
著作者名: 佐藤秀峰
サイト名: 漫画 on web

  • 何・・・だと・・・?
  • 前世紀に流行った FLMASK みたいな姿に変わり果てていました
  • 取り急ぎ JSONP の内容をチェックします
loadResource2({
    "resList": null,
    "data": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAA.../2Q==",
    "metadata": {
        "id": 2,
        "map": [144, 0, 8, 8, 144, 204,
                144, 612, 168, 8, 144, 204,
                432, 408, 328, 8, 144, 204,
                288, 0, 488, 8, 144, 204,
                0, 204, 648, 8, 144, 204,
                0, 408, 8, 228, 144, 204,
                576, 0, 168, 228, 144, 204,
                0, 612, 328, 228, 144, 204,
                576, 204, 488, 228, 144, 204,
                0, 816, 648, 228, 144, 208,
                432, 0, 8, 448, 144, 204,
                288, 612, 168, 448, 144, 204,
                0, 0, 328, 448, 144, 204,
                432, 816, 488, 448, 144, 208,
                144, 816, 648, 448, 144, 208,
                288, 408, 8, 668, 144, 204,
                288, 816, 168, 668, 144, 208,
                576, 612, 328, 668, 144, 204,
                576, 816, 488, 668, 144, 208,
                144, 408, 648, 668, 144, 204,
                432, 204, 8, 888, 144, 204,
                288, 204, 168, 888, 144, 204,
                432, 612, 328, 888, 144, 204,
                144, 204, 488, 888, 144, 204,
                576, 408, 648, 888, 144, 204],
        "type": "image/jpeg"
    }
});
  • metadata.map の配列が [dx, dy, sx, sy, w, h] の繰り返しとなっていて、幅 w ピクセル、高さ h ピクセルの画像を、座標 sx, sy から dx, dy にマップすると復元できるようです(多分)
  • 上記の例でいうと、最初の 6 要素は、モザイク画像の 8, 8 から 144, 204 のサイズで切り出して、新しい画像の 144, 0 に貼り付けることになります

2014-11-04 Kindle Cloud Reader の本を Kindle DX で読みたい このエントリーを含むブックマーク

loadResource70({"data":"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEC...gD//Z","metadata":{"id":70,"type":"image/jpeg"},"resList":null});

    • 画像リソースを並列に取得して JSZip に渡してダウンロード
    • ZIP ファイルを適当なツールPDF に変換して完成
    • 写真が荒いですが、読み込むとこんな感じになりました

http://f.st-hatena.com/images/fotolife/a/asannou/20141104/20141104022918.jpg

http://f.st-hatena.com/images/fotolife/a/asannou/20141104/20141104023631.jpg


タイトル: ブラックジャックによろしく
著作者名: 佐藤秀峰
サイト名: 漫画 on web

  • 9.7インチ以上の Kindle はもう発売されないのでしょうか
  • 個人的には以下のようなシンプルな電子書籍リーダーを望みます
    • 電子インク(画面の美しさと充電頻度の少なさ)
    • 9.7インチクラス
    • 150ppi 以上の解像度(Kindle DX はやや足りない)
    • SDカードなどの外部ストレージKindle DX は内蔵 4GB のみで不便)
    • PDF対応(ZIP をそのまま見開きで見られればうれしい)
    • 軽量
ソニー デジタルペーパー DPT-S1

ソニー デジタルペーパー DPT-S1

2014-02-17 OAuth 以外の方法 このエントリーを含むブックマーク

f:id:asannou:20140217033609j:image

By zama_zama - IMG_0741-2(2014) / CC BY 2.0

  • API を持たないウェブサービスから、保護された情報を安全に取得する方法を検討しています
  • 正攻法では OAuth なのですが、よりサーバ側の実装コストが少ない手段を模索してみましょう
  • 比較表
サーバ側コスト取得元コスト動作検証リスク
OAuth 2.0 + API不可トークン漏洩
アプリケーション固有のパスワード不可固有パスワード漏洩
ネイティブアプリなし可能ログインパスワード漏洩
XMLHttpRequest Level 2容易取得情報漏洩
  • クライアントから情報を取得するケースは、ユーザによる改ざんの可能性があるので、用途によっては注意が必要ですね
  • ネイティブアプリはたびたび意図に反した送信が発覚しますが、ユーザから見て JavaScript は安心感があります
  • サーバ間の通信については、後日掘り下げて考えてみたいと思います

2013-11-14 OAuthセキュリティ強化を目的とする拡張仕様を導入してもらいました このエントリーを含むブックマーク

f:id:asannou:20131114214431j:image

By Steven Crawford - Attack of the rotten oranges(2011) / CC BY-NC-SA 2.0

  • そして Request Token 復活的な発想に至ったのでした
  • 今気づいたけど「カスタム URL スキーム上書き Authorization Code 横取り問題」の解決にもなってる?