Javaの利用ライブラリの脆弱性を調査する

あるJavaのプロジェクトについて利用ライブラリの脆弱性を調べる必要がある。
Maven Central RepositoryではAPIによる検索が可能らしいので、こちらを元に調査してみる。

まずは元となるライブラリ一覧。
pom.xmlを見てみると、コード内に配置されたjarを参照しているっぽい。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<dependencies>
<dependency>
<groupId>jp.co.xxx.xxx.xxx</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${basedir}/lib/aopalliance-1.0.jar</systemPath>
</dependency>
<dependency>
<groupId>jp.co.xxx.xxx.xxx</groupId>
<artifactId>asm</artifactId>
<version>7.3.1</version>
<scope>system</scope>
<systemPath>${basedir}/lib/asm-7.3.1.jar</systemPath>
</dependency>
...
問題はgroupId
のとこで、本来はライブラリ自体のGroupIDが入ってくるのだけど、すべて開発元の会社名が入ってしまっている。つまりaopalliance
という同名のライブラリが複数存在した場合に、どのaopalliance
なのかpom.xmlだけでは判断できない可能性がある。
確認したい場合はjar
ファイルを回答して中を見に行く必要がある。
とても面倒なのでいったんこの問題は放置する。

次にpomファイルをパースしてREST APIを呼び出す際に必要な情報を取得してみる。
JavaScriptしかまともに書けないマンなので、JSでスクリプトを書いてみよう。
import fs from 'fs'
import { XMLParser } from 'fast-xml-parser'
const pom = fs.readFileSync('pom.xml', 'utf-8')
const parser = new XMLParser();
const parsedPom = parser.parse(pom)
const dependencies = parsedPom.project.dependencies.dependency
const formattedDeps = dependencies.map(x => ({
artifactId: x.artifactId,
version: String(x.version),
}))
console.log(formattedDeps)
これで以下のような結果が取れるようになった。とりあえずOK。
% node maven_checker.mjs
[
{ artifactId: 'aopalliance', version: '1' },
{ artifactId: 'asm', version: '7.3.1' },
...
]

次に、Maven Central RepositoryのAPIの呼び出し方を理解したい。
取得したい項目は以下。
- 最新バージョン
- 最新バージョンの更新日
- 最新バージョンに報告されている脆弱性
ここにまとまっている。
以下で、今までとれているartifactIdとversionで検索できるっぽい。
> curl "https://search.maven.org/solrsearch/select?q:a=${artifactId}+AND+v:${version}"
戻り値は以下。
複数件返ってくるのもや0件の場合もあるっぽいが、まあいいや。
これで戻りが1件の場合はgroupIdも取得できるようになった。
{
"responseHeader": {
"status": 0,
"QTime": 2,
"params": {
"q": "a:aopalliance AND v:1.0",
"core": "",
"indent": "off",
"fl": "id,g,a,v,p,ec,timestamp,tags",
"start": "",
"sort": "score desc,timestamp desc,g asc,a asc,v desc",
"rows": "20",
"wt": "json",
"version": "2.2"
}
},
"response": {
"numFound": 1,
"start": 0,
"docs": [
{
"id": "aopalliance:aopalliance:1.0",
"g": "aopalliance",
"a": "aopalliance",
"v": "1.0",
"p": "jar",
"timestamp": 1451280333000,
"ec": [
"-sources.jar",
".jar",
".pom"
],
"tags": [
"alliance"
]
}
]
}
}

いよいよ最新バージョンと脆弱性報告数をとりたい。
と思ったらMaven Central RepositoryのAPIでは脆弱性報告数のような値はとれないらしい!
詰んだと思ったら、ライブラリの脆弱性を取得するツールがあるらしい。
神!初めて知った。
Macではbrewでインストールできる。
インストール後に、以下のようなコマンドでHTLMのレポートを作成してくれた。
> dependency-check --out . --scan "lib/*.jar"
なおフォーマットも指定できる。
JSONだと結果のサマリがなくHTMLのほうが見やすい(見方が悪いだけかも)
> dependency-check --out . --scan "lib/*.jar" --format JSON
レポートを整形して、以下のようなCSVを作成。
なんと最初に問題だったGroupIDもとれているっぽい。すごいぞ!
Package,Highest Severity,CVE Count
pkg:maven/org.postgresql/postgresql@42.2.16,CRITICAL,4
pkg:maven/org.apache.xmlgraphics/batik-bridge@1.7,CRITICAL,12
このCSVを元に、Maven Central RepositoryのAPIを呼び出して最新バージョンと最新バージョンの更新日を取得できれば良さそう。
最終的に書いた雑コードは以下。
import fs from 'fs'
import { parse } from 'csv-parse/sync'
import { Parser } from '@json2csv/plainjs';
const csv = fs.readFileSync('cve.csv', 'utf-8')
const packages = parse(csv, {
columns: true
})
const summary = []
for (const p of packages) {
const [_, groupId, artifactIdAndVersion] = p.Package.split('/')
const [artifactId, version] = artifactIdAndVersion.split('@')
const url = `https://search.maven.org/solrsearch/select?q=g:${groupId}+AND+a:${artifactId}`
const resp = await fetch(url)
const json = await resp.json()
const detail = json.response.docs[0]
const ts = detail ? new Date(detail.timestamp) : null
const item = {
name: p.Package,
id: detail?.id,
version,
latestVersion: detail?.latestVersion,
latestVersionTimestamp: ts ? `${ts.getFullYear()}-${String(ts.getMonth()+1).padStart(2, '0')}-${String(ts.getDate()).padStart(2, '0')}` : "",
highestSeverity: p.HighestSeverity,
cveCount: p.CVECount,
}
console.log(item)
summary.push(item)
}
const parser = new Parser({
delimiter: '\t'
});
const listCsv = parser.parse(summary);
fs.writeFileSync('output.csv', listCsv)
出力されるCSV(TSV)はこんな感じ。
name" "id" "version" "latestVersion" "latestVersionTimestamp" "highestSeverity" "cveCount"
"pkg:maven/aopalliance/aopalliance@1.0" "aopalliance:aopalliance" "1.0" "1.0" "2015-12-28" "" "0"
"pkg:maven/org.ow2.asm/asm@7.3.1" "org.ow2.asm:asm" "7.3.1" "9.6" "2023-09-30" "" "0"