Javaコード監査で発見するSSRF脆弱性:CNVD実践記録

コード監査において、SSRF(Server-Side Request Forgery)脆弱性を発見する際には、まず以下のようなキーワードを検索してCMS内でSSRFが利用されている箇所を洗い出す。

/**
 * 監査対象の関数
 * 1. URL
 * 2. HttpClient
 * 3. OkHttpClient
 * 4. HttpURLConnection
 * 5. Socket
 * 6. ImageIO
 * 7. DriverManager.getConnection
 * 8. SimpleDriverDataSource.getConnection
 */

OkHttpClientを検索したところ、OkHttp3をラップしたユーティリティクラスが存在しているのを発見。ただし、このクラスはHTTPプロトコルのみを主にサポートしている。その後、変数urlが制御可能かどうかを確認するために呼び出し元をたどっていく。

ここでは脆弱性を発見した一連の流れのみを記録する。実際の監査ではすべての候補を調査する必要があるが、今回はfetchRemoteFileの呼び出しを追跡した。

最終的にルーティングの箇所に到達し、悪用の可能性があることが判明した。権限チェックの詳細は省くが、このインターフェースは認証なしでアクセス可能であった。

コードのロジックを見ると、typeパラメータを取得し、isExternalUrlで外部URLかどうかをチェックしている。外部URLの場合はエラーを返す。

チェック処理は単純で、URLがhttp://またはhttps://で始まるかどうかを判定している。これがフィルタリングの実態である。

チェックを通過すると、URLの後ろに/index.jsonを連結してパラメータとして渡す。以降のコードを見てみよう。

public static JSON fetchRemoteJson(String fileUrl) throws RebuildException {
    String content = fetchRemoteFile(fileUrl);
    if (JSONUtils.wellFormat(content)) {
        return (JSON) JSON.parse(content);
    }
    throw new RebuildException("Unable to read data from RB-Store");
}

次にfetchRemoteFileを見る。このメソッドでは、fileUrlhttpで始まるかどうかを判定し、始まる場合はそのままgetで内容を取得する。httpで始まらない場合は、無関係なURLを連結してしまい悪用できない。しかし、前述の外部URLチェックは既に通っている。

しばらく検討した結果、ここで判定しているのはhttp(コロンなし)であるのに対し、前述のチェックはhttp://https://(コロン2つ+スラッシュ)であることに気づいた。つまり、前方のチェックと後方の判定に不整合があり(多くの脆弱性の原理)、バイパスの可能性がある。そこでhttp:/https:/(コロン1つ+スラッシュ)を試してみることにした。

テストクラスを作成して確認したところ、可能であった。OkHttp3ライブラリはhttp:/https:/をパースできる。

さらに、URLの後ろに/index.jsonを連結される問題に対処する必要がある。様々な方法があるが、ここではパラメータを使って後続の/index.jsonを打ち消し、無効なパラメータとして送り込む手法を選んだ。

例:http://10.82.189.40:18080/setup/load-index?type=http:/127.0.0.1:80/2.txt?x=

このリクエストにより、攻撃者のサーバに置いたファイルの内容を取得できた。ただし、その後JSON解析が行われるため、JSON形式のコンテンツのみがレスポンスに反映される。すなわち、結果は非表示のSSRF(No-echo SSRF)となる。任意のHTTPリソースにアクセスさせることは可能だが、結果が返ってこないため、被害の程度は低いと言える。とはいえ、実際の監査で得られた貴重な経験である。

タグ: Java code-audit SSRF okhttp3 Security

5月22日 08:45 投稿