注意: 本エントリで紹介するツールは現時点でPoCな実装であり、効果や効率を保証するものではありません。
ちょっと前に社内でGitHub Actionsのサプライチェーン攻撃についての話題があがって、「なるほどー。今時は、リポジトリのコードだけの脆弱性や第三者コードの混入とかだけを気にしていても足りない時があるのか」いう感想でした。
いろいろな軽減策が提案されているので*1基本的にそれらを実践するが良いとする上で、「GitHub Actionsのサプライチェーンをたどって脆弱性スキャンとか危険なコード混入をチェックできたら意味あったりするかなあ」とふと思って、その「GitHub Actionsのサプライチェーンをたどる」というところに興味がでてきたのでツールとして作ってみました。
oshka
oshka*2の振る舞いは以下の通りです。
- 指定したディレクトリ(
fs
)や、リポジトリ(repo
)、アクション(action
)、Dockerイメージ(image
)をローカルのファイルシステムのテンポラリディレクトリに展開する。 - 1で展開したテンポラリディレクトリに対して指定のコマンド(デフォルトは
trivy fs . --exit-code 1
)を実行する。 - 展開したディレクトリからGitHub Actionsのサプライチェーンを解析して(
.github/workflows/*.yml
とかaction.yml
とか、action.ymlで使っているDockerfileとか)、構成しているDockerイメージやサードパーティActionを特定する。 - 3で特定したDockerイメージやサードパーティActionをローカルのファイルシステムのテンポラリディレクトリに展開する。
- 4で展開したテンポラリディレクトリに対して指定のコマンド(デフォルトは
trivy fs . --exit-code 1
)を実行する。 - 3-5を指定の深さ(
--depth
、デフォルトは1)繰り返す。 - 実行結果のサマリを出力する。
使い方
インストールは簡単でHomebrewだと
$ brew install k1LoW/tap/oshka
です。
デフォルトコマンドにTrivyを採用しているので合わせてTrivyのインストールもしてください。
実際に実行してみると面白くて「これがサプライチェーンかー」となります。
試しに oshka のリポジトリに対して実行してみます。
$ oshka run repo k1LoW/oshka 2021-09-02T07:26:34+09:00 [INFO] Create temporary directory for extracting supply chains: /var/folders/fp/hk95_wsj7s18mmc9drvrxdp1tt294n/T/ 2021-09-02T07:26:34+09:00 [INFO] Extract repo github.com/k1LoW/oshka to /var/folders/fp/hk95_wsj7s18mmc9drvrxdp1tt294n/T/github.com/k1LoW/oshka Enumerating objects: 131, done. Counting objects: 100% (131/131), done. Compressing objects: 100% (92/92), done. Total 131 (delta 41), reused 85 (delta 14), pack-reused 0 2021-09-02T07:26:35+09:00 [INFO] Run `trivy fs --exit-code 1 .` on /var/folders/fp/hk95_wsj7s18mmc9drvrxdp1tt294n/T/github.com/k1LoW/oshka 2021-09-02T07:26:35.864+0900 INFO Using your github token 2021-09-02T07:26:35.866+0900 INFO Need to update DB 2021-09-02T07:26:35.866+0900 INFO Downloading DB... 3.17 MiB / 23.43 MiB [------------------------>_______________________________________________________________________________________________________________________________________________________________] 13.55% ? p/s ?9.22 MiB / 23.43 MiB [------------------------------------------------------------------------>_______________________________________________________________________________________________________________] 39.36% ? p/s ?14.48 MiB / 23.43 MiB [----------------------------------------------------------------------------------------------------------------->_____________________________________________________________________] 61.80% ? p/s ?20.04 MiB / 23.43 MiB [------------------------------------------------------------------------------------------------------------------------------------------------->________________________] 85.53% 28.05 MiB p/s ETA 0s23.43 MiB / 23.43 MiB [-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------] 100.00% 33.59 MiB p/s 1s2021-09-02T07:26:37.914+0900 INFO Number of language-specific files: 1 2021-09-02T07:26:37.914+0900 INFO Detecting gomod vulnerabilities... go.sum (gomod) ============== Total: 18 (UNKNOWN: 3, LOW: 0, MEDIUM: 7, HIGH: 8, CRITICAL: 0) +------------------------------------+------------------+----------+-----------------------------------+---------------------------------------+-----------------------------------------+ | LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | +------------------------------------+------------------+----------+-----------------------------------+---------------------------------------+-----------------------------------------+ | github.com/apache/thrift | CVE-2019-0205 | HIGH | 0.12.0 | 0.13.0 | thrift: Endless loop when | | | | | | | feed with specific input data | | | | | | | -->avd.aquasec.com/nvd/cve-2019-0205 | + +------------------+ + + +-----------------------------------------+ | | CVE-2019-0210 | | | | thrift: Out-of-bounds read | | | | | | | related to TJSONProtocol | | | | | | | or TSimpleJSONProtocol | | | | | | | -->avd.aquasec.com/nvd/cve-2019-0210 | + +------------------+ + +---------------------------------------+-----------------------------------------+ | | CVE-2020-13949 | | | v0.14.0 | libthrift: potential DoS when | | | | | | | processing untrusted payloads | | | | | | | -->avd.aquasec.com/nvd/cve-2020-13949 | +------------------------------------+------------------+ +-----------------------------------+---------------------------------------+-----------------------------------------+ | github.com/buger/jsonparser | CVE-2020-10675 | | 0.0.0-20180808090653-f4dd9f5a6b44 | v0.0.0-20200321185410-91ac96899e49 | golang-github-buger-jsonparser: | | | | | | | infinite loop via a Delete call | | | | | | | -->avd.aquasec.com/nvd/cve-2020-10675 | + +------------------+ + +---------------------------------------+-----------------------------------------+ | | CVE-2020-35381 | | | v1.1.1 | jsonparser: GET call can lead to | | | | | | | a slice bounds out of range... | | | | | | | -->avd.aquasec.com/nvd/cve-2020-35381 | +------------------------------------+------------------+----------+-----------------------------------+---------------------------------------+-----------------------------------------+ | github.com/containerd/containerd | CVE-2021-32760 | MEDIUM | 1.5.3 | v1.4.8, v1.5.4 | containerd: pulling and | | | | | | | extracting crafted container | | | | | | | image may result in Unix file... | | | | | | | -->avd.aquasec.com/nvd/cve-2021-32760 | +------------------------------------+------------------+----------+-----------------------------------+---------------------------------------+-----------------------------------------+ | github.com/dgrijalva/jwt-go | CVE-2020-26160 | HIGH | 3.2.0+incompatible | | jwt-go: access restriction | | | | | | | bypass vulnerability | | | | | | | -->avd.aquasec.com/nvd/cve-2020-26160 | +------------------------------------+------------------+----------+-----------------------------------+---------------------------------------+-----------------------------------------+ | github.com/gorilla/handlers | GO-2020-0020 | UNKNOWN | 0.0.0-20150720190736-60c7bfde3e33 | v1.3.0 | | +------------------------------------+------------------+----------+-----------------------------------+---------------------------------------+-----------------------------------------+ | github.com/miekg/dns | CVE-2019-19794 | MEDIUM | 1.0.14 | v1.1.25-0.20191211073109-8ebf2e419df7 | golang-github-miekg-dns: predictable | | | | | | | TXID can lead to response forgeries | | | | | | | -->avd.aquasec.com/nvd/cve-2019-19794 | +------------------------------------+------------------+----------+-----------------------------------+---------------------------------------+-----------------------------------------+ | github.com/sassoftware/go-rpmutils | CVE-2020-7667 | HIGH | 0.0.0-20190420191620-a8f1baeba37b | v0.1.0 | In package | | | | | | | github.com/sassoftware/go-rpmutils/cpio | | | | | | | before version 0.1.0, the | | | | | | | CPIO extraction functionality | | | | | | | doesn't sanitize... | | | | | | | -->avd.aquasec.com/nvd/cve-2020-7667 | +------------------------------------+------------------+----------+-----------------------------------+---------------------------------------+-----------------------------------------+ | github.com/satori/go.uuid | GO-2020-0018 | UNKNOWN | 1.2.0 | v1.2.1-0.20181016170032-d91630c85102 | | +------------------------------------+------------------+----------+-----------------------------------+---------------------------------------+-----------------------------------------+ | github.com/ulikunitz/xz | CVE-2021-29482 | HIGH | 0.5.7 | v0.5.8 | ulikunitz/xz: Infinite | | | | | | | loop in readUvarint allows | | | | | | | for denial of service | | | | | | | -->avd.aquasec.com/nvd/cve-2021-29482 | + +------------------+----------+ + +-----------------------------------------+ | | GO-2020-0016 | UNKNOWN | | | | +------------------------------------+------------------+----------+-----------------------------------+---------------------------------------+-----------------------------------------+ | k8s.io/kubernetes | CVE-2019-1002101 | MEDIUM | 1.13.0 | 1.11.9, 1.12.7, 1.13.5, | kubernetes: Mishandling of | | | | | | 1.14.1-beta.0 | symlinks allows for arbitrary | | | | | | | file write via `kubectl cp`... | | | | | | | -->avd.aquasec.com/nvd/cve-2019-1002101 | + +------------------+ + +---------------------------------------+-----------------------------------------+ | | CVE-2019-11250 | | | v1.16.0-beta.1 | kubernetes: Bearer tokens | | | | | | | written to logs at high | | | | | | | verbosity levels (>= 7)... | | | | | | | -->avd.aquasec.com/nvd/cve-2019-11250 | + +------------------+ + +---------------------------------------+-----------------------------------------+ | | CVE-2020-8554 | | | | kubernetes: MITM using | | | | | | | LoadBalancer or ExternalIPs | | | | | | | -->avd.aquasec.com/nvd/cve-2020-8554 | + +------------------+ + +---------------------------------------+-----------------------------------------+ | | CVE-2020-8564 | | | v1.20.0-alpha.1 | kubernetes: Docker config | | | | | | | secrets leaked when file is | | | | | | | malformed and loglevel >=... | | | | | | | -->avd.aquasec.com/nvd/cve-2020-8564 | + +------------------+ + +---------------------------------------+-----------------------------------------+ | | CVE-2020-8565 | | | v1.20.0-alpha.2 | kubernetes: Incomplete fix | | | | | | | for CVE-2019-11250 allows for | | | | | | | token leak in logs when... | | | | | | | -->avd.aquasec.com/nvd/cve-2020-8565 | +------------------------------------+------------------+----------+-----------------------------------+---------------------------------------+-----------------------------------------+ 2021-09-02T07:26:37+09:00 [INFO] Detect action actions/setup-go@v2 from /var/folders/fp/hk95_wsj7s18mmc9drvrxdp1tt294n/T/github.com/k1LoW/oshka 2021-09-02T07:26:37+09:00 [INFO] Detect action actions/checkout@v2 from /var/folders/fp/hk95_wsj7s18mmc9drvrxdp1tt294n/T/github.com/k1LoW/oshka 2021-09-02T07:26:37+09:00 [INFO] Detect action golangci/golangci-lint-action@v2 from /var/folders/fp/hk95_wsj7s18mmc9drvrxdp1tt294n/T/github.com/k1LoW/oshka 2021-09-02T07:26:37+09:00 [INFO] Extract action actions/checkout@v2 to /var/folders/fp/hk95_wsj7s18mmc9drvrxdp1tt294n/T/actions/checkout@v2 Enumerating objects: 997, done. Counting objects: 100% (27/27), done. Compressing objects: 100% (20/20), done. Total 997 (delta 10), reused 11 (delta 6), pack-reused 970 2021-09-02T07:26:39+09:00 [INFO] Run `trivy fs --exit-code 1 .` on /var/folders/fp/hk95_wsj7s18mmc9drvrxdp1tt294n/T/actions/checkout@v2 2021-09-02T07:26:39.114+0900 INFO Using your github token 2021-09-02T07:26:39.177+0900 INFO Number of language-specific files: 1 2021-09-02T07:26:39.177+0900 INFO Detecting npm vulnerabilities... package-lock.json (npm) ======================= Total: 3 (UNKNOWN: 0, LOW: 0, MEDIUM: 2, HIGH: 1, CRITICAL: 0) +---------------+------------------+----------+-------------------+---------------------+---------------------------------------+ | LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | +---------------+------------------+----------+-------------------+---------------------+---------------------------------------+ | @actions/core | CVE-2020-15228 | MEDIUM | 1.1.3 | 1.2.6 | Environment Variable | | | | | | | Injection in GitHub Actions | | | | | | | -->avd.aquasec.com/nvd/cve-2020-15228 | +---------------+------------------+ +-------------------+---------------------+---------------------------------------+ | node-fetch | CVE-2020-15168 | | 2.6.0 | 3.0.0-beta.9, 2.6.1 | node-fetch: size of data after | | | | | | | fetch() JS thread leads to DoS | | | | | | | -->avd.aquasec.com/nvd/cve-2020-15168 | +---------------+------------------+----------+-------------------+---------------------+---------------------------------------+ | underscore | CVE-2021-23358 | HIGH | 1.8.3 | 1.12.1 | nodejs-underscore: Arbitrary code | | | | | | | execution via the template function | | | | | | | -->avd.aquasec.com/nvd/cve-2021-23358 | +---------------+------------------+----------+-------------------+---------------------+---------------------------------------+ 2021-09-02T07:26:39+09:00 [INFO] Extract action golangci/golangci-lint-action@v2 to /var/folders/fp/hk95_wsj7s18mmc9drvrxdp1tt294n/T/golangci/golangci-lint-action@v2 Enumerating objects: 1342, done. Counting objects: 100% (431/431), done. Compressing objects: 100% (237/237), done. Total 1342 (delta 329), reused 272 (delta 192), pack-reused 911 2021-09-02T07:26:40+09:00 [INFO] Run `trivy fs --exit-code 1 .` on /var/folders/fp/hk95_wsj7s18mmc9drvrxdp1tt294n/T/golangci/golangci-lint-action@v2 2021-09-02T07:26:40.707+0900 INFO Using your github token 2021-09-02T07:26:40.764+0900 INFO Number of language-specific files: 2 2021-09-02T07:26:40.764+0900 INFO Detecting gomod vulnerabilities... 2021-09-02T07:26:40.765+0900 INFO Detecting npm vulnerabilities... package-lock.json (npm) ======================= Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) sample-go-mod/go.sum (gomod) ============================ Total: 4 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 3, CRITICAL: 0) +-----------------------------+------------------+----------+-----------------------------------+---------------------------------------+---------------------------------------+ | LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | +-----------------------------+------------------+----------+-----------------------------------+---------------------------------------+---------------------------------------+ | github.com/dgrijalva/jwt-go | CVE-2020-26160 | HIGH | 3.2.0+incompatible | | jwt-go: access restriction | | | | | | | bypass vulnerability | | | | | | | -->avd.aquasec.com/nvd/cve-2020-26160 | +-----------------------------+------------------+ +-----------------------------------+---------------------------------------+---------------------------------------+ | github.com/gogo/protobuf | CVE-2021-3121 | | 1.2.1 | v1.3.2 | gogo/protobuf: | | | | | | | plugin/unmarshal/unmarshal.go | | | | | | | lacks certain index validation | | | | | | | -->avd.aquasec.com/nvd/cve-2021-3121 | +-----------------------------+------------------+----------+-----------------------------------+---------------------------------------+---------------------------------------+ | github.com/miekg/dns | CVE-2019-19794 | MEDIUM | 1.0.14 | v1.1.25-0.20191211073109-8ebf2e419df7 | golang-github-miekg-dns: predictable | | | | | | | TXID can lead to response forgeries | | | | | | | -->avd.aquasec.com/nvd/cve-2019-19794 | +-----------------------------+------------------+----------+-----------------------------------+---------------------------------------+---------------------------------------+ | golang.org/x/crypto | CVE-2020-29652 | HIGH | 0.0.0-20200622213623-75b288015ac9 | v0.0.0-20201216223049-8b5274cf687f | golang: crypto/ssh: crafted | | | | | | | authentication request can | | | | | | | lead to nil pointer dereference | | | | | | | -->avd.aquasec.com/nvd/cve-2020-29652 | +-----------------------------+------------------+----------+-----------------------------------+---------------------------------------+---------------------------------------+ 2021-09-02T07:26:40+09:00 [INFO] Extract action actions/setup-go@v2 to /var/folders/fp/hk95_wsj7s18mmc9drvrxdp1tt294n/T/actions/setup-go@v2 Enumerating objects: 1043, done. Counting objects: 100% (39/39), done. Compressing objects: 100% (31/31), done. Total 1043 (delta 13), reused 19 (delta 3), pack-reused 1004 2021-09-02T07:26:41+09:00 [INFO] Run `trivy fs --exit-code 1 .` on /var/folders/fp/hk95_wsj7s18mmc9drvrxdp1tt294n/T/actions/setup-go@v2 2021-09-02T07:26:41.822+0900 INFO Using your github token 2021-09-02T07:26:41.883+0900 INFO Number of language-specific files: 1 2021-09-02T07:26:41.883+0900 INFO Detecting npm vulnerabilities... package-lock.json (npm) ======================= Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) 2021-09-02T07:26:41+09:00 [INFO] Cleanup temporary directory for extracting supply chains: /var/folders/fp/hk95_wsj7s18mmc9drvrxdp1tt294n/T/ Run results =========== +----------------------------------+--------+--------------------------+-----------+-------+------------------------------------------+ | NAME | TYPE | COMMAND | EXIT CODE | DEPTH | HASH | +----------------------------------+--------+--------------------------+-----------+-------+------------------------------------------+ | github.com/k1LoW/oshka | repo | trivy fs --exit-code 1 . | 1 | 0 | ca36be55f7fcfbc31cc7dc5e80b6fb7d01c53981 | | | | | | | (commit hash) | | actions/setup-go@v2 | action | trivy fs --exit-code 1 . | 0 | 1 | 331ce1d993939866bb63c32c6cbbfd48fa76fc57 | | | | | | | (commit hash) | | actions/checkout@v2 | action | trivy fs --exit-code 1 . | 1 | 1 | 5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f | | | | | | | (commit hash) | | golangci/golangci-lint-action@v2 | action | trivy fs --exit-code 1 . | 1 | 1 | 5c56cd6c9dc07901af25baab6f2b0d9f3b7c3018 | | | | | | | (commit hash) | +----------------------------------+--------+--------------------------+-----------+-------+------------------------------------------+ $
--depth 2
にすると、さらに深くサプライチェーンをたどります。
$ oshka run repo k1LoW/oshka --depth 2 [...] Run results =========== +----------------------------------+--------+--------------------------+-----------+-------+-------------------------------------------------------------------------+ | NAME | TYPE | COMMAND | EXIT CODE | DEPTH | HASH | +----------------------------------+--------+--------------------------+-----------+-------+-------------------------------------------------------------------------+ | github.com/k1LoW/oshka | repo | trivy fs --exit-code 1 . | 1 | 0 | ca36be55f7fcfbc31cc7dc5e80b6fb7d01c53981 | | | | | | | (commit hash) | | actions/setup-go@v2 | action | trivy fs --exit-code 1 . | 0 | 1 | 331ce1d993939866bb63c32c6cbbfd48fa76fc57 | | | | | | | (commit hash) | | actions/checkout@v2 | action | trivy fs --exit-code 1 . | 1 | 1, 2 | 5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f | | | | | | | (commit hash) | | golangci/golangci-lint-action@v2 | action | trivy fs --exit-code 1 . | 1 | 1 | 5c56cd6c9dc07901af25baab6f2b0d9f3b7c3018 | | | | | | | (commit hash) | | github/codeql-action/init@v1 | action | trivy fs --exit-code 1 . | 1 | 2 | 33f3438c1d59883f5e769fdf2b6adb6794d91d0f | | | | | | | (commit hash) | | github/codeql-action/analyze@v1 | action | trivy fs --exit-code 1 . | 1 | 2 | 33f3438c1d59883f5e769fdf2b6adb6794d91d0f | | | | | | | (commit hash) | | actions/publish-action@v0.1.0 | action | trivy fs --exit-code 1 . | 0 | 2 | 63abd0d521e21329399edb30e8f577a7ab85443c | | | | | | | (commit hash) | | actions/setup-node@v1 | action | trivy fs --exit-code 1 . | 1 | 2 | f1f314fca9dfce2769ece7d933488f076716723e | | | | | | | (commit hash) | | alpine/git:latest | image | trivy fs --exit-code 1 . | 1 | 2 | sha256:70f7b70d9324314642b324ac79655862656255dd71b4d18357e5a22f76215ade | | | | | | | (digest) | | datadog/squid:latest | image | trivy fs --exit-code 1 . | 1 | 2 | sha256:f7d19d5e3f4163771291d91de393ce667f2327a3e080c39b9b7ea9e19f91488f | | | | | | | (digest) | +----------------------------------+--------+--------------------------+-----------+-------+-------------------------------------------------------------------------+
デフォルトコマンドは trivy fs --exit-code 1 .
ですが、全てローカルのファイルシステムのテンポラリディレクトリに展開する実装になっているので、ローカルにインストールされているコマンドならなんでも実行できるはずです。複数コマンドも以下のように指定可能です。
$ oshka run repo k1LoW/tbls -c 'command1' -c 'command2' -c ...
起点とできるのはリポジトリ( repo
)以外にも、ディレクトリ( fs
)やアクション( action
)、Dockerイメージ( image
)が指定可能です。コマンドの作りはTrivyを参考にしています。
$ oshka run fs .
$ oshka run action actions/cache@v2
$ oshka run image nginx:alpine
というわけで
GitHub Actionsのサプライチェーンをたどって任意のコマンドを実行できるツール oshka を作ってみたというお話でした。
感想としては、
- これがサプライチェーンか
- Trivyすごい。いろいろスキャンしてくれる。
- ついでにサードパーティアクションがcommit SHAでピン留めされているか確認する機能を追加すると良さそう
- ついでにサードパーティアクションでリポジトリリダイレクト*3が発生しているか確認すする機能を追加すると良さそう
- こういうサプライチェーン攻撃に対する何かしらの機能にニーズがあるならどこかのセキュリティツールが実装してくれたらいいなあ
という感じです。
以上、夏休みの自由研究発表でした。