開発者ツール · URL エンコーダーとデコーダー
二重 URL エンコード: %2520 が発生する仕組みと、それを検出して元に戻す方法
· 仕組み
URLエンコーディング JavaScript 開発者ワークフロー デバッグ
URL 内の %2520 は、スペースが 2 回エンコードされたことを意味します。この投稿では、その原因となるパイプラインの間違い、署名の認識方法、安全なデコード パスの数について説明します。
ダブル URL エンコード: %2520 の場合、スペースが 2 つのエンコーダを通過したことを意味します
"my file.pdf" ではなく "my%20file.pdf" として到着するファイル名は、二重エンコードを示しています。スペースが %20 にエンコードされ、パーセント記号自体が %25 にエンコードされ、最終 URL に %2520 が生成されます。クライアント コード、Web フレームワーク、リバース プロキシなどのシステムの各層は、一度エンコードされる場合があります。 2 つの別々のレイヤーがエンコードすると、1 つの文字が壊れてしまいます。
二重エンコードは、複雑なリダイレクト チェーンやテンプレート システムで最も頻繁に発生します。開発者は、フレームワーク内でエンコードされた URL を生成し、そのフレームワーク自体がデフォルトですべての出力をエンコードする場合があります。リバース プロキシまたはコンテンツ配信ネットワークは、バックエンド システムからエンコードされてすでに到着した URL を再エンコードする場合があります。すでにエンコードされた値を含むパラメータは、別の URL 構造内にネストされる前に再度エンコードされます。
%25 が原因である理由 — パーセント記号自体がエンコードされるため、%20 は %2520 になり、%C3%A9 は %25C3%25A9 になります。
二重エンコードの明らかな兆候は、URL またはデータ内で通常 1 つのパーセント記号が表示されると予想される場所に %25 が表示されることです。通常エンコードされた URL では、リテラル「%25」を送信しない限り、%25 が表示されることはありません。 %20 としてエンコードされたスペースを再度エンコードすると、%2520 になります。
é のようなアクセント付き文字は、通常は %C3%A9 にエンコードされますが、2 つの異なるシステムで連続して 2 回エンコードされると、%25C3%25A9 になります。 URL バー、ログ、エラー メッセージ内の %25 パターンを特定する方法を学習すると、複数のサービスをデータが流れる実稼働環境での数え切れないほどのイライラするデバッグ作業を節約できます。
二重エンコーディングが導入される場所 — クライアント コードとフレームワーク、リダイレクト、プロキシ、テンプレート ヘルパー
二重エンコーディングは、可読性と、サーバー側システムが URL を正しく解析する能力の両方を破壊します。 「my file.pdf」という名前のファイルは、正しくエンコードされると「my%20file.pdf」になります。エンコードされた文字列がフォームによって再エンコードされると、「my%2520file.pdf」になります。
サーバーがこれを受信して一度デコードすると、「my file.pdf」として認識するのではなく、「my%20file.pdf」をリテラルのファイル名として認識します。単一のデコード パスのみを受け取ることを期待しているアプリケーションは、壊れた結果を受け取ります。さらに悪いことに、1 回だけエンコードされた値の問題を修正するために 2 回デコードする開発者は、実際には余分なデコード パスによって正当なデータを破損してしまいます。
実用的な例: 二重にエンコードされた URL を一度に 1 パスずつデコードする - 各パスで何が明らかになり、いつ停止するか
クライアント側の JavaScript コードとサーバー側のフレームワークのデフォルトは、運用システムにおける偶発的な二重エンコードの最も一般的な原因です。 JavaScript アプリケーションは、値に対して encodeURIComponent を使用し、それをデフォルトですべての文字列出力をエンコードするフレームワークに直接渡すことで、パーセント記号を 2 回目にエンコードする場合があります。 URL をサニタイズすることを目的としたリバース プロキシ レイヤーは、バックエンド アプリケーションから事前にエンコードされたパラメータを再エンコードする場合があります。
ユーザー指定の入力をフレームワーク ヘルパー関数と連結して構築されたリダイレクト URL は、両方のステップで同時にエンコードできます。実用的な例: ユーザーが HTML フォーム経由で「test&value」を送信すると、ブラウザはそれを「test%26value」としてエンコードします。フレームワークはリテラルのパーセント テキストを認識してエンコードし、「test%2526value」を生成します。 1 つのデコードでは「test%26value」が得られますが、やはり間違っています。
二重エンコードが意図的である場合 - URL が別の URL のクエリ パラメータ内に含まれる
意図的な二重エンコーディングは、ある特定のケース、つまり、URL が別の URL のクエリ パラメータ内を移動する必要がある場合に有効です。 OAuth フローとログイン戻りリンクでは、1 つの完全な URL を別の完全な URL 内にネストする必要がある場合があります。まず内部 URL を完全にパーセント エンコードしてから、エンコードされた文字列全体を外部 URL のパラメータ値として再度エンコードする必要があります。
この二重エンコードは意図的であり、このような場合には絶対に必要です。外部パラメータ パーサーは 1 回デコードし、エンコードされたままの内部 URL を生成します。その後、内部システムが再度デコードして、元の URL を復元します。重要な鍵は、意図を理解し、将来のメンテナのためにコード コメントで明確に文書化することです。
よくある間違い — 何も変わらないまでデコードするため、正当に %25 を含む値が破損します。
古典的で危険な間違いは、何も変化しないまで繰り返しデコードすることです。これにより、実際のデータに正当にパーセント記号が含まれる値が破損します。 "discount%2525" のようなパラメーター (パラメーター値としてエンコードされ、転送用に再度エンコードされたリテラル "%25" を表します) は、設計上完全に正しいものです。これを一度デコードすると「discount%25」が得られますが、これは依然として正しいものです。もう一度デコードすると「discount%」が得られますが、これは間違っており、情報が失われます。
開発者は、「%25」が間違いであると考え、デコードを繰り返し、パーセント記号を失う可能性があります。代わりに、アーキテクチャが要求する回数だけデコードします (パラメータに対して 1 回、ネストされたものが 2 回)。正しいデコード操作を知るためにレイヤーを数えます。
これでカバーされないもの — URL の上に重ねられた HTML エンティティ エンコーディング。これは HTML エンティティ エスケーパーが処理します。
よくある間違いには、encodeURIComponent を使用して URL 全体をエンコードし、スラッシュとコロンが構造区切り文字として機能することを期待していましたが、エンコード後は機能しなくなることが含まれます。もう 1 つの頻繁なエラーは、異なるエンコード標準を混合することです。一部のコードは RFC 3986 に従ってパーセント エンコードを使用し、他のコードはスペースを表すプラス記号を使用したフォーム エンコードを使用します。 「my+file」のような値はまったくあいまいになります。「my file」を意味する場合もあれば、プラスを付けたリテラル テキスト「my+file」を意味する場合もあります。
パーセントエンコーディングが最初に「my+file」に触れる場合、それは「my%2Bfile」になります。フォームのデコードが続いて、スペースとしてのプラスを期待すると、間違ったままになります。レイヤー間の一貫性が不可欠です。すべてのシステムは同じエンコード標準を使用する必要があります。そうでない場合は、各レイヤーを明示的に文書化する必要があります。
要点: レイヤーごとに 1 回だけエンコードする — URL エンコーダーとデコーダーを使用して一度に 1 つのパスをデコードし、それぞれの中間結果を確認する方法
運用システムで発生している二重エンコーディングを特定できたら、修正はパイプライン内のどこで重複が発生しているかに完全に依存します。クライアント コードとフレームワークの両方がエンコードされている場合は、いずれか 1 つからエンコードを完全に削除します。パラメーターが複数のバックエンド サービスを通過する場合は、各サービスの完全なパスを追跡し、エンコードすべきでないときにどのサービスがエンコードしているかを見つけます。
サンプル データを完全なエンドツーエンド パイプラインに渡して修正を徹底的にテストし、データが完全に変更されていない状態で宛先に到着することを確認します。 「このエンドポイントはパーセントエンコードされたパラメータを返す」または「このミドルウェアは生の UTF-8 を期待し、それにエンコードを適用する」など、各境界でのエンコードの前提を明確に文書化します。将来の開発者のために、そのドキュメントに予想されるデコード パスの数を含めます。