Indy Cookie 2007


執筆:2007.01.05
編集:2007.03.19
編集:2007.07.09
編集:2007.07.18
編集:2007.07.21
編集:2007.11.23
編集:2009.10.16


追記:2009.10.16 IndyTiburon.zip(Indy 10.5.7)にて修正されているようです。
IndyTiburon.zipに移行して indy10.zip(10.2.3)は開発がとまったようですので注意が必要です。
このページの内容は古くなっています。
参考にする場合は注意が必要です。

10.0.52は、かなり古いので
cookieのドメインの最初が .で始まっていると処理できずエラーになります。
(IdCookie、IdCookieManager)
(10.1.6でも直っていませんでした。:2007.3、2007.7)
(10.2.3でも直っていませんでした。:2007.10)


他にも修正をみつけたので、この文書は
インストール方法と分離しました。(2007.7.21)
最新版のインストール方法は、こちらに書いてあります。


パッチ: クッキーのバグを修正します。

Indy10 Cookie bug fix(2007年3月中旬 時点、version 10.1.6)
Indy10 Cookie bug fix(2007年10月中旬 時点、version dev 10.2.3)
Indy9   Cookie bug fix(2007年7月中旬 時点、version 9.0.5.0(stable),indy9.0.18(stable), DevSnapshot)

【現象】
1.
   example.comがexample.comのcookieを渡し、ソフトウェアに保存したとします。
   .example.comでのcookieを、example.comで取得できません。

2.
   anywordexample.comにソフトウェアが接続するとexample.comのcookieをanywordexample.comに渡します。 
   example.com.anydomainにソフトウェアが接続するとexample.comのcookieをexample.com.anydomainに渡します。

 後者(2.)は、セキュリティ勧告ものレベルなのは容易に想像がつくと思います。

   サイトと同名の文字列を含んでる場合に限られますが、
   誘導によりクッキーの略奪が可能なので注意が必要です。

(3.)パス付き/で終わらないクッキーを保存した場合、同じ文字が並ぶパスにわたしてしまいます。
    firefox2ではわたさないことを確認しました。(違うパスへ保存はできるようです。)
 ※ IE7では、Indyと同様の現象が起きます。
    Netscpeの仕様書を見るとそうなると 最終パス名の部分一致になると書いてありました。
   ※ Cookieには、きちんとした規格が無いようなことがかいてありました。
    firefox2のような動作がいいと個人的に私は思いますのでそういう風に修正を加えます。

  /で終わらないクッキーを書いた方が悪いと思いますが、そういうミスはあるものです。
  (共有サーバーで問題になります。他人のアカウントにわたすので)
  例:   path=/tea; http/〜/tea/のものを  http/〜/team/ にもわたします。 
    

【対策】
※:適用した場合のコードの安全性はご自分で確認を取ってください。
  わたしは、一切責任はもちません。

コードが修正されている可能性も否定できませんので
まず、何もしない状態でセキュリティホールが残っているか確認をとることをお勧めします。
下の再現コードである程度確認できると思います

2007年3月でのdevコードなので、該当行が多少移動しているかもしれません。
関数名も書いているので探しやすいと思います。
 (delphi2005付属の10.0.52は、行数が違いますので関数名で探してください。)

Indy10
\Lib\Protocols\
IdCookie.pas
  単純に切り落とすと間違った判定をします。
TIdNetscapeCookie.IsValidCookie
Indy 〜 10.1.16 :
331: function TIdNetscapeCookie.IsValidCookie

begin
//....(省略)
   Result := FDomain = RightStr(AServerHost, Length(FDomain));
修正
  var ACookieDomain : String;
begin
//....(省略)
//  Result := FDomain = RightStr(AServerHost, Length(FDomain));
// bad code : FDomain = RightStr(AServerHost, Length(FDomain));
//   FDomain        AServerHost
//  xxxx.yyyy.com , zxxxx.yyyy.com : is not valid
//  xxxx.yyyy.com ,  xxxx.yyyy.com : is valid
// .xxxx.yyyy.com ,  xxxx.yyyy.com : is valid
// .xxxx.yyyy.com , yxxxx.yyyy.com : is not valid
      if (Copy(FDomain,1 , 1)='.') then
           ACookieDomain := FDomain
          else
           ACookieDomain := '.' + FDomain;
       Result := ( ACookieDomain = RightStr('.' + AServerHost, Length(ACookieDomain)) );
Indy 10.2.3 : 
-      Result := TextEndsWith(AServerHost, FDomain);
+       // bad code : Result := TextEndsWith(AServerHost, FDomain);
+       //    TextEndsWith('noexample.com',  'example.com') = true : is not valid
+        if (Copy(FDomain,1 , 1)='.') then
+            Result := TextEndsWith('.' + AServerHost, FDomain)
+         else
+            Result := TextEndsWith('.' + AServerHost, '.' + FDomain);
IdCookieManager.pas
  ドメインの比較にPos関数を使ってはいけません。
135: function TIdCookieManager.GenerateCookieList

begin
//....(省略)
  if IndyPos(LCookiesByDomain[i], URL.Host) > 0 then
修正
  TempCookiesDomain : String;
begin
//....(省略)
//  if IndyPos(LCookiesByDomain[i], URL.Host) > 0 then
    TempCookiesDomain := LCookiesByDomain[i];
    if (Copy(TempCookiesDomain,1 , 1) <> '.') then TempCookiesDomain := '.' + TempCookiesDomain;
    if (TempCookiesDomain  = RightStr('.' + URL.Host, Length(TempCookiesDomain))) then
注意
 Netscapeの仕様書では、/で終わらない場合、最後のパス名は部分一致になっています
 IE7もそういう動作をしているようです。
 firefox2では、部分一致にはなっていません。
 firefox2風に、/で終わるパスと同様の動作にするには以下のようにします。

function TIdCookieManager.GenerateCookieList

 if Pos(LCookieList.Cookies[j].Path, URL.Path) = 1 then
-->
// if Pos(LCookieList.Cookies[j].Path, URL.Path) = 1 then
 if (RightStr(LCookieList.Cookies[j].Path,1) = '/') then
  begin
    if Pos(LCookieList.Cookies[j].Path, URL.Path) <> 1 then continue;
  end
  else
  begin
    if Pos(LCookieList.Cookies[j].Path + '/', URL.Path) <> 1 then continue;
  end;
Firefox2では、Cookieのドメインに . がつくようなのでそうします。
Dev版(Indy 10.2.3)のIdGlobalProtocols.IsHostNameで
最初に . が付いているとFalseを返すようになってしまったので[ . ]が最初についていた場合取り除きます。

 .で始まるものを保存できるように修正をしましたので
 そのままでは最初の文字が . 付きと .がついていない物と混在してしまいます。
 以下のように修正します。
procedure TIdCookieManager.DoAdd

 if Length(ACookie.Domain) = 0 then LDomain := AHost
  else LDomain := ACookie.Domain;
-->
 if Length(ACookie.Domain) = 0 then LDomain := AHost
  else LDomain := ACookie.Domain;

  while (copy(LDomain, 1 , 1)='.') do LDomain := copy(LDomain, 2 , Length(LDomain));
  if (LDomain ='') then exit;
(省略)の部分は、ここでわかりやすくするために
省略しただけで誤解しないようにお願いします

Indy 9
IdCookie.pas
  単純に切り落とすと間違った判定をします。
function TIdNetscapeCookie.IsValidCookie

begin
//....(省略)
    if IsHostname(AServerHost) then
    begin
      if IsHostName(FDomain) then
        result := FDomain = RightStr(AServerHost,Length(FDomain))
       else
        result := FDomain = RightStr(DomainName(AServerHost),Length(FDomain));
    end
修正
  var ACookieDomain : String;
begin
//....(省略)
    if IsHostname(AServerHost) then
    begin
//      if IsHostName(FDomain) then
//        result := FDomain = RightStr(AServerHost,Length(FDomain))
//       else
//        result := FDomain = RightStr(DomainName(AServerHost),Length(FDomain));
      if (Copy(FDomain,1 , 1)='.') then
           ACookieDomain := FDomain
          else
           ACookieDomain := '.' + FDomain;
       Result := ( ACookieDomain = RightStr('.' + AServerHost, Length(ACookieDomain)) );
    end
IdCookieManager.pas
  ドメインの比較にPos関数を使ってはいけません。
function TIdCookieManager.GenerateCookieList

begin
//....(省略)
  if IndyPos(Uppercase(LCookiesByDomain[i]), Uppercase(URL.Host + URL.path)) > 0 then {FLX}
修正
  ACookieDomain : String;
begin
//....(省略)
//  if IndyPos(Uppercase(LCookiesByDomain[i]), Uppercase(URL.Host + URL.path)) > 0 then {FLX}
    ACookieDomain := LCookiesByDomain[i];
    if (Copy(ACookieDomain ,1 , 1) <> '.') then ACookieDomain := '.' + ACookieDomain;
    if (ACookieDomain  = RightStr('.' + URL.Host, Length(ACookieDomain))) then

注意
 Netscapeの仕様書では、/で終わらない場合、最後のパス名は部分一致になっています
 IE7もそういう動作をしているようです。
 firefox2では、部分一致にはなっていません。
 firefox2風に、/で終わるパスと同様の動作にするには以下のようにします。


 if Pos(LCookieList.Cookies[j].Path, URL.Path) = 1 then
-->
// if Pos(LCookieList.Cookies[j].Path, URL.Path) = 1 then
 if (RightStr(LCookieList.Cookies[j].Path,1) = '/') then
  begin
    if Pos(LCookieList.Cookies[j].Path, URL.Path) <> 1 then continue;
  end
  else
  begin
    if Pos(LCookieList.Cookies[j].Path + '/', URL.Path) <> 1 then continue;
  end;

 .で始まるものを保存できるように修正をしましたので
 そのままでは最初の文字が . 付きと .がついていない物と混在してしまいます。
以下のように修正します。

procedure TIdCookieManager.DoAdd

 if Length(ACookie.Domain) = 0 then LDomain := AHost
  else LDomain := ACookie.Domain;
-->
 if Length(ACookie.Domain) = 0 then LDomain := AHost
  else LDomain := ACookie.Domain;

  LDomain := Sys.Trim(LDomain);
  if ((LDomain <> '')and(IsHostname(LDomain))) then
    if (Copy(LDomain,1,1) <> '.') then LDomain := '.' + LDomain;
(省略)の部分は、ここでわかりやすくするために
省略しただけで誤解しないようにお願いします

バグの確認のためのコード DOS版 FORM版  
     ※コピーは、IEのみ動作します
ダウンロード
indy_10_src_patches
indy_mytest

ブラウザで確認したい場合 【 .で始まるクッキーの場合】

asp.netだとサンプルを作りにくいのでphpをサーバーに置きます。
---サーバーに保存します----phpのファイルです--------
<?php
  // cookie_test.php
   $s = '';
   if (isset($_COOKIE['name1'])) { $s .= 'name1='.$_COOKIE['name1']."\r\n"; }
   if (isset($_COOKIE['name2'])) { $s .= 'name2='.$_COOKIE['name2']."\r\n"; }

   setcookie ('name1','value1' ,time()+60*60*24*30, '/', '.example.com');
   setcookie ('name2','value2' ,time()+60*60*24*30, '/', 'example.com');
   header('Content-type: text/plain');

   print $s;
?>
-----------------------------------------------
example.comは、適当に自分のドメインに置き換えてください。
このファイルにIEやFirefoxでアクセスしてください。
2回ほどアクセスすると2つクッキーが表示されると思います。

しかし、Indyでは、パッチを適用しない場合は、1つしか表示できません。

バグの再現コードです。 (上のphpとセットで使用します)
-----------------------------------------------
 example code
-----------------------------------------------
procedure TForm1.Button1Click(Sender: TObject);
  var i:Integer;
begin
  Memo1.Text := IdHTTP1.Get(Edit1.Text);
  Memo2.Clear;
  for i := 0 to IdCookieManager1.CookieCollection.Count-1 do
    Memo2.Lines.Add(IdCookieManager1.CookieCollection.Items[i].CookieText);
end;

----------------------------------------------
サーバを経由しないで単体で確認したい場合は、上の DOS版を利用します




おまけ:
C:\WINDOWS\SYSTEM32\DRIVERS\etc\hosts

に、 127.0.0.数字  ホスト名[ドメイン名]
みたいに追加して、apache等のサーバーを起動すると
容易にローカルでデバッグできると思います。

-----------------------
ssl通信のはじめにエラー
最新版は、どうも一緒に配布してあるdllだと古くてエラーになるようです。
 ソースコードを見ると以下のようにありました。
 自分でコンパイル済みのdll最新版を入れるようにだと・・・

Indy OpenSSL now uses the standard OpenSSL libraries
for pre-compiled win32 dlls, see:
http://www.openssl.org/related/binaries.html
recommended v0.9.8a or later


バグ報告してあげようと思ったのですが、バグ報告ページに接続できないので、あきらめました。
(また海外のメーリングリストは、ウイルス・スパムメールの嵐に遭うので関わる気はありません。)

 ポカ。。。(((∂。∂)ン
あれ? 私は誰 いまなにしていました?
 あれれ・・・

追記:2007年7月でも修正されていなかったので、newsグループに報告しました。
追記:Snapshotをみても反応が無かったので、QualityCentralに登録しました。 openされたようです。
追記:2007.10 RightStrがTextEndsWithに変更されていることを確認しました。
           関数名が変わっただけで欠陥は直っていないようです。


Indyのバージョンの確認方法
(新規フォームにボタンを貼って、コードを置き換えてください)

uses IdAbout;

procedure TForm1.Button1Click(Sender: TObject);
begin
  IdAbout.ShowDlg
end;