見識不夠,踩到一個 ASP.NET Cookie 命名地雷。

在某個 ASP.NET 專案用 Response.Cookies.Add() 新增名為 "$myCookie" 的 HttpCookie,之後用 Request.Cookies["$myCookie"] 卻讀不到值,試著把名稱改成 "_myCookie" 就正常。心想,有可能 $myCookie 不是合法的 Cookie 名稱,於是我寫了小程式想重現問題:

<%@Page Language="C#"%>
<script runat="server">
const string cookieName = "$myCookie";
string cookieValue;
void Page_Load(object sender, EventArgs e)
{
	cookieValue = Request.Cookies[cookieName] == null ? 
		string.Empty : Request.Cookies[cookieName].Value;
	if (Request["m"] == "set") 
	{
		Response.Cookies.Add(new HttpCookie(
			cookieName, new Random().Next().ToString()));
		Response.Redirect("default.aspx");
	}
}
</script>
<!DOCTYPE html>
<html>
	<head>
	</head>
	<body>
		<div>
			Cookie["<%=cookieName%>"] = <%= cookieValue %>
		</div>
		<a href="?m=set">Set Cookie</a>
	</body>
</html>

你猜怎麼著,居然成功了。所以 $myCookie 可以當成 Cookie 名稱呀!

那... 到底 $ 可不可以當成 Cookie 名稱開頭?

爬文研究後後,得到以下結論。

  1. 依 RFC,$ 為合法的 Cookie 名稱字元 依據 RFC 6265,Cookie 命名遵守 RFC2616 Section 2.2 Token 規範,$ 不在禁用之列:參考
    token          = 1*<any CHAR except CTLs or separators>
    CHAR           = <any US-ASCII character (octets 0 - 127)>
    CTL            = <any US-ASCII control character
                     (octets 0 - 31) and DEL (127)>
    separators     = "(" | ")" | "<" | ">" | "@"
                     | "," | ";" | ":" | "\" | <">
                     | "/" | "[" | "]" | "?" | "="
                     | "{" | "}" | SP | HT
    
  2. MS 文件,Cookie 名稱不能以 $ 起首 參考

    The following characters must not be used inside name: equal sign, semicolon, comma, newline (\n), return (\r), tab (\t), and space character. The dollar sign character ("$") cannot be the first character.

再研究了一下,大概知道是怎麼一回事了。在古早年代,Cookie 規範以 RFC 2109 為準,在 4.2.2 節提到有所謂 Attribute-Value Pair (Version、Domain、Path、Max-Age、Secure、Comment),而 Cookie 名稱不能以 $ 開頭:

NAME=VALUE
      Required.  The name of the state information ("cookie") is NAME,
      and its value is VALUE.  NAMEs that begin with $ are reserved for
      other uses and must not be used by applications.

在 4.4 則提到 $ 起首的 Cookie 名稱為相鄰 Cookie 的屬性值

When it receives a Cookie header, the origin server should treat cookies with NAMEs whose prefix is $ specially, as an attribute for the adjacent cookie.

不過呢,在 2000 年,RFC 2965 取代了 RFC2109,2011 年再被 RFC 6265 取代,當今 Cookie 規範已不再使用 $Path、$Domain 等名稱標註 Cookie 屬性,$ 也不再是 Cookie 名稱字首保留字元。

由此推測,雖然現代瀏覽器已不再使用 $ 起首 Cookie 設定屬性,但 ASP.NET 為向前相容,仍遵循古禮,將 $ 字首 Cookie 視為相鄰 Cookie 的屬性。最早的範例之所以可以成功,是因為只有一個 Cookie 沒有相鄰 Cookie,$myCookie 被視為獨立 Cookie 值。因此,我們只需隨便加個 Cookie 就能重現 $ 起首 Cookie 讀不到的狀況。

	if (Request["m"] == "set") 
	{
		Response.Cookies.Add(new HttpCookie(
			cookieName, 
			new Random().Next().ToString()
		));
		// 隨便再多設一個 Cookie
		Response.Cookies.Add(new HttpCookie(
			"A", "1234"
		));
		Response.Redirect("default.aspx");
	}

成功重現問題! 瀏覽器識別出 $myCookie、A 兩個 Cookie,但 ASP.NET 讀不出 $myCookie。

所以,在 2022 年,瀏覽器可以接受名稱以 $ 起首的 Cookie 沒問題,至於伺服器能不能接受,依平台而定。ASP.NET 不支援,在文件也有載明。至於其他平台,來用 PHP 跑看看:

<?php
$cn = '$myCookie';
$m=$_GET['m'] ?? '';
if ($m == 'set') {
	setcookie($cn, rand(0, 100000));
	setcookie('A', '1234');
	header('Location: index.php');
	die();
}
?>
<!DOCTYPE html>
<html>
	<head>
	</head>
	<body>
		<div>
			Cookie["<?php echo $cn ?>"] = <?php echo $_COOKIE[$cn] ?? '' ?>
		</div>
		<a href="?m=set">Set Cookie</a>
	</body>
</html>

實驗證明,PHP 可以接受以 $ 起首的 Cookie 名稱!

所有謎團解開,再增長一些知識。

Study of wether the dollar sign can be the first char of cookie name.


Comments

# by 小黑

真是實用,黑哥嚴謹精神令人嚮往

Post a comment