Indy Cookie 2007
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; |
|
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; |
|
(省略)の部分は、ここでわかりやすくするために 省略しただけで誤解しないようにお願いします |
※コピーは、IEのみ動作します
バグの確認のためのコード DOS版 | |
program testCookieManager; {$APPTYPE CONSOLE} uses IdCookieManager,IdURI, IdGlobal; var TestCount : integer = 0; function CookieManager_check(Cookie, HostName , AccessURL : string; Accept : Boolean; mesg : string) : Integer; var aURI : TIdURI; IdCookieManager1 : TIdCookieManager; s : string; isError : boolean; begin Result := 0; aURI := TIdURI.Create(AccessURL); IdCookieManager1 := TIdCookieManager.Create(nil); try try IdCookieManager1.CookieCollection.Clear; IdCookieManager1.AddCookie(Cookie, HostName); IdCookieManager1.AddCookie('Set-Cookie: name2=value2; path=/tea/; domain=dummy', 'dummy'); // aURI.URI := AccessURL; s := IdCookieManager1.GenerateCookieList(aURI,False); if (Accept) then isError := (s = '') else isError := (s <> ''); if (isError) then begin Result := 1; Inc(TestCount); Writeln(' _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ ' ,TestCount); Writeln(mesg); Writeln(' Cookie '+Cookie); Writeln(' CookieHostName '+HostName); Writeln(' AccessURL '+AccessURL); Writeln(' GenerateCookieList('+aURI.URI+') = ' + s); end; except Result := -1; Writeln('internal error'); end; finally aURI.Free; IdCookieManager1.Free; end; end; function TestCookie() : Integer; var r : integer; begin Result := 0; // r := CookieManager_check('Set-Cookie: name1=value1; path=/; domain=localhost' r := CookieManager_check('Set-Cookie: name1=value1;' , 'abc.test' , 'http://testabc.test/index' , false , 'bug: send other domain''s cookie'); if (r<>0) then Result := r; r := CookieManager_check('Set-Cookie: name1=value1;' , 'deny.abc.abc' , 'http://nodeny.abc.abc/index' , false , 'bug: send other domain''s cookie'); if (r<>0) then Result := r; r := CookieManager_check('Set-Cookie: name1=value1;' , 'all.abc' , 'http://all.abc/index' , true , 'bug: can not get own cookie'); if (r<>0) then Result := r; r := CookieManager_check('Set-Cookie: name1=value1; domain=.all.abc' , 'all.abc' , 'http://all.abc/index' , true , 'bug: can not get own cookie'); if (r<>0) then Result := r; r := CookieManager_check('Set-Cookie: name1=value1; path=/;' , 'denyall.abc' , 'http://denyall.abc.localhost/index' , false , 'bug: send other domain''s cookie'); if (r<>0) then Result := r; r := CookieManager_check('Set-Cookie: name1=value1; path=/;' , 'denyall.abc' , 'http://denyall.bc/index' , false , 'bug: send other domain''s cookie'); if (r<>0) then Result := r; if (Result = 0) then Writeln(' ok : testCookieManager'); end; begin Writeln(gsIdProductName + ' ' + gsIdVersion); Halt(TestCookie()); end. |
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;