GitHub の More secure private attachments の挙動を調べる
先日 GitHub から以下のアップデートがあったので、アップデート後の挙動について調べる。
結論
プライベートリポジトリの画像へアクセスするルートは大別すると以下の2通りあり、
- ドラッグ・アンド・ドロップした直後に生成される URL(
/{org}/{repo}/assets
)からアクセス - プレビュー時の画像 URL である private-user-images.githubusercontent.com からアクセス
GitHub が以下のように言ってるのは 1
のことだと思われる。(2
は有効期限付きとはいえ誰でも閲覧可能なので)
Now, future attachments associated with private repositories can only be viewed after logging in.
/{org}/{repo}/assets
)からアクセス
1. ドラッグ・アンド・ドロップした直後に生成される URL(まとめようと思ったらすでに以下の記事に書かれていました。ありがとうございます。
自分の調査内容も踏まえてまとめると以下のようになる。
- Issue や PR に画像・動画をドラッグ・アンド・ドロップすると
https://github.com/{org}/{repo}/assets/xxx
のような URL が生成される- 上記の URL はプライベートリポジトリのパスなので、そのリポジトリの権限がある人しかアクセスできない
-
https://github.com/{org}/{repo}/assets/xxx
にアクセスすると実際に画像・動画が格納されている S3 の署名付き URL にリダイレクトされる - S3 の署名付き URL はその URL を知ってる人ならば誰でもアクセス可能だが、有効期限が設定でき今回は 5 分に設定されているので、それ以降はアクセスできなくなる
2. プレビュー時の画像 URL である private-user-images.githubusercontent.com からアクセス
上記の記事に書かれていなかったルートとして、以下がある。
- 貼り付けた画像のプレビュー時などに img タグの src として、
https://private-user-images.githubusercontent.com/xxx?jwt=xxx
のような JWT 付きの URL が生成される - 上記 URL にアクセスするとリダイレクトされず直接 200 で画像データが返る
- S3 の署名付き URL と同じく 5 分間ならば誰でもアクセス可能だがそれ以降はアクセスできなくなる
以下、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 分であることを確認したいので以下のシェルスクリプトで確認した。
#!/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 を抜き出して確認する前提。
<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 が返ることが確認できた。