メインコンテンツへスキップ
見出し画像

ゲームに鍵をかけたら自分も締め出された|Signed URLが403になる原因と直し方

    ゲームに鍵をかけたら自分の鍵も壊れてた|Signed URLとURL.searchParamsの罠

    「ゲームのアクセスを制限したい」——そう思ってセキュリティ実装をしたら、自分もアクセスできなくなりました。

    しかも原因は、JavaScriptのごく普通の書き方でした。個人開発の釣りゲームアプリで踏んだ罠と解決策をまとめます。

    そもそもSigned URLって何?

    「Signed URL(署名付きURL)」は、期限付きの合言葉が埋め込まれたURLです。

    通常のURLは誰でもアクセスできますが、Signed URLはURLの末尾に署名(デジタル的なハンコ)が付いていて、その署名が正しい場合だけアクセスを許可します。署名が少しでも変わると即403エラー(アクセス拒否)です。

    画像

    マンチーは個人開発の釣りゲームに、AWSのCloudFront(コンテンツ配信サービス)を使ったSigned URLでアクセス制限をかけようとしていました。

    やりたかったこと

    ゲームを開いたまま放置されるとセッションが残り続けるので、「ハートビート(定期的な生存確認の信号)」を実装しました。

    キャッシュ回避のため毎回URLにタイムスタンプを付け加える処理をJavaScriptで書きました。

    const url = new URL(signedUrl);
    url.searchParams.set('_hb', Date.now());
    fetch(url.toString());

    `URL.searchParams`はURLのパラメータを操作できる便利な機能です。一見、何も問題なさそうに見えます。

    結果:毎回403。自分も締め出された

    動かしてみると、ハートビートのたびに403エラー。しかもゲームそのものにもアクセスできなくなりました。セキュリティを強化しようとして自爆です。

    原因:URL.searchParamsが署名を壊していた

    CloudFrontのSigned URLの署名にはBase64エンコードされた文字列(`+`や`/`などの記号を含む)が使われています。

    `URL.searchParams`はURLを「正規化」するとき、これらの記号を`%2B`や`%2F`などに変換してしまいます。CloudFrontは「署名が変わった=不正アクセス」と判断して403を返していたのです。

    画像

    流れを整理するとこうです。

    1. Signed URLを生成(署名に`+`や`/`が含まれている)

    2. `URL.searchParams.set()`を使う

    3. `+`が`%2B`に勝手に変換される

    4. CloudFrontが「署名が違う」と判断 → 403

    解決策:文字列で直接連結する

    // NG
    const url = new URL(signedUrl);
    url.searchParams.set('_hb', Date.now());
    
    // OK
    const url = signedUrl + '&_hb=' + Date.now();

    `URL.searchParams`を使わず文字列として直接連結するだけ。署名文字列に一切触れないので壊れません。

    画像

    教訓

    「便利だから使う」が裏目に出るのがAWS開発のあるあるです。Signed URLを扱うときは署名した後は文字列を絶対に触らない、これだけ覚えておけば同じ罠を避けられます。


    【この記事が興味深かった方は、以下記事もおすすめです】

    <おすすめ記事①>
    URLを知らない人には存在すら見えない。招待制で、安全に試してもらう仕組みを自作した話はこちら。

    <おすすめ記事②>
    お試し公開は、軽い鍵でいい。JSパスコードで限定公開する最小構成を知りたい方はこちら。

    <おすすめ記事③>
    公式どおりにやっても動かない。Cognito + Google OAuthで詰まる3つの罠はこちら。


     
     
    jijiがAIで開発してます🤖 AI駆動開発 / AWS / Web制作 / 業務システム 上流工程10年→AI実装に挑戦中 ブログ: munchie.jp

    あなたへのおすすめ