yamory Blog

ライブラリの導入、それが脆弱性の始まり

こんにちは、Dependency Hell Survivor のけんです。

ビジネスにスピードが求められる昨今、開発を効率化するために、適切なライブラリを導入して開発に臨まれる方は多いのではないでしょうか?

しかしながら、ライブラリを導入するということは、そのライブラリが依存する他のライブラリ、さらにはそれらの脆弱性までも引き込んでしまう可能性があります。

今回は、便利だけれども時に厄介なライブラリと、その依存関係(Dependency)における脆弱性について Gradle を使って例をあげながらお話いたします。

ライブラリの追加に何か問題でも?

あるとき、JSON で通信をする要件が出てきたとします。

HTTP 通信をしつつ、JSON 形式でデータをやり取りできるライブラリが欲しいので、Kotlin で人気の Fuel を導入しましょう。
データフォーマットは JSON なので、合わせて fuel-jakcson を導入してみます。

build.gradle は次のようになりました。
fuel を取得するため repositoriesjcenter() を追加しています。

repositories {
    mavenCentral()
    jcenter()
}
dependencies {
  implementation group: 'com.github.kittinunf.fuel', name: 'fuel', version: '2.1.0'
  implementation group: 'com.github.kittinunf.fuel', name: 'fuel-jackson', version: '2.1.0'
}

出来上がったコードはこちらです。うまく通信できたようです。
(サンプルを実際に動かしたい方は、Swagger や json-server 等で API モックを作成すると良いでしょう)

import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.jackson.responseObject
import com.github.kittinunf.result.Result

data class Person(
    val firstName: String,
    val lastName: String
)

fun main() {
    fun getPerson(personId: Int): Person {
        val responseObject =
            Fuel.get("http://localhost:8080/persons/$personId")
                .responseObject<Person>()

        return when (responseObject.third) {
            is Result.Success<*> -> responseObject.third.get()
            else -> throw RuntimeException(responseObject.second.responseMessage)
        }
    }

    val person = getPerson(1)
    println(person) // => Person(firstName=John, lastName=Smith)
}

依存関係を確認しよう

作成したプログラムが動いたからといって安心はできません。
さきほど追加した依存ライブラリをよく確認してみましょう。

Gradle の場合は dependencies タスクを実行することで、依存関係を確認することができます。

Gradleコマンド:

gradle :dependencies --configuration=default

依存関係:

default - Configuration for default artifacts.
+--- com.github.kittinunf.fuel:fuel:2.1.0
|    +--- com.github.kittinunf.result:result:2.2.0
|    |    \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.30 -> 1.3.72
|    |         +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72
|    |         \--- org.jetbrains:annotations:13.0
|    \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.30 -> 1.3.72 (*)
+--- com.github.kittinunf.fuel:fuel-jackson:2.1.0
|    +--- com.github.kittinunf.fuel:fuel:2.1.0 (*)
|    +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.30 -> 1.3.72 (*)
|    \--- com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8
|         +--- com.fasterxml.jackson.core:jackson-databind:2.9.8
|         |    +--- com.fasterxml.jackson.core:jackson-annotations:2.9.0
|         |    \--- com.fasterxml.jackson.core:jackson-core:2.9.8
|         +--- com.fasterxml.jackson.core:jackson-annotations:2.9.0
|         \--- org.jetbrains.kotlin:kotlin-reflect:1.3.10
|              \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.10 -> 1.3.72 (*)
...

たった2つライブラリ( fuel, fuel-jackson )を追加しただけなのに、実際にはさらに多くのライブラリが追加されました。
このようにして導入されたライブラリは推移的依存(あるいは間接依存)ライブラリ、さきほど明示的に追加したライブラリは直接依存ライブラリと呼ばれます。
推移的依存とは、簡単に言えばある依存ライブラリがさらに別のライブラリに依存する関係です。

さて、これらの推移的依存ライブラリは今回のプログラムでは直接使用していません。
推移的依存のライブラリは無視してよいのでしょうか?
それとも、推移的依存ライブラリまで確認してから直接依存のライブラリを導入するべきなのでしょうか?

ライブラリのサポートがある場合や別チーム等にてメンテナンスされている場合を除いて、基本的にはライブラリを導入した側でその推移的依存も引き受けることになるでしょう。

今回の直接導入した2つのライブラリならいざしらず、これらすべてのライブラリを1つ1つ確認していくのは骨が折れるため、依存関係全体をスキャンできるツールを導入して確認したほうがよいでしょう。

yamory で依存関係をまるごとスキャン

yamory が提供するコマンドラインスキャンでは Java 系プロジェクトの依存関係をまるごとスキャンし、依存関係の中にあるライブラリの脆弱性を検知することができます。

yamory を使ってさきほどの依存関係をスキャンしてみましょう。

yamory ではスキャンした依存関係をツリー形式で確認することができます。
加えて、依存関係のどの部分にどれぐらいの脆弱性があるかも識別可能となっています。

Closed dependency tree

ツリーのノードの外側にある数字は、そのノードから下の脆弱性の総数を示しています。
I: Immediate, D: Delayed。Immediate, Delayed は対応優先度を示します。)

どうやら fuel-jackson の依存関係に脆弱性が潜んでいるようです!
ツリーを展開していきましょう。

Opended dependency tree

ツリーのいくつかのノードを展開した状態が上図です。
直接依存になっているライブラリは色付きで表示されるため、識別しやすくなっています。

ツリーを展開していくと、推移的依存のライブラリに脆弱性があることがわかります。

ノードの内側にある数字は、そのノード(ライブラリ)の脆弱性件数を示しています。
依存先の jackson-databindkotlin-reflect にそれぞれ脆弱性がありました。
攻撃コードが出回っていて、推移的依存の脆弱性を経由してアプリが攻撃されないとも限りませんので、対応したいところです。

yamory の依存ツリーでは、脆弱性のあるノードをクリックすると脆弱性を確認できます。
依存ツリーを表示したまま脆弱性を確認できるため、依存関係の位置を確認しながら脆弱性を調べることができます。

Tree and Vulnerabilities

なお、依存関係のうち脆弱性のある箇所にのみ着目したい場合は、下図のように「危険な脆弱性のみ表示」をONにすることでフィルタすることができます。

Tree vulnerabilities filetered

このように、yamory では依存関係とその中にある脆弱性を把握することができます。

推移的(間接)依存のライブラリ (Transitive Dependency)

推移的に依存されるライブラリは、直接管理するライブラリではありません。
そのため推移的依存ライブラリのアップデートは、時として困難を伴います。

さきほどの jackson-databind はバージョン 2.9.8 でした。
jackson-databind に着目するために、yamory でスキャンした結果をキーワードでフィルタしてみましょう。

Tree keyword filetered

この図をみると、 fuel-jackson:2.1.0 では jackson-module-kotlin2.9.8 を前提にして作られており、 jackson-module-kotlin:2.9.8jackson-databind2.9.8 を前提にしている、ということが伺えます。

したがって、安易に依存ツリーの下層にある推移的依存のライブラリをバージョンアップしてしまうと、これらの前提が崩れてしまい、意図しない挙動を引き起こしかねません。

理想を言えば、依存関係の前提を壊さず、目的の依存ライブラリをバージョンアップしたいところです。そのためには推移的依存関係全体をアップデートする必要があります。

しかし、目的の推移的依存ライブラリが、他の依存関係からも参照されたり、依存元のライブラリが望んだバージョンに依存していないような場合、対応は困難になります。

見落としがちな推移的(間接)依存

よかれと思って導入したライブラリでも、思わぬところで問題が発生します。
そして推移的依存のライブラリに脆弱性が発生した場合、その対応は困難になる場合があることをみてきました。

このような事態に備えて柔軟に対応するためには、常に依存関係を確認し、依存関係のどこに脆弱性があるのかを識別して依存関係それ自体を見直していかなければなりません。
依存関係の脆弱性を手動で1つ1つ確認することは時間が掛かり非現実的ですが、yamory では依存ツリーの位置関係を見ながら脆弱性を確認できるため、容易に脆弱性を把握できます。

依存関係の問題はなかなか理解されない側面があると感じますが、依存関係は時として根深い問題に発展し、ビジネスの成長を妨げたり、セキュリティ上の問題を引き起こしたりします。
次の機会では依存関係の話をさらに掘り下げて、対処方法のパターンをいくつかみていきたいと思います。

健全なソフトウェアは健全な依存関係に宿ります。
早速 yamory で依存関係をスキャン しましょう。

オープンソース脆弱性管理ツール yamory

  • 利用中のOSSを抽出し
    脆弱性を自動スキャン
  • 脆弱性への対応優先度を自動で分類
  • 組織規模に合わせたプランを選択可能

フリー

¥0

個人向け

1チームまでの開発チーム作成

yamory を使いはじめる

プロ

有料料金はお問い合わせください

中〜大規模組織向け

無制限の開発チーム作成

開発チームを俯瞰できるセキュリティチーム機能

危険な脆弱性の Slack 連携通知機能

Jira Cloud と連携

30日間の無料トライアルを開始