ちりもつもればミルキーウェイ

好奇心に可処分時間が奪われる

Goの最近のおもしろCVEとその対応を眺める

はじめに

どうもみなさんお世話になってます @convto です。
最近ちらちら直近のGoのCVEとかみてるので、危険度は度外視してパっとみて面白かったCVEとそのパッチの感想みたいなのをまとめつつバージョンアップを啓蒙していきます。
みんなバージョンアップすぐやろうな!

これは Kyash Advent Calendar 2022 の2日目の記事だよ。

CVE情報の集め方

Goに関するCVEは基本的に修正パッチ提供後にメーリスで流れてきます。はいってればいろいろ見れるはずです。
https://groups.google.com/g/golang-announce

[security] prefixがあるのがセキュリティ関連の情報を含んでます。ここから任意のCVEをさがせばよいです。

CVEの修正パッチの探し方

メーリスから探してるならだいたい該当の投稿にissueチケットへのリンクがあるので、そこからパッチ追えます。
CVE-IDから調べてるケースでも、だいたいCVE登録時にgolang-annonceの該当の投稿へのリンクがあるので結局そこからたどれます。

おもしろかったCVEの紹介

興味があって調べたCVEで個人的に面白かったやつを紹介します。
危険度関係なくおもしろポイントが高いものです。順番も適当なのであしからず。

win環境でcrypto/randにでかいバッファでreadするとハングする

CVE: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-30634
パッチ: https://github.com/golang/go/commit/32dedaa69e22f1a058ae90b9484fd4c3b46fbcbf

windows環境でReadするときにbufのbyte countが32bit値をこえたbyte数のとき、すなわち4GBちょいを超えてるとハングしちゃうらしい。
これについてはよく発見したな(よくこんなの踏み抜いたな)という気持ちがつよい。めちゃめちゃシバいてたんやろか...

修正としては、windowsからの RtlGenRandom syscallからのreadを1<<31-1 bytesごとに32bit超えないように区切ったよう。

32bit APIぽいし超えるとなんかどえらい感じになるんだろうな。
ドキュメントっぽいの確認したけど32bitを超えるbyte countの読み取りでどうなるか言及されてなかったのでわからん(deprecatedなのねこのAPI)
ULONGなのでいけそうな気はするけどなー
https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom

チケット みるかぎり32bit Readまでしかできんぽい?
ただエラー返しておしまいじゃなくてハングするのはちょっと面白い。
Goの実装都合なのかRtlGenRandom側がそうなのか、どっちなんだろーとおもったけど上記チケットだとGo実装の32bit uintに丸める処理に問題があるみたいな話っぽい。

せっかくなのでなんで uint32(len(b)) でuint32まるめするのが問題になるのか考えます。

例えば64bitのbyte countを持つ値が来たときのことを考えると、初回のReadはそのuint32で評価できる部分だけ読み取るので下位32bitぶんのbyte countをReadします。このとき rand.Read() なり io.ReadFull() みたいなbufぶん全部読み切る処理だと続きを読もうとします。で、残ったbyteは下位32bitぶんのbyte countはRead済みなので uint32(len(b)) が必ず0になります。なので0byte読み取って return 0, nil を返します。bufぶん全部読み取りたい呼び出し側は何度読んでも絶対0なので先に進めずに、無限にReadが発行されるというからくりです。

そういえば調べてる途中で出くわしたのでメモっておきますが、RtlGenRandom自体deprecatedぽいし過去にはより新しいやつにしよーぜみたいな提案もあったぽいけど RtlGenRandomのままにしたよう

big.Floatとbig.RatのGobDecodeで小さいgobバイナリを流すとpanicで死ぬ

CVE: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-32189
パッチ: https://github.com/golang/go/commit/9240558e4f342fc6e98fec22de17c04b45089349

どうやら big.FloatのGobEncode側の実装 をみるにmath/big.Floatの型が内部的にもつ、多倍長のwordで表現される値以外の、pos/negとかの情報がすくなくとも前半6byteに出力されるっぽい(big.Ratは5byte)。で、まともにencodeされてることを信頼してdecode側では断りなくsliceアクセスしてたよう。

というわけで、お手々で適当なbyteつくってまともにencodeされてない値をbig.Floatやbig.Ratを期待するdecoderに食わせるとpanicで殺せるという話。

big.Floatは6byte未満で死んで、big.Ratは5byte未満を流すと死んじゃう状態だったから、それぞれそのサイズを満たしてるかのチェックが入ったよう!

意図せぬpanicなのでCVEついたってことっすね。入力が信頼できない場合はチェックしないと範囲外のアクセスが発生するinputで殺されるので気をつけないとな。

httputil.ReverseProxy.ServeHTTPしたときにX-Forwarded-Forがnilだと自身のIPを送っちゃう

CVE: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-32148
パッチ: https://github.com/tailscale/go/commit/f54a01240d7caa391394ac68211c76347b0469a1

これは文脈あるので説明します。

ReverseProxy.ServeHTTPは元reqを受け取りcloneしたのち、そのcloneに対して向き先の変更など必要な加工をしたのち、clone reqにてhttpリクエストを実行するような挙動をします。
で、このときに元reqのremote_addrを勝手にX-Forwarded-Forに追加する仕様がありました。

それには嬉しい部分もあるんですが、リクエスト元のclient IPを漏らしたくないときにX-Forwarded-Forの追加をskip方法がなかったので困ってる人もいて、それじゃあskip手段を提供しようということで こういうパッチがマージされました

これはoutreq(元reqの加工済みclone)のX-Forwarded-Forがnilだったらskip扱いにして追加しない!とする制御です。ちょっと無理くりだし既存の挙動を壊しそうだけど、 既存の該当ケースだと X-Forwarded-For: , 192.0.2.0 となるパターンでどちらにせよ無効なので使っちゃおうということっぽい です。

で、これで晴れてclient IPを漏らしたくないときの対応が出来たと思ったそこのあなた!残念でした〜これだけだとまだ出来てません!というのがこのCVEの内容です。

チケット に詳しいですが、どうやらこれだけではX-Forwarded-Forを送り続けちゃうらしいです。
意図的にIPを漏らしたくない書き方をしてるユーザーのIPが漏れてるってことなのでCVE行きしました。

なんでうまくいかなかったかというと、http reqのclone実装が(というよりその内部で呼ばれてるheaderのclone実装が)今回のReverseProxy側のnilヘッダーに意味を見出す使い方を想定してなくて、値までちゃんと存在してはじめてHeaderを複製する挙動だったからです。nilだったらヘッダ複製を飛ばしてたので、ReverseProxy内部ではX-Forwarded-Forヘッダ自体存在しない状態で複製されてました。そうするとさっきのskip判定に引っかからずに X-Forwarded-For にclient IPを追記しちゃうよな、という流れです。

このパッチでなおってます。cloneするときに値がnilでもそれに依存したロジックを考慮してHeader複製すんで!という差分
https://github.com/tailscale/go/commit/f54a01240d7caa391394ac68211c76347b0469a1

これは結構面白くて、でかい規模のプロジェクトあるあるの「そんなところで影響するんか〜〜い」が観測できてけっこうよかったです。
なんというか大変だなぁと思った。

さいごに

ほかにも これ とか面白いCVEはまだあるんですが、今回はここまでにしときます!

いろいろ見た感じやっぱこまかい考慮漏れのバグみたいなのがあるので、ふとstandard library読んでたらCVE報告チャンスが爆誕しちゃった!なんてこともあるかもしれません。
暗号周りとかencoding周りとかは文脈わからないと見れないものもあるけど、そうじゃないCVEもたくさんあるので虎視眈々と狙いましょう。

また、Goチームが頑張っててsecurity fix提供してくれてるから、毎度こまめにバージョンはあげたほうが良さそうですね。

今回いろいろgo-announce確認しなおして感じたことは、fuzzingツール経由で発見された脆弱性がけっこうあるということ。OSSのfazzingツールが本体に貢献していてcoolですね。

最後に、検証して脆弱性報告あげる人たちめちゃめちゃ尊敬してます。Goは好きな言語だし、僕もいつかなにかで貢献したいな〜

Kyash Advent Calendar 2022 はまだまだ続きます!明日の投稿もおたのしみに!

あと、Kyashでは一緒にプロダクトを支える開発を手伝ってくれるメンバーを絶賛募集中です!ご興味あればぜひご連絡ください〜

https://herp.careers/v1/kyash