なんとなくZip Slipについて語る
この記事はZip Slipが公開されてから3ヶ月近くも後に書かれたお話です。
そうだ、旅に出よう
ディレクトリトラバーサル(パストラバーサル)は、格式張って語ると、ユーザ入力のバリデーションが不十分で親ディレクトリにトラバースするってことです。
トラバースって横断って訳されるけど、マウントな隣の山に移るみたいな意味もあるような無いような。とりあえず意識高く語るなら、このディレクトリはリスペクトできなかった、俺はビッグになるためにパレントディレクトリへのトラバースにチャレンジした。です。
名前付き脆弱性(Branded Vulnerabilities)
脆弱性だろうがなんだろうが、名前を付けるってのは悪くないアイデアです。脆弱性の原因が〇〇だった場合、それが初めてって劇レアケースならいいんだけど、多くは原因と影響を説明しても「で?なに?」って言われちゃいます。それって美味しいの?ぐらいの反応すら返ってきません。
そこで、2014年の命名Heartbleedさんはうまい方法をとりました。①影響がイメージできるペットネームをとる②ドメインとる③アイコンをつくる。これで勝利です!
そう、商品ではなくストーリーごと売り込めです。こういった感じに、印象的なお名前の付いたバグ(Bug With An Impressive Name)はBWAINって呼ばれます。
Zip Slipのご紹介
Zip Slipはぶっちゃけ前述の名前付き脆弱性の一つでBWAINです。Zipがズレちゃった、てへぺろって感じですね。Zip Slipでは、Zipファイルを展開するときにディレクトリトラバーサルが起きるライブラリを一斉摘発しました。githubで検挙者リストと対策状況を公開しています。ライブラリの脆弱性じゃなくて仕様で、呼び出し側ツールの脆弱性なんじゃね?って意見もありますが、その議論はスルー。
※Zipper(チャック)を開き閉めするとき噛んだりズレたり引っかかったりすると困りますが、そんな不具合をまず起こさないYKKの高性能なジッパーが普及して、昭和末期には一度は幸せになったんですが、最近は安いジッパーが服に使われていることもあって、それ原因で服が寿命に…。残念。
そんな対策で大丈夫か?
Zip Slipの脆弱性説明文
脆弱性の説明はこうなってます。
説明文から受ける印象
ああ、".."に起因するディレクトリトラバーサルのBWAINね。そう思うと生き残れないかもしれません、あなたは頓死に一歩近づいています。脆弱性があるって指摘されたからには対策すると良いわけですが、そこで原因をどう思うかが重要。
× ".."に起因する
です。".."だと思うと、なんと情弱だったのでしょう!と自己批判に迫られる事態に陥ります。
推奨している対策(.NET編)
snykは、親切にも脆弱性のあるコードの例とその対策を公開しています。以下のコードは.NETのサンプルです。どのコードも引用です。
脆弱性のあるコード:
ヤバいコードの例です。destDirectoryがなんだろうが、fileが相対パスだったら親ディレクトリにさかのぼることも可能だし、ましては絶対パスだったりすると、どこでもフリーアクセスです。豪快ですね。
ちなみに、Path.Combineは第二引数(の正規化評価結果)が絶対パスだと、第一引数は無視して第二引数を使う仕様です。第三引数があるパターンもやっぱり後ろが優先されます。言われてみると「そういう仕様はあり得るな」でしょうが、単純に引数を連結してから正規化だと思ってると大変なことになります。
正しいコード:
対策も公開しています。「まったく簡単だ」です。標準ライブラリにパスを正規化するAPIがあるので、正規化した後のものが期待したパスから始まっているかチェックすればいいんです。簡単なお仕事です。
さて、本題
対策済みリストが出ているからといって、信じていいんでしょうか?
友達は疑い深いのでリストをチェックしたそうです。前述のように.NETでは単純明快な対策がでているので、まずは.NETのライブラリを見てみると良さそうってこともあり、
DotNetZip.Semverdってのを見てみたんだそうです。ほら、対策済みになってますよね。
親切にgithubの修正差分へのリンクもついてます。便利ですねー。コメント文にきちんと対策内容も書いてます。ご立派。
けど、あれ? if (dir == "..") って呪文が!?
うーん。もちろん、ディレクトリデリミタを"\"を"/"で正規化するときに、Linuxでは有効だとしてもWindowsでは無視される余分なスペース省いているんですよね。知らないけど絶対そう。
.NETのWindows世界の常識というか定説というか
さて、.NETのWindows世界の高校の屋上で以下の会話があったとしましょう。
A「親ディレクトリって".."だけですよね? 」
B「えっ、なんだって」
別にラノベの突発性難聴主人公ではありません。そうです。.NETのWindows世界ではDotDotこと".."だけが親ディレクトリではありません。DotDotの後ろにいくつかスペースを加わってても、親切なWindowsは華麗にスルーしてくれます。そうです。DotDotSpacesはDotDotと等価なのです。
※下位フレームワークというか言語のランタイムによっては、WindowであってもDotDotとDotDotSpacesが異なる可能性もゼロではないです(SE的危険表現)。
推奨している対策(Java編)
たまたま見たライブラリが.NETのWindowsの有名な地雷を踏んでいるように見えるので(呼び出し元でチェック済みな可能性も微レ存)、ついでに疑り深くJavaも見てみましょう。同じく、どのコードも引用元です。
脆弱性のあるコード:
これまた分かりやすい例ですね。destnationDirがなんだろうと、Enumerationキーに相対パスや絶対パスが入っていると困ったことになります。
正しいコード:
これの対策の例示も明快ですね。標準ライブラリのAPIを信頼してgetCanonicalPathで正規化した後のものが期待したパスから始まっているか確かめます。
サンプルはサンプルであって(以下略)
getCanonicalPathはシンボリックリンクをフォローする仕様だけど、destinationfileもgetCanonicalPathしているから比較しているので、展開先ディレクトリがシンボリックリンクであってもうまく動作するサンプルコードになっていて素晴らしいです。
なお、存在しないパスを指定するとIOExceptionが出て、そのままメッセージを表示すると原因パスが懇切丁寧に表示される事があるので、必要に応じてキャッチしましょう。作業ディレクトリにあたるdestinationfileが漏れた場合は脆弱性とみなされるシチュエーションプレイがあります。この辺りは常識の範囲だし、問題ないこともあるのでサンプルでは省略してるのでしょう。
おまけ
アタッシェケースという暗号ソフトはv3.3.0で、ATCファイル(独自の暗号化ファイルフォーマット)のディレクトリトラバーサル対策が強化されました。けど不十分だったということで2018年8月末にv3.4.0が公開され、対策をブラックリスト方式から、展開先パスを確認するホワイトリストっぽい方式に変更したそうです。これって、ZipSlipの対策でも紹介されている定番のアプローチです。一方、ブラックリスト方式ってのはDotNetZip.Semverdの例で紹介したような、これが入ってるとダメのアプローチです。完全に使いこなせば問題ないですが、ブラックリストアプローチはなかなか難しいものです。脆弱性追加修正の謝辞にでてくる、対処方法を指導したJPCERT/CCさんグッジョブですね。
魔法の呪文
いかがでしたか、いかがでしょうか、いかがでしたでしょうか、調べてみました。