署名付き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