なまえは まだ ない

思いついたことをアウトプットします

Pull Request Title Injection とその対策

2020/04/05 12:30 その後の顛末を追記しました。

この記事の続きです。

furusax0621.hatenablog.com

GitHub上でマージした変更内容をSlackに通知するGitHub Actionsを実装して意気揚々としていたところ、次のようなツッコミをもらったところまで書きました。

そういえばこれって Pull Request Title Injection できないですかね? まあ、タイトル書くの社員なのでいいんですが。

Pull Request Title Injectionについて

Pull Request Title Injection とは

そもそもPull Request Title Injectionとはどういうことでしょう?何を指すのか? 簡単に言うと、Pull Requestのタイトルに不正な文字列を設定することで、このGitHub Actionsを通じて任意のメッセージをSlackに通知できる(更に、任意のコマンドを実行できてしまうかもしれない)攻撃方法です。 例えば、前回の記事で紹介したActionsが設定されたリポジトリに対して、次のようなタイトルのPull Requestを作成します。

Test Pull Request Title Injection", footer: ":male-police-officer: Injection!! :male-police-officer:

このPull Requestを閉じた時、Slackには以下のようなメッセージが投稿されます。

f:id:furusax0621:20200331212806p:plain

おわかりでしょうか?Actionsでは pretexttitletitle_linkしか設定していないにも関わらず、メッセージのfooterに文字列が挿入されています。一体どういうことでしょうか?

Injection はなぜ発生したのか?

ここでもう一度Actionsの設定を見てみましょう。スコープをわかりやすくするため、jqコマンドに渡しているJSONの部分のみ切り出します。

{
    attachments: [{
        pretext: "Swagger が更新されたよ!",
        color: "good",
        title: "${{ github.event.pull_request.title }}",
        title_link: "${{ github.event.pull_request.html_url }}"
    }]
}

title に設定している ${{ github.event.pull_request.title }} はPull Requestのタイトルに一致するため、ここに上述のような文字列を設定した場合、任意のコマンドを実行できてしまう(かもしれない)わけですね。SQL Injectionと全く同じ手口です。怖い怖い。

Injectionの対策

Pull Request Title Injectionの概要がわかったところで、次はその対策です。

toJSON関数を使う

GitHub ActionsにはActions内部で使える特別な関数がいくつかあり、今回はその中のtoJSON関数を使います。

Context and expression syntax for GitHub Actions - GitHub Help

toJSON関数は引数に渡したコンテキストをJSON形式で出力してくれる関数です。コンテキストのデータに特殊文字が含まれている場合、toJSON関数がいい感じにエスケープしてくれる(っぽい)です。問題となっているPull Requestのタイトルの部分にこの関数を噛ませてあげることで、今回のようなInjectionを回避できます。

name: notify
on:
  pull_request:
    types: ['closed']
    paths:
      - 'webapi/swagger/**'
    branches:
      - master

jobs:
  notify:
    name: Slack Notification
    runs-on: ubuntu-latest
    steps:
      - name: 'Send Notification'
        run: |
          jq -n '{
            attachments: [{
              pretext: "Swagger が更新されたよ!",
              color: "good",
              title: ${{ toJSON(github.event.pull_request.title) }},
              title_link: "${{ github.event.pull_request.html_url }}"
            }]
          }' | curl -H 'Content-Type: application/json' -d @- ${{ secrets.SLACK_WEBHOOK }}

実際に動かしてみると、以下のようなメッセージが投稿されます。titleの部分にPull Requestのタイトルが全て挿入されているのがわかります。

f:id:furusax0621:20200331215404p:plain

実行ログはこちらから確認できますJSON特殊文字がちゃんとエスケープされて渡されていることがわかると思います。

まとめ

GitHub Actionsで発生し得るPull Request Title Injectionの概要とその対策としてtoJSON関数を使う方法を紹介しました。 詳しく調べてませんが、以前の記事で紹介したいくつかのSlack通知Actionsでも同様のInjectionが実行できるようです。 Slack通知に限らず、Pull RequestのコンテキストをActionsのパラメータとして利用したい場合は十分気をつけましょう。

また、他にもInjection対策の方法があれば是非教えてください。

2020/04/05追記: 出題者よりお便り

こちらの記事を公開したところ、最初に議題を提案してくれた @shogo82148 よりまだまだ甘いぞというお手紙をいただきました。

RE: Pull Request Title Injection とその対策

。。精進しよう。。