てきとうなメモスマートフォン用の表示で見るてきとうなメモ本の感想とか技術メモとかこの広告は、90日以上更新していないブログに表示しています。FileUploadにDoS攻撃の脆弱性なのだが、攻撃方法の詳細は説明されていない。調べたところ、推測なのだが、2年前のDoS攻撃の脆弱性(CVE-2014-0050)と関係するものではないかと思う。2年前の脆弱性はこちら。脆弱性この脆弱性はboundaryに長い文字列を指定してマルチパートのPOSTリクエストを送ると発生し、無限ループに陥ることがあるというものである。この脆弱性に対するPoCはネットを探せば見つかる。以下の2つの条件を満たすと攻撃できるようだboundaryを4092バイト以上にするbodyに4097バイト以上のboundaryを含まないデータを入れる適当にサーブレットを作ってPoCを実行すると無限ループに陥る。サーブレットを停止してスタックトレースを見ると以下でループしているようだ。288ループしているのはMultipartStream$ItemInputStream.makeAvailableだが、このメソッドを説明するためには各クラスの説明を先にした方が良い。関係する主要なクラスはMultipartStreamとItemInputStreamである。MultipartStreamはマルチパートのPOSTリクエストをparseするクラスであり、子クラスItemInputStreamはboundaryで分割された現在のitemを読みだすためのクラスである。この2つのクラスは効率化のために内部のバッファを利用している。このバッファの処理が脆弱性の原因である。MultipartStreamはバッファ関係のフィールドとして以下のものを持っている。boundaryをバッファから探すために必要なサイズさらに、ItemInputStream側にもバッファ関係のフィールドとして以下のものがある。boundaryを検索するために保持すべき実際のバイト数。MultipartStream.keepRegionと似たようなものheadとtailはbuffer内の有効な部分を指している。1バイト読みだすとheadをインクリメントする。head=tailになるとInputStreamから実際に読み出してbufferに入れている。boundaryはリクエストで指定されたboundaryを入れるバイト列だが、それだけでなくprefixに[CR,LF,-,-]がくっついている。これは実際の区切り文字にはboundaryに--がくっつくためであり、さらに行頭なのでCR,LFがprefixとしてくっついている。この辺りはコンストラクタのコードを見ることで確認できる。...}MultipartStream内では、buffer内からboundaryを検索する際にboundaryの途中までマッチしたという情報を残していない。そのままだと読み込んだ単位の境にboundaryがある場合は検知できない。そのため、バッファを全て読み込んでしまい入力ストリームからバッファに読み込む時に、boundaryのサイズだけは常にバッファに残しておき、残りの部分に入力ストリームからデータを書き込む必要性がある。keepRegion)である。ItemInputStream.makeAvailableの話に戻る。makeAvailableは読み込み可能なデータがバッファ内にない場合にデータを入力ストリームから読み込みバッファに入れて、読み込み可能なバイト数を返すメソッドである。}available()は現在の読み込み可能なバイト数を返すが、ちょっと特殊で以下のようになっている。head;}buffer内にboundaryが見つかった場合はboundaryの開始インデックスまでのサイズを返すが、見つからなければ今有効なサイズ(tail-head)からboundaryに必要なサイズを除いたサイズを返している。boundaryのサイズ分はすぐには使えないと考えているようだ。ループしているのはmakeAvailableのfor文の部分である。このforループが以下のような条件で攻撃された時の動作を考えてみる。入力ストリームがboundaryのない4097文字のバイト列とする。fillされているbufSize=4096(デフォルト)boundaryにはCR,LF,-,-,boundaryの値が入っている。よって4096文字keepRegionはboundary.lengthなので4096すると、以下のように処理が進む初期状態ではhead=tail=0となっている。input.readでバッファを読んでいき、tailも読んだサイズに拡張される。0その後も上記の状態のままinput.readしても0バイト読み込むだけなので終了しないとなって無限ループする。fileuploadのv1.3.1ではこの問題が修正されており、以下のコードが追加されている。BOUNDARY_PREFIX.length,boundaryLengthはリクエスト指定したboundaryのサイズ+4なので4092バイト以上のboundaryが指定された場合はエラーになるようになった。大元のCVE-2016-3092の話に戻る。boundaryが4092バイト以上の場合は良いが4091バイトだったらどうか。keepRegion)=min(4096,4095)=1となり、availableは1を返す。res;}1バイトに対するSystem.arraycopyが発生する。リクエストボディが小さければ大したことないが1MB程度のリクエストを送るとこれを1バイトずつ処理して100万回近くループが発生してしまう。これがCVE-2016-3092の脆弱性ではないかと思う。bytes.http://mail-archives.apache.org/mod_mbox/www-announce/201606.mbox/%3C6223ece6-2b41-ef4f-22f9-d3481e492832@apache.org%3Eと無限ループになるわけではないし、今回の修正のdiffをみるとコンストラクタで最低でもboundaryLengthの2倍をとるようになっているという点とも合致する。this.boundary.length;こうすることで、バッファの半分程度はavailable()で返るようになり、デフォルトのバッファのサイズが4KBなので最低でも2KB程度ずつは処理されることになる。メモ月別アーカイブはてなブログをはじめよう!bosconoさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。