お仕事の関係で、HTTPリクエストのCookieヘッダの値はどう解釈されているのか調べてみました。
通常はCookieヘッダの値部分は、空白以外の値で、一般的には余計な記号など入り込まないように言語やライブラリ、フレームワーク、あるいはプログラマが処理してます。
しかし、特殊な要件を満たすため、普段は設定しない記号系の文字もCookieで使う必要がありました。
そこでいくつかのメジャーなWebアプリ実行環境について、実際にCookieに特殊な記号を入れて送ってみて、Webアプリケーション側ではどのように処理されて、見えるようになるのか調べてみました。また、逆に、特殊な記号を入れた値をアプリケーションからCookieとして設定すると、最終的なSet-Cookieでどのようにエンコードやエスケープされるかも確認してみました。
2014-02-08時点での確認状況
| 言語/プラットフォーム | Cookieヘッダの解釈 | Set-Cookieでのエンコード |
|---|---|---|
| PHP5 | URLデコードする | setcookie():URLエンコード, setrawcookie():そのまま |
| Tomcat7 | 一部記号を除きそのまま | 基本そのまま、一部記号が入ると""囲み |
| Ruby(Rackベース) | URLデコードする | URLエンコードする |
| ASP | (未検証) | (未検証) |
| ASP.NET | (未検証) | (未検証) |
| ASP.NET MVC | (未検証) | (未検証) |
| Python(WSGIベース) | (未検証) | (未検証) |
| node.js | (未検証) | (未検証) |
| Play(Nettyベース) | (未検証) | (未検証) |
| (その他) | (未検証) | (未検証) |
細かい状況については、以下の個別の詳細確認状況を御覧ください。
検証に使用した文字種:
(0x20)
!
"
#
$
%
&
'
(
)
=
~
|
-
^
\
@
[
]
{
}
;
+
:
*
,
.
<
>
?
_
HTTPリクエストのCookieヘッダの検証では、上記をエンコードせずにそのままサーバに対して送信したケースと、URLエンコードしたケースの2パターンを確認した。
サーバからのSet-Cookieの検証では、上記を含んだ値を言語処理系やライブラリ・フレームワークの提供する機能を用いてSet-Cookieした時の挙動を確認した。
環境:
CentOS 6.5 (x64) PHP 5.3.3 + Apache/2.2.15 (Apache 2.0 Handler) magic_quotes_gpc : off
サンプルコード:cookies.php (HTTPリクエストのCookie値の解釈を確認)
<?php var_dump($_COOKIE);
サンプルコード:setcookie.php (setcookie()の動きを確認)
<?php setcookie(@$_GET['cn'], @$_GET['cv']);
サンプルコード:setrawcookie.php (setrawcookie()の動きを確認)
<?php setrawcookie(@$_GET['cn'], @$_GET['cv']);
URLデコードする。ただしURLエンコードされていない文字についてはそのまま素通しになる。
Cookie: abc=123 DEF; def=456bGHI
->
array(2) {
["abc"]=>
string(7) "123 DEF"
["def"]=>
string(7) "456bGHI"
}
Cookie: abc=123!DEF; def=456bGHI
->
string(7) "123!DEF"
Cookie: abc=123%DEF; def=456bGHI
->
string(5) "123(0xDE)F"
Cookie: abc=123+DEF; def=456bGHI
->
string(7) "123 DEF"
Cookie: abc=123,DEF; def=456bGHI
->
string(7) "123,DEF"
";"がそのまま混入すると、そこでCookie値が分割されるようだ。
Cookie: abc=123;DEF; def=456bGHI
->
array(3) {
["abc"]=>
string(3) "123"
["DEF"]=>
string(0) ""
["def"]=>
string(7) "456bGHI"
}
Cookie値がURLエンコードされていると、URLデコードされてアプリ空間から参照できた。
Cookie: abc=123%20DEF; def=456bGHI
->
array(2) {
["abc"]=>
string(7) "123 DEF"
["def"]=>
string(7) "456bGHI"
}
Cookie: abc=123%25DEF; def=456bGHI
->
string(7) "123%DEF"
Cookie: abc=123%3bDEF; def=456bGHI
->
string(7) "123;DEF"
Cookie: abc=123%2bDEF; def=456bGHI
->
string(7) "123+DEF"
Cookie: abc=123%3aDEF; def=456bGHI
->
string(7) "123:DEF"
Cookie: abc=123%2aDEF; def=456bGHI
->
string(7) "123*DEF"
Cookie: abc=123%2cDEF; def=456bGHI
->
string(7) "123,DEF"
Cookie: abc=123%2eDEF; def=456bGHI
->
string(7) "123.DEF"
setcookie()を使う場合は、URLエンコードする。
(setcookie() にセットした値) -> (実際にサーバから送信されたSet-Cookieヘッダ) 123 DEF -> Set-Cookie: abc=123+DEF 123_DEF -> Set-Cookie: abc=123_DEF 123?DEF -> Set-Cookie: abc=123%3FDEF 123.DEF -> Set-Cookie: abc=123.DEF 123,DEF -> Set-Cookie: abc=123%2CDEF 123*DEF -> Set-Cookie: abc=123%2ADEF 123;DEF -> Set-Cookie: abc=123%3BDEF 123+DEF -> Set-Cookie: abc=123%2BDEF 123-DEF -> Set-Cookie: abc=123-DEF
setrawcookie()を使う場合は、URLエンコードされない。ただし、いくつかの文字は使えず、Set-Cookieヘッダは発行されずに、Warningが発生してしまう。
Warning: Cookie values cannot contain any of the following ',; \t\r\n\013\014' in ...
(setrawcookie() にセットした値) -> (実際にサーバから送信されたSet-Cookieヘッダ) 123 DEF -> (上記Warning発生) 123_DEF -> Set-Cookie: abc=123_DEF 123?DEF -> Set-Cookie: abc=123?DEF 123.DEF -> Set-Cookie: abc=123.DEF 123,DEF -> (上記Warning発生) 123*DEF -> Set-Cookie: abc=123*DEF 123;DEF -> (上記Warning発生) 123+DEF -> Set-Cookie: abc=123+DEF 123-DEF -> Set-Cookie: abc=123-DEF
環境:
CentOS 6.5 (x64) Oracle JDK 1.7.0_51 (x64), Server VM Apache Tomcat 7.0.50
サンプルコード:cookies.jsp (HTTPリクエストのCookie値の解釈を確認)
<%@page
contentType="text/html; charset=utf-8"
pageEncoding="UTF-8"
%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Cookie Viewer</title>
</head>
<body>
<%
Cookie[] cookies = request.getCookies();
if (null != cookies) {
for (Cookie c : cookies) {
out.println("Cookie[" + c.getName() + "]=[" + c.getValue() + "]<br>");
}
}
%>
</body>
</html>
サンプルコード:setcookie.jsp (Set-Cookieの挙動を確認)
<%@page
contentType="text/html; charset=utf-8"
pageEncoding="UTF-8"
%>
<%
String cn = request.getParameter("cn");
String cv = request.getParameter("cv");
if (null != cn && null != cv) {
response.addCookie(new Cookie(cn, cv));
}
%>
URLデコードされず、そのまま取得されるケース:
Cookie: abc=123%DEF; def=456bGHI -> Cookie[abc]=[123%DEF]<br> Cookie: abc=123.DEF; def=456bGHI -> Cookie[abc]=[123.DEF]<br> Cookie: abc=123+DEF; def=456bGHI -> Cookie[abc]=[123+DEF]<br> Cookie: abc=123%2FDEF; def=456bGHI -> Cookie[abc]=[123%2FDEF]<br> 他: !, #, $, &, ', ~, |, -, ^, *, _
以降の値が削除されてしまったケース:
Cookie: abc=123"DEF; def=456bGHI
->
Cookie[abc]=[123]<br>
Cookie: abc=123,DEF; def=456bGHI
->
Cookie[abc]=[123]<br>
他: (, ), =, \, @, [, ], {, }, ;, :, <, >, ?
""で囲むことで、削除されなくなる例:
Cookie: abc="123,DEF"; def=456bGHI -> Cookie[abc]=[123,DEF]<br> Cookie: abc="123@DEF"; def=456bGHI -> Cookie[abc]=[123@DEF]<br> Cookie: abc="123(DEF"; def=456bGHI -> Cookie[abc]=[123(DEF]<br> ただし " は変化なし。 Cookie: abc="123"DEF"; def=456bGHI -> Cookie[abc]=[123]<br> → \" としてバックスラッシュエスケープすれば残る。 Cookie: abc="123\"DEF"; def=456bGHI -> Cookie[abc]=[123"DEF]<br> また、 \ はその文字だけ削除される。 Cookie: abc="123\DEF"; def=456bGHI -> Cookie[abc]=[123DEF]<br> → \\ としてバックスラッシュエスケープすれば残る。 Cookie: abc="123\\DEF"; def=456bGHI -> Cookie[abc]=[123\DEF]<br>
基本的にはそのままSet-Cookieヘッダに出力される。
(response.addCookie(new Cookie("abc", ...)) にセットした値)
->
(実際にサーバから送信されたSet-Cookieヘッダ)
123%DEF
->
Set-Cookie: abc=123%DEF
123&DEF
->
Set-Cookie: abc=123&DEF
123+DEF
->
Set-Cookie: abc=123+DEF
他、このケースで確認した記号: !, #, $, ', ~, |, -, ^, *, ., _
一部の記号は "" で囲われ、 " 自体はバックスラッシュエスケープされる。また、"Version=1"が自動で追加された。
(response.addCookie(new Cookie("abc", ...)) にセットした値)
->
(実際にサーバから送信されたSet-Cookieヘッダ)
123\DEF
->
Set-Cookie: abc="123\DEF"; Version=1
123,DEF
->
Set-Cookie: abc="123,DEF"; Version=1
123;DEF
->
Set-Cookie: abc="123;DEF"; Version=1
123 DEF
->
Set-Cookie: abc="123 DEF"; Version=1
123"DEF
->
Set-Cookie: abc="123\"DEF"; Version=1
他、このケースで確認した記号: (, ), =, @, [, ], {, }, :, <, >, ?
環境:
ruby-2.0.0-p353-i386-mingw32
Windows7 Pro SP1 64bit
> gem environment
RubyGems Environment:
- RUBYGEMS VERSION: 2.0.14
- RUBY VERSION: 2.0.0 (2013-11-22 patchlevel 353) [i386-mingw32]
(...)
- RUBYGEMS PLATFORMS:
- ruby
- x86-mingw32
(...)
> gem install sinatra
rack-1.5.2
tilt-1.4.1
rack-protection-1.5.2
sinatra-1.4.4
サンプルコード:
require 'sinatra'
get '/cookies' do
request.cookies.inspect
end
get '/setcookie' do
response.set_cookie(params[:cn], params[:cv])
end
get '/hi' do
"Hello World!"
end
URLデコードする。ただしURLエンコードされていない文字についてはそのまま素通しになる。
Cookie: abc=123 DEF; def=456bGHI
->
{"abc"=>"123 DEF", "def"=>"456bGHI"}
Cookie: abc=123!DEF; def=456bGHI
->
{"abc"=>"123!DEF", "def"=>"456bGHI"}
Cookie: abc=123%DEF; def=456bGHI
->
{"abc"=>"123\xDEF", "def"=>"456bGHI"}
Cookie: abc=123+DEF; def=456bGHI
->
{"abc"=>"123 DEF", "def"=>"456bGHI"}
";"や","がそのまま混入すると、そこでCookie値が分割されるようだ。
Cookie: abc=123;DEF; def=456bGHI
->
{"abc"=>"123", "DEF"=>nil, "def"=>"456bGHI"}
Cookie: abc=123,DEF; def=456bGHI
->
{"abc"=>"123", "DEF"=>nil, "def"=>"456bGHI"}
Cookie値がURLエンコードされていると、URLデコードされてアプリ空間から参照できた。
Cookie: abc=123%20DEF; def=456bGHI
->
{"abc"=>"123 DEF", "def"=>"456bGHI"}
Cookie: abc=123%25DEF; def=456bGHI
->
{"abc"=>"123%DEF", "def"=>"456bGHI"}
Cookie: abc=123%3bDEF; def=456bGHI
->
{"abc"=>"123;DEF", "def"=>"456bGHI"}
Cookie: abc=123%2bDEF; def=456bGHI
->
{"abc"=>"123+DEF", "def"=>"456bGHI"}
Cookie: abc=123%3aDEF; def=456bGHI
->
{"abc"=>"123:DEF", "def"=>"456bGHI"}
Cookie: abc=123%2aDEF; def=456bGHI
->
{"abc"=>"123*DEF", "def"=>"456bGHI"}
Cookie: abc=123%2cDEF; def=456bGHI
->
{"abc"=>"123,DEF", "def"=>"456bGHI"}
Cookie: abc=123%2eDEF; def=456bGHI
->
{"abc"=>"123.DEF", "def"=>"456bGHI"}
URLエンコードする。
(response.set_cookie("abc", ...) にセットした値)
->
(実際にサーバから送信されたSet-Cookieヘッダ)
123 DEF
->
Set-Cookie: abc=123+DEF
123_DEF
->
Set-Cookie: abc=123_DEF
123?DEF
->
Set-Cookie: abc=123%3FDEF
123.DEF
->
Set-Cookie: abc=123.DEF
123,DEF
->
Set-Cookie: abc=123%2CDEF
123*DEF
->
Set-Cookie: abc=123*DEF
123;DEF
->
Set-Cookie: abc=123%3BDEF
123+DEF
->
Set-Cookie: abc=123%2BDEF
123-DEF
->
Set-Cookie: abc=123-DEF