署名付きURLを壊してしまうトラッキングリンクのバグ
バグは、コードの中に長い間潜んでいることがあります。ほとんどのリンクでは正常に動作し、マーケティング用のリンクでも静かに振る舞います。しかし、最も重要なリンクを壊してしまうのです。
私は自分のツールである mail-history でこのバグを発見しました。これは署名付きURL(signed URLs)を壊してしまうものです。これらは、メール認証やセキュアなダウンロードに使用されるリンクです。
経緯は以下の通りです。
私のツールは、クリックを追跡するためにメールのHTMLを書き換えます。元のURLを取得して暗号化し、トラッキングリンクへと変換します。ユーザーがクリックすると、ツールはURLを復号し、実際の遷移先にユーザーを送り出します。
問題は、レンダリングされたHTMLからURLを取得したときに発生しました。
LaravelとBladeはHTML属性をエスケープします。URL内のアンパサンド(&)はHTMLエンティティになります。つまり、& が & に変わってしまうのです。
通常のリンクであれば、ブラウザがこれを自動的に修正します。そのため、違いに気づくことはありません。
しかし、私のツールはリンクをブラウザに送るわけではありません。文字列を復号関数に渡すのです。
もし署名付きURLが次のようであれば:
https://example.com/verify?expires=123&signature=abc
HTMLは次のようになります:
https://example.com/verify?expires=123&signature=abc
私のツールは & が含まれたバージョンを暗号化します。ユーザーがクリックすると、ツールはそれを復号し、& が含まれた状態のURLへとユーザーを誘導します。
Laravelは & を見て、署名が間違っていると判断します。その結果、検証が失敗します。
修正はわずか1行のコードです。暗号化が行われる前に html_entity_decode を使用して、& を & に戻します。
これにより、復号されたURLが元の署名付きURLとバイト単位で完全に一致することを保証できます。
仕事に活かせる教訓:
- レンダリングされたHTMLからデータを取り出して関数で使用する場合は、それがエスケープされているものと想定してください。
- ブラウザはミスを許容してくれますが、暗号化や署名検証はそうはいきません。
- 1行の修正を加えたときは、そのためのテストを書いてください。
私は & が存在するかどうかを具体的にチェックするテストを書きました。将来、別の開発者がコードの整理中にこの修正を削除してしまっても、テストが即座に失敗して気づくことができます。
Source: https://dev.to/nasrulhazim/the-tracking-link-bug-that-only-breaks-signed-urls-38c