お仕事の関係で、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