トップ «前の日記(2021-08-18) 最新 次の日記(2021-09-05)» 編集

日々の破片

Subscribe with livedoor Reader
著作一覧

2021-08-29

_ padding-oracleはおもしろい

例によってアスキードワンゴの嘉平さんにネットワークプロトコルハッカーズガイドをもらった。

この本は、抜群におもしろい。

たかだか300ページ強にこれでもかとネットワークプロトコル(といっても物理層はさすがに無い)の諸要素について解説している。

当然、やたらとすっ飛ばしているので、読者はこの本をガイドとして自力で深堀りする必要はある。

とはいえ、読むだけでもえらくおもしろい。

おもしろいのは、(最初に戻って)これでもかと諸要素について解説しているからだ。本当に奥が深くてネットワークはおもしろい!

無くても良いとはいえ、直接実行するスクリプトはPython、Wireshark(キャプチャを取るので必須)に自力で解析処理を記述するためにLua、サンプルの解析用のちゃちなチャットプログラムにC#を使っているので、これらは読めたほうが良い。(とはいえ、Java、C、Ruby、Perl、PHPあたりをまじめに使っていれば読めるはず)

というか、そもそもおれがRubyを使い始めたのは、C++でネットワークアプリケーションのクライアントやサーバーを書きまくっていたころに、手軽にソケットを叩いてテスト用のクライアントやサーバーを作れるWin32で実行できるスクリプト言語が必要だったからなのだった。というわけでネットワークプログラミングにはC系(今だとGoやRustなのだろうけど)とスクリプト(RubyとかPythonとか)の両方が使えるほうがいずれにしても良いと思う。

で、こちらも考えながら読んでいたりするので、まだ全体の2/3くらいなのだが、途中、セキュリティについての解説がある。最終的にはPKIになるとはいえ、これまた諸要素について乱数、暗号、署名、証明書などについて解説がある。

暗号化の中でブロック暗号化としてECB(使うな)とCBCが出てくる(これは当然なのだが、当然とは言わずに説明してある)。で、ブロックということはブロックに満たない場合にどうするかとしてパディングについて説明が続く。

で、CBC+PKCS#7の弱点としてパディングオラクル攻撃について2ページ程度の説明がある。おそらく、パディングオラクル攻撃について説明することで、CBCに必要となるIV(初期化ベクタ)やパディングについて明確に説明できるからだろう。と同時に、作法としてナイーブに成功失敗をクライアントに返してはならないことの説明にもなる。

というわけで、おもしろいので、以下、パディングオラクル攻撃(パディングを答えを教えてくれる水晶玉として利用する攻撃)のサンプルコードを書いてみた。実装はP.108に書いてあるとおり。下の例はAES-128で暗号化した16バイト1ブロックの暗号文とIVから平文を求める。複数ブロックの場合はIVではなく直前のブロックを利用することになる。

# coding: utf-8
require 'openssl'
 
# 攻撃者は知らない
KEY = "\f\xC0\xCE+\x04\xE8\x99Fe\xEF\xE6\xB1\xB9\x93\xDB!".force_encoding('ASCII-8BIT')
# 暗号化されたメッセージ(攻撃者は当然知っている)
CRYPTED = "\x9B\xF2;\xFD.\x92\x1F\x0F\x9C\x13\xA2\x8B\x9EY\xE5\xF9".force_encoding('ASCII-8BIT')
# IVはメッセージと共に拾えるものとする
IV = "6\xBCYZ\xA4\xB6\xAD)cat\xFB\x9F\xC9\xC5\xB2".force_encoding('ASCII-8BIT')
 
# 復号の成功、失敗を返すサーバー側のAPI
def blackbox(iv, crypted)
  cipher = OpenSSL::Cipher.new('aes-128-cbc')
  cipher.decrypt
  cipher.key = KEY
  cipher.iv = iv
  cipher.update(crypted) + cipher.final
  true
rescue
  false
end
 
# paddingを作成するヘルパ
def dummy_iv(offset, encoded)
  ret = ''.force_encoding('ASCII-8BIT')
  return ret if offset == 1
  (1...offset).each do |i|
    c = encoded[i - 1] ^ offset
    ret = c.chr + ret
  end
  ret
end
 
encoded = []  # IV適用前の暗号化ブロック
plain = []    # 平文(padding付き)
# IVを変形してパディング1から16に相当するIV適用前のブロックを得る
(1..16).each do |padding|
  org = IV[16 - padding]
  result = []
  tail = dummy_iv(padding, encoded)
  # 総当たりで暗号文とキーにマッチするパティング相当のコードを得る
  (0..255).each do |x|
    new_iv = IV[0...(16 - padding)] + x.chr + tail
    if blackbox(new_iv, CRYPTED)
      result << x
      break if result.length >= 2
    end
  end
  if result.length == 1
    plain << (result[0] ^ padding ^ org.getbyte(0))
    encoded << (result[0] ^ padding)
  else # 2回出現するのは実際のパディングと一致する場合
    r = result.find {|e| e != org}
    encoded << (r ^ padding)
    plain << (r ^ padding ^ org.getbyte(0))
  end
  puts plain.inspect if $DEBUG
  puts encoded.inspect if $DEBUG
end
puts plain[plain[0]..-1].reverse.map(&:chr).join()
ネットワークプロトコルハッカーズガイド キャプチャ、解析、エクスプロイトの理論と実践 (アスキードワンゴ)(James Forshaw)

2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|

ジェズイットを見習え