Closed5

GitHub の More secure private attachments の挙動を調べる

daido1976daido1976

結論

プライベートリポジトリの画像へアクセスするルートは大別すると以下の2通りあり、

  1. ドラッグ・アンド・ドロップした直後に生成される URL(/{org}/{repo}/assets)からアクセス
  2. プレビュー時の画像 URL である private-user-images.githubusercontent.com からアクセス

GitHub が以下のように言ってるのは 1 のことだと思われる。(2 は有効期限付きとはいえ誰でも閲覧可能なので)

Now, future attachments associated with private repositories can only be viewed after logging in.

daido1976daido1976

1. ドラッグ・アンド・ドロップした直後に生成される URL(/{org}/{repo}/assets)からアクセス

まとめようと思ったらすでに以下の記事に書かれていました。ありがとうございます。

https://dev.classmethod.jp/articles/github-more-secure-private-attachments/

自分の調査内容も踏まえてまとめると以下のようになる。

  • Issue や PR に画像・動画をドラッグ・アンド・ドロップすると https://github.com/{org}/{repo}/assets/xxx のような URL が生成される
    • 上記の URL はプライベートリポジトリのパスなので、そのリポジトリの権限がある人しかアクセスできない
  • https://github.com/{org}/{repo}/assets/xxx にアクセスすると実際に画像・動画が格納されている S3 の署名付き URL にリダイレクトされる
  • S3 の署名付き URL はその URL を知ってる人ならば誰でもアクセス可能だが、有効期限が設定でき今回は 5 分に設定されているので、それ以降はアクセスできなくなる
daido1976daido1976

2. プレビュー時の画像 URL である private-user-images.githubusercontent.com からアクセス

https://dev.classmethod.jp/articles/github-more-secure-private-attachments/

上記の記事に書かれていなかったルートとして、以下がある。

  • 貼り付けた画像のプレビュー時などに img タグの src として、https://private-user-images.githubusercontent.com/xxx?jwt=xxx のような JWT 付きの URL が生成される
  • 上記 URL にアクセスするとリダイレクトされず直接 200 で画像データが返る
  • S3 の署名付き URL と同じく 5 分間ならば誰でもアクセス可能だがそれ以降はアクセスできなくなる

daido1976daido1976

以下、2 の詳細な調査内容。

https://github.com/daido1976/private-attachments-test というプライベートリポジトリを作成して調査開始。

プレビュー時やコメント投稿後にレンダリングされる URL は以下のようになる。

https://private-user-images.githubusercontent.com/xxx.png?jwt=xxx

ドメインは S3 ではなく GitHub のものになっており、こちらは https://github.com/{org}/{repo}/assets/xxx にアクセスした時のようにリダイレクトされず 200 で画像が返ってくるが、クエリパラメータについている JWT のペイロードを覗くと、以下のように path フィールドに S3 の署名付き URL(有効期限 5 分)のパスが入っており、同じように S3 の署名付き URL から画像取得して返していると推測できる。

{
  "key": "key1",
  "exp": 1683707406,
  "nbf": 1683707106,
  "path": "/35087200/237325878-ef485084-89c2-4cc5-9420-0ae35b0c328e.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20230510%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230510T082506Z&X-Amz-Expires=300&X-Amz-Signature=453a32480aec0f72c403029aab3bccecd54e20c3a5e2debfe8bd1681c4b4f806&X-Amz-SignedHeaders=host"
}

が、サーバ側の処理なので実際はどのようになっているか分からない。

セキュリティリスクを考察するために署名付き URL に直接アクセスした時と同じく、有効期限が 5 分であることを確認したいので以下のシェルスクリプトで確認した。

check_url_until_404.sh
#!/bin/bash

# 標準入力からレスポンスを取得します
response=$(cat)

# hrefのURLを取得します
url=$(echo "$response" | grep -o 'href="[^"]*' | sed 's/href="//')

# URLの有効期限を計測します
start=$(date +%s)

while true; do
    http_code=$(curl -o /dev/null -s -w "%{http_code}" "$url")
    current=$(date +%s)
    diff=$((current - start))

    if [ "$http_code" -eq 404 ]; then
        echo "The URL returned 404 after $diff seconds."
        exit 0
    else
        echo "Checked URL at $(date), HTTP response code was $http_code, and the elapsed time is $diff seconds."
    fi

    sleep 1
done

Preview に hover した時に走る https://github.com/preview へのリクエストで以下のような HTML が返ってくるので、これの href を抜き出して確認する前提。

preview-request

<p dir="auto"><a target="_blank" rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/35087200/237577059-b5c819d8-dc3c-4df1-aa6b-4eed4c0365e4.jpg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJrZXkxIiwiZXhwIjoxNjgzOTc2MzIzLCJuYmYiOjE2ODM5NzYwMjMsInBhdGgiOiIvMzUwODcyMDAvMjM3NTc3MDU5LWI1YzgxOWQ4LWRjM2MtNGRmMS1hYTZiLTRlZWQ0YzAzNjVlNC5qcGc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBSVdOSllBWDRDU1ZFSDUzQSUyRjIwMjMwNTEzJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDIzMDUxM1QxMTA3MDNaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0yYzBmYzA4MjZiYmI5NWU4MDlhNTBlYTNlZjEyZjFlMzg1MDJkMTAwYjMwYzM5OGVkNmYxYWQxNzMwOGZlZjM2JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.pCN0MjycHAemoLfQW3WMr_lb0389PM5mg3dPZDUcR58"><img src="https://private-user-images.githubusercontent.com/35087200/237577059-b5c819d8-dc3c-4df1-aa6b-4eed4c0365e4.jpg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJrZXkxIiwiZXhwIjoxNjgzOTc2MzIzLCJuYmYiOjE2ODM5NzYwMjMsInBhdGgiOiIvMzUwODcyMDAvMjM3NTc3MDU5LWI1YzgxOWQ4LWRjM2MtNGRmMS1hYTZiLTRlZWQ0YzAzNjVlNC5qcGc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBSVdOSllBWDRDU1ZFSDUzQSUyRjIwMjMwNTEzJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDIzMDUxM1QxMTA3MDNaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0yYzBmYzA4MjZiYmI5NWU4MDlhNTBlYTNlZjEyZjFlMzg1MDJkMTAwYjMwYzM5OGVkNmYxYWQxNzMwOGZlZjM2JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.pCN0MjycHAemoLfQW3WMr_lb0389PM5mg3dPZDUcR58" alt="free-cat" style="max-width: 100%;"></a></p>

https://github.com/preview へのリクエストを copy as curl して以下のように実行する。

$ curl 'https://github.com/preview?issue=1704982527&markdown_unsupported=false&repository=638915899&subject_type=Issue' \
# ヘッダ情報が続く
| ./check_url_until_404.sh

Checked URL at Sat May 13 20:41:43 JST 2023, HTTP response code was 200, and the elapsed time is 1 seconds.
Checked URL at Sat May 13 20:41:44 JST 2023, HTTP response code was 200, and the elapsed time is 2 seconds.
...
The URL returned 404 after 300 seconds.

ざっくりだが、これで 300 秒(5分)で 404 が返ることが確認できた。

このスクラップは2023/05/15にクローズされました