saアカウント

コーディングを行う上で、Web上からサンプルを探すなんてのは、当たり前のようにやっている思う。普通の(?)プログラマならそんなことはないが、経験・見識のないプログラマだったら、下手したらコピペで済ませるヤツもいるだろう。

むやみに管理者権限使うな。
たとえば、WebアプリなどでDB接続に管理者アカウントを使っているなんてのは論外。
当たりまえのことのように思えるが、管理者アカウントを使用しているサンプルコードが多すぎる。
ちなみに、以下はSQL Serverに対してDB接続するためのサンプルコードだが、全部「sa」で接続している。

http://support.microsoft.com/kb/317880(←これなんかMS)
http://keicode.com/cgi/sql-server-driver-for-php-reference.php
http://blogs.msdn.com/b/osamum/archive/2010/09/27/php-ms-sql-server.aspx
http://www.triconsole.com/dotnet/sqlparametercollection_class.php
http://www.vacant-eyes.jp/tips/tadonet/040.aspx
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?forum=7&topic=25169
http://homepage1.nifty.com/k-umezu/nikki/niiki000506.htm
http://www.geocities.jp/ak09z/vec/vba/xlodbc.htm
http://keicode.com/cgi/how-to-connect-to-mssql.php
http://d.hatena.ne.jp/yourvoice/?of=5

もちろん、サンプルはあくまでサンプルなので、利用する側のリテラシーが第一だが載せるほうも考えよう。

「暗号化」と「ハッシュ」

「暗号化」と「ハッシュ」について、よく混同されていると思う。

ここで、一度定義を整理してみる。

ハッシュ・・・あるデータが与えられた場合にそのデータを代表する数値を得る操作
暗号化・・・第3者に内容を知られないように行う方法のうち、特別な知識なしでは読めないように変換する表記法

「ハッシュ」は「暗号化みたいなもの」という説明をよく聞くが、正確には異なる。
確かに、元々の意味や実体を一見してわかりづらくするという観点から言えば、
「ハッシュ」も暗号化の一部として捉えることもできなくない。
しかしながら、ハッシュとは本来「秘匿」を目的にしたものではない。
あるデータを端的に表すために行う特殊な計算で、概念としてはチェックサムとかチェックディジットとか
と同様なものだ。セキュリティの世界ではよく改ざんの検知に使用される。

このハッシュ化するアルゴリズムの代表的なものとして「MD5」と「SHA1」がある。
「aaa」という文字列をMD5変換すると、「47bce5c74f589f4867dbd57e9ca9f808」となり、
SHA1変換すると「7e240de74fb1ed08fa08d38063f6a6a91462a815」となる。
そして、ポイントはハッシュは「不可逆」であるということ。
上記、「47bce5c74f589f4867dbd57e9ca9f808」から「aaa」を導くことは不可能なのだ。

暗号化は「第3者に内容を知られないようにする方法」である。
つまり、第3者以外、通信する本人と相手方は内容を知る必要がある(当然といえば当然だが)。
ハッシュは不可逆であるが故に、相手方すら内容がわからない。
「47bce5c74f589f4867dbd57e9ca9f808」を受け取っても、それから「aaa」を導くことが
不可能なため、秘匿通信という要件を満たさない。よって、これは暗号化とは呼べない。

よくMD5は脆弱だ。解読可能だ。という話が出ているが、
これも「47bce5c74f589f4867dbd57e9ca9f808」から直接なんらかの計算で「aaa」を
導くわけではなく、大量のサンプリングを得た結果から導くものだ。

通信するお互い(だけ)が内容を知る必要があるため、暗号は「可逆」だ。
それを可能にするのが「鍵」である。
平文を「鍵」を使用して特殊な計算をして暗号化する。
受け取った側はまた「鍵」を用いて暗号文を、これまた特殊な計算で平文にする。
この「鍵」は、送受信側で共通のものであったり、異なったものであったりする。

暗号化には必ず「鍵」が必要になる。
内容を秘匿するために「パスワードつきZIP」をかけて、メール送信することはよくある。
これも暗号化に他ならない。パスワードが「鍵」になるわけだ。
ハードディスクの暗号化もパスワード(鍵)を必ず必要とする。

古典的な暗号化の方法として、アルファベットを1文字ずらすなんていう方法もある。
あらかじめ、送信者と受信者でその約束を共有しておく。

「abc」という内容を相手方に知らせるために「bcd」と送る。
相手方は1文字ずらしているということを知っているので、「abc」という内容
だということがわかる。
この場合、「一文字ずらず」という約束事が「鍵」になるわけだ。

当然、この程度のアルゴリズム(とまでは言えないが)であれば、
暗号文を見ただけで、あっという間に解読されてしまうだろうが、
現在使われている暗号化も原則は変わらない。

当人しか知り得ないものがあって、初めて暗号化が成立する。

アルゴリズムがいくら強力であっても、その「当人しか知りえないもの」が漏洩
してしまっては、基も子もない。

巷にはさまざまな暗号化ソリューションがあふれているが、
どんな優秀な製品を導入しようとも、この「鍵」管理を怠ってしまっては、
まったく意味のないものになってしまう。

ハッシュと暗号化の違いも含めて、一見難しそうだと敬遠しがちな部分だと思う。
これは、MD5だの、SHAだの、RSAだの、SSLだの、ビット長だのとアルゴリズム
方にフォーカスがいきがちだからだと思われる。
しかしながら、原理・原則は単純なものなので、しっかりと把握したい。

Visual Studio 2010 での ISAPI 開発

このたび、えらく昔に開発したISAPIフィルタを更新する必要がでてきた。そのため、開発環境も新たに Visual Studio 2010 (以下、VS2010) に移行することとした。さて、とりあえず、現状のプロジェクトを VS2010 で開いて、自動アップグレードを実行。

とくに問題なく終わった。さて、ビルド。すると、「afxisapi.h」がないと怒られる。どうやら、VS2008から ISAPI開発用のMFCラッパが無くなってしまったようだ。

CHttpServer not included in Visual Studio 2008
http://blogs.msdn.com/b/jpsanders/archive/2007/12/10/chttpserver-not-included-in-visual-studio-2008.aspx

この記事によると、必要なファイルやライブラリをVS2005が頂戴すればいいらしいが、MFCをスタティックリンク設定しなければならないらしい。

これは、これで問題がある。なんとか、MFCを共有ライブラリとしてビルドしながら、従来通りのAFXラッパを使用してVS2010でISAPIの開発ができないものか。

いろいろ、ためしてみたところ、上記、記事にあるヘッダファイルやライブラリをVS2005から頂戴してくるところはそのままに、MFC共有ビルド可能なようにするには、以下のような定義を StdAfx.h に入れることで、可能なようだ。

namespace ATL
{
#ifndef _CONVERSION_DONT_USE_THREAD_LOCALE
typedef UINT (WINAPI *ATLGETTHREADACP)();

inline UINT WINAPI _AtlGetThreadACPFake() throw()
{
	UINT nACP = 0;
	LCID lcidThread = ::GetThreadLocale();
	char szACP[7];
	if (::GetLocaleInfoA(lcidThread, LOCALE_IDEFAULTANSICODEPAGE, szACP, 7) != 0)
	{
		char* pch = szACP;
		while (*pch != '\0')
		{
			nACP *= 10;
			nACP += *pch++ - '0';
		}
	}
	if (nACP == 0)
	nACP = ::GetACP();

	return nACP;
}
inline UINT WINAPI _AtlGetThreadACPReal() throw()
{
	return( CP_THREAD_ACP );
}

extern ATLGETTHREADACP g_pfnGetThreadACP;

inline UINT WINAPI _AtlGetThreadACPThunk() throw()
{
	OSVERSIONINFO ver;
	ATLGETTHREADACP pfnGetThreadACP;
	ver.dwOSVersionInfoSize = sizeof( ver );
	::GetVersionEx( &ver );
	if( (ver.dwPlatformId == VER_PLATFORM_WIN32_NT) && (ver.dwMajorVersion >= 5) )
	{
		pfnGetThreadACP = _AtlGetThreadACPReal;
	}
	else
	{
		pfnGetThreadACP = _AtlGetThreadACPFake;
	}
	InterlockedExchangePointer( reinterpret_cast< void** >(&g_pfnGetThreadACP), reinterpret_cast< void** >(pfnGetThreadACP) );

	return( g_pfnGetThreadACP() );
}

ATLGETTHREADACP g_pfnGetThreadACP = _AtlGetThreadACPThunk;

#else

inline UINT WINAPI _AtlGetConversionACP() throw()
{
	return( CP_ACP );
}

#endif
};  

お困りの方、試してみてください。

FTPクライアントソフトのパスワード盗難

さて、Gumblar。さまざまなFTPクライアントソフトからパスワードを盗み出す。IEなんかのオートコンプリートの情報からも盗み出す。オートコンプリートについては前回の記事に書いたとおり前々から危険性があった。おそらくこの方法に近いことをしてパスワードを盗んでいるのだろう。

被害にあったFTPクライアントアプリは結構大きな数で、有名どころはほとんど含まれている。その中にFFFTPも入っている。これはオープンソースなので容易に解析される。FFFTPはホストごとに設定したパスワード情報を暗号化してレジストリに格納している。



デコード方法もソースを見ればそこに書かれているので、その通りやればパスワードを平文でいとも簡単に取得できる。

以下は以前のバージョン(ver.1.96d)のソースで、デコード処理の部分。

static void DecodePassword(char *Str, char *Buf)
{
  unsigned char *Get;
  unsigned char *Put; 
  int Rnd;
  int Ch;

  Get = (unsigned char *)Str;
  Put = (unsigned char *)Buf;
  while(*Get != NUL)
  {
    Rnd = ((unsigned int)*Get >> 4) & 0x3;
    Ch = (*Get & 0xF) | ((*(Get+1) & 0xF) << 4);
    Ch <<= 8;
    if((*Get & 0x1) != 0)
      Get++;
    Get += 2;
    Ch >>= Rnd;
    Ch = (Ch & 0xFF) | ((Ch >> 8) & 0xFF);
    *Put++ = Ch;
  }
  *Put = NUL;
  return;
}

今回のGumblar騒ぎでFFFTPはバージョンアップされた。現在の最新バージョンはVer.1.97aだ。このバージョンから暗号化にAESが使用されている。

AESだから安心なのではなく重要なのはキーだ。本人しか知り得ないキーを暗号化の際に使用することが大事なわけで、そのあたりも開発者は当然ながらわかっているので、「マスターパスワードを設定してください。」と言っている。この「マスターパスワード」が使用されてFTPパスワードがAES暗号化される。

いまさらながら、前回までのバージョンについて、アルゴリズム上、キー設定もなく、デコードコードが公開されている状態で「暗号化」とは.....

「マスターパスワード」はSHA1でハッシュ後、レジストリに格納している。毎回、起動時に設定したパスワードを入力させ、入力値のSHA1ハッシュとレジストリに設定されているハッシュとを比較している。これは認証処理の王道だね。

ちなみに以下が新バージョンのソース内のデコード処理部分のコード。

static void DecodePassword3(char *Str, char *Buf, const char *Key)
{
  char *Get;
  unsigned char *EncBuf;
  size_t StrLen;
  size_t IvIndex;
  size_t EncBufIndex;
  size_t EncBufLen;
  unsigned char AesKey[32];
  unsigned char AesCbcIv[AES_BLOCK_SIZE];
  aes_decrypt_ctx Ctx;

  Buf[0] = NUL;

  Get = Str;
  StrLen = strlen(Str);

  if(AES_BLOCK_SIZE * 2 + 1 < StrLen)
  {

    EncBufLen = (StrLen - 1 ) / 2 - AES_BLOCK_SIZE;
    if((EncBuf = malloc(EncBufLen)) != NULL)
    {
      for(IvIndex = 0; IvIndex < AES_BLOCK_SIZE; IvIndex++)
      {
        AesCbcIv[IvIndex]  = hex2bin(*Get++) << 4;
        AesCbcIv[IvIndex] |= hex2bin(*Get++);
      }

     if(*Get++ == ':')
     {
       if(CreateAesKey(AesKey, Key) == SUCCESS)
       {
         aes_decrypt_key(AesKey, 32, &Ctx);

         for(EncBufIndex = 0; EncBufIndex < EncBufLen; EncBufIndex++)
         {
           EncBuf[EncBufIndex]  = hex2bin(*Get++) << 4;
           EncBuf[EncBufIndex] |= hex2bin(*Get++);
         }

         if(aes_cbc_decrypt(EncBuf, Buf, EncBufLen, AesCbcIv, &Ctx) == EXIT_SUCCESS)
         {
           Buf[EncBufLen] = NUL;
         }
       }
     }

     free(EncBuf);
   }
 }
 return;
}

スターパスワードを設定しなければ、↑の第3引数が固定(空文字かな)になるので意味がない。なので、FFFTPを使用している人は「バージョンアップしたから大丈夫」ではなく、ちゃんと「マスターパスワード」を設定しましょう。



と、それはそれで置いておいて、言いたいのはこれではなく、同じパスワードを使いまわすな!ということ。さんざんセキュリティ上よろしくないと言われているけど、なかなか徹底できていないんじゃないかな。

今回の場合だと、この「マスターパスワード」に設定したパスワードと同じパスワードをどこかのまったく別のサイトで使用していて、IEのオートコンプリートに保存されていたら、前回の記事で書いたこととの合わせ技でFTPパスワードが盗まれてしまう。

確かにパスワードをいっぱい覚えるのは面倒くさいけどね。でも、とくにサイト管理をしているような人はそれなりの責任を背負っているわけだから、しっかりと意識してね。

IEのオートコンプリートは無効にしたほうがいいことを再認識した

毎回パスワード入力するのがめんどい。IEとかにはユーザが入力したIDとパスワードを記憶させることができる。キーパンチしないのでキーロガーの対策にはいいのではと思いがち。でも、このオートコンプリートの情報は簡単に取り出せるといわれて久しい。どのくらい簡単なんだろうと思って調べてみた。

保存されているパスワード情報を確認できるソフトは昔からけっこうある。


[HideSeek]
http://www.vector.co.jp/soft/win95/util/se312032.html
[認術修業]
http://www.vector.co.jp/soft/win95/util/se290270.html


ほんとに簡単に取り出せそうだ。

このIEオートコンプリートやOutlookのクレデンシャルの情報はWindowsによって暗号化され、レジストリの保存されている。この部分を司さどっているのがProtected Storage Service。このサービスがクレデンシャル情報の暗号・複合を行っているが、APIが用意されていて、基本的にどんなアプリケーションからも利用できるようだ。

このAPIを利用してIE7以降からオートコンプリート文字列を取得するサンプルがあった(↓)。


[IE7におけるパスワード等の操作要領]
http://www.sapporoworks.ne.jp/ie7_pass/


ソースをダウンロードしたところ、実はそのままでは動かなかったがチョチョッと修正したら見事に動いた。ソースを見たところ、ログインしているユーザのIEのオートコンプリート情報は「HKEY_CURRENT_USERS\Software\Microsoft\Internet Explorer\IntelliForms\Storage2」に保存されているらしい。



値の名前がURLのハッシュ値SHA1)。値がオートコンプリート情報で暗号化されバイナリで保存されている。URL部分はハッシュなので不可逆。じゃあ、どうやってURL文字列を取り出しているかというと、実はアクセスされたURLの履歴情報を別途取得し、その値をSHA1でハッシュしたものと比較しているのだ。

このURL履歴情報はCOMの「IUrlHistoryStg2」インターフェースを使用することで取得可能だ。URLが判明したところで、肝心の暗号化されているオートコンプリート情報だが、「CryptUnprotectData」という「Crypt32.dll」に含まれるAPI一発で複合化できてしまうらしい。

いや、やってみると、ほんとに簡単だ。以下取得したオートコンプリート情報。なんだが、平文でパスワードが表示されているとゾッとする。



今更ながらこれは怖い。そういえば Windows Fire Wall の穴もそれ用のAPIあっさり空けることができたっけ。Windows APIってけっこうオープンだ。なんか制限できないのかな。実行しているプロセスの権限トークンを使ったファイル単位でのファイルシステムのアクセス制御ではなく、もっと、なんかこう、MSの証明書がついてないプログラムからのアクセスは不可とか。そもそも公開APIを絞るとか。開発できるアプリの柔軟性とか拡張性とか考えるとなかなか難しいのかな。

ちなみに、IEのオートコンプリートを無効にするには、メニューバーから「ツール」-「インターネットオプション」-「コンテンツ」タブ-「オートコンプリート」-「設定」で行うことができる。


IISのログファイル文字コード

気がつけばもう9月。結局8月は完全にサボってしまった。夏休みやらなんやらでどうも書く気になれず、これといったネタもなかった。これ以上、間を空けるわけにはいかない!ということで、掲題について書いてみる。

IISのログファイルの文字コードは、IIS7からデフォルトでUTF-8になった。もちろんANSIにも変更できるし、IIS6でもUTF-8に変更することができた。実はログパーサー的なもの(ログファイルをスキャンして攻撃痕跡はないかとか、いろんな統計情報を収集したりするもの)をつくる必要があって、このファイル文字コードを気にする必要があった。ファイルをUTF-8として開くのかANSIで開くのか...これは開発する上で非常に重要なことだ。

開発環境によって内部的な文字コードは異なるが、少なくともファイルを開く前にそのファイルがどの文字コードで記述されているかを知っておく必要がある。もちろん、自動判別機能をつけられればそれにこしたことはないんだが、あまたある文字コードにすべて対応するのは非現実的だ。

Webサーバのログなんて、ASCII部分だけ見ればよいかもしれないが、たとえばSQLインジェクションなんかを受けた際に500エラーで返る場合がある(というか攻撃者はそれを狙っている)。このときODBCなどから出力されるエラー文字列がログファイルに出力されるがこれがとても意味をもつことがある。実際にエラー文字列に個人情報が含まれているログを見たことがある。



でまあ、そんなこんなで、実際にIIS7で出力されるログファイルを確認してみた。

■IIS7のログファイルでUTF-8を選択(u_ex090901.log)
2009-09-01 07:54:16 192.168.11.218 GET /index.htm aaa=ああああああ 6530 - 192.168.11.199 Mozilla/4.0+(compatible;+MSIE+8.0;+Windows+NT+5.2;+Trident/4.0;+.NET+CLR+1.1.4322;+.NET+CLR+2.0.50727;+.NET+CLR+3.0.4506.2152;+.NET+CLR+3.5.30729) http://192.168.11.218:6530/index.htm?aaa=縺ゅ≠縺ゅ≠縺ゅ≠ 

■IIS7のログファイルでANSIを選択(ex090901.log)
2009-09-01 08:06:54 192.168.11.218 GET /index.htm aaa=ああああああ 6530 - 192.168.11.199 Mozilla/4.0+(compatible;+MSIE+8.0;+Windows+NT+5.2;++Trident/4.0;+.NET+CLR+1.1.4322;+.NET+CLR+2.0.50727;+.NET+CLR+3.0.4506.2152;+.NET+CLR+3.5.30729) http://192.168.11.218:6530/index.htm?aaa=縺ゅ≠縺ゅ≠縺ゅ≠ 


両者とも全く同じものが出力される。クエリストリングである「ああああああ」の部分はSJISリファラである化けている部分はUTF-8でいうところの「ああああああ」だ。違いがない。ODBCエラーを発生させてエラーメッセージを見てみた。

■IIS7のログファイルでUTF-8を選択
2009-09-01 08:24:08 192.168.11.218 GET /asptreebbs11/treebbs.asp |32|80004005|[Microsoft][ODBC_Driver_Manager]_データ_ソース名および指定された既定のドライバが見つかりません。 80 - 192.168.11.218 Mozilla/4.0+(compatible;+MSIE+8.0;+Windows+NT+6.0;+Trident/4.0;+SLCC1;+.NET+CLR+2.0.50727;+.NET+CLR+3.5.30729;+.NET+CLR+3.0.30729) 500 0 0 1250


日本語の部分はSJISだ。これはメッセージの発行元であるODBCSJISでメッセージを出力しているからだろう。結局のところ、UTF-8ANSI、どちらを選択しても、リファラも含めて入力された文字コードを素直にログに出力しているようだ(これはフォレンジックという意味からは正しいのかもしれない)。つまり、ファイル内で文字コードが混在することになる。じゃあIISのファイル文字コードの設定はなんのためにあるのか?何に違いがあるのか?

よく分らなくなってきた。

次に、ためしに日本語のファイル名のhtmを公開しブラウザよりアクセス、ログを確認したところ「cs-uri-stem」の部分(パラメータ部分を除いたURL)に違いがみられた。

■IIS7のログファイルでUTF-8を選択
2009-09-02 01:10:30 192.168.11.218 GET /繝・せ繝・htm - 6530 - 192.168.11.151 Mozilla/4.0+(compatible;+MSIE+8.0;+Windows+NT+5.2;+Trident/4.0;+.NET+CLR+1.1.4322;+.NET+CLR+2.0.50727;+.NET+CLR+3.0.4506.2152;+.NET+CLR+3.5.30729) - 200 0 0 1875

■IIS7のログファイルでANSIを選択
2009-09-02 01:14:14 192.168.11.218 GET /テスト.htm - 6530 - 192.168.11.151 Mozilla/4.0+(compatible;+MSIE+8.0;+Windows+NT+5.2;+Trident/4.0;+.NET+CLR+1.1.4322;+.NET+CLR+2.0.50727;+.NET+CLR+3.0.4506.2152;+.NET+CLR+3.5.30729) - 200 0 0 1015

ちゃんとファイル文字コードの設定に合わせて「cs-uri-stem」の部分が変化した。
次にリファラが「http://localhost/テスト.htm?aaa=テストテストテスト」となるようなリクエストを投げ、ログを確認した。

■IIS7のログファイルでUTF-8を選択
2009-09-02 01:29:54 192.168.11.218 GET /繝・せ繝・htm - 6530 - 192.168.11.151 Mozilla/4.0+(compatible;+MSIE+8.0;+Windows+NT+5.2;+Trident/4.0;+.NET+CLR+1.1.4322;+.NET+CLR+2.0.50727;+.NET+CLR+3.0.4506.2152;+.NET+CLR+3.5.30729) http://localhost/繝・せ繝・htm?aaa=繝・せ繝医ユ繧ケ繝医ユ繧ケ繝・304 0 0 1171

■IIS7のログファイルでANSIを選択
2009-09-02 01:26:40 192.168.11.218 GET /テスト.htm - 6530 - 192.168.11.151 Mozilla/4.0+(compatible;+MSIE+8.0;+Windows+NT+5.2;+Trident/4.0;+.NET+CLR+1.1.4322;+.NET+CLR+2.0.50727;+.NET+CLR+3.0.4506.2152;+.NET+CLR+3.5.30729) http://localhost/繝・せ繝・htm?aaa=繝・せ繝医ユ繧ケ繝医ユ繧ケ繝・304 0 0 0


両方ともUTF-8で出力された。うーむ。ちなみに、UTF-8ログファイルにBOMはついていない。

MSのIISログファイルの文字コード設定については以下のような記述がある。

ログ ファイルのエンコード方式を UTF-8ANSI から選択します。

シングルバイトおよびマルチバイトの文字が同一の文字列に混在する場合は [UTF-8] を選択します。 このエンコード方式では、英語以外の言語の W3C 拡張形式、IIS 形式、NCSA (National Center for Supercomputing Applications) 共通形式などのテキスト ベースのログを読み取ることができます。 また、Web サーバーで、サーバーの既定のコード ページによってサポートされていない言語または方言で URL を処理する場合、ログ内容に対して UTF-8 エンコード方式を有効にする必要があります。

既定で、IIS は、サーバーの既定のコード ページ以外のコード ページで URL の処理を試みます。 セキュリティの観点からも、攻撃により UTF-8 の URL が既定のコード ページに正しく変換されない可能性があるため、UTF-8 形式を有効にしておく必要があります。


うーむ。何を言っているのか...理解力がないのでいまいち飲み込めない。つぎのような記述も。

"UTF-8" は、シングルバイト系およびマルチバイト系の文字を同一の文字列で混在させることができるエンコード方式です。英語以外の言語でテキストベースの (W3C 拡張、IIS、および NCSA 共通) ログ ファイルを読み取る場合、UTF-8 形式を有効にすることができます。たとえば日本語のシステムで中国語の URL を処理する場合など、ネイティブ サーバー コード ページ以外の言語で URL を処理する場合にも、UTF-8 形式が必要になります。また、既定で IIS 6.0 は、サーバーの既定のコード ページ以外のコード ページで URL の処理を試みるので、セキュリティ上の理由からも、UTF-8 形式を有効にしてください。UTF-8 形式の URL による攻撃を受けた場合、既定のコード ページに正しく変換されないこともあるので、UTF-8 形式を有効にすることは、セキュリティに対する適切な方法です。

つまり、こういうこと?
たとえば(あまりありえないと思うが)、(/テスト/テスト.aspx)に対して、UTF-8エンコードしたクエリ(t=パラメータ)を送信した場合、ANSIログ設定の場合、「cs-uri-stem」の部分(/テスト/テスト.aspx)は「SJIS」で出力され、「cs-uri-query」部分(t=パラメータ)は「UTF-8」で出力され、リファラ部分(ついている場合は)は「UTF-8」で出力されることになる。これでは、1行に複数の文字コードが含まれることになり(単にテキストエディタで開いた場合はどっちかが文字化けすることになり)、フォレンジック調査で効率が悪いと言っているのか??

それを「セキュリティの観点」というにはやや大袈裟な気がする.....。パラメータの部分は何もUTF-8でくるとは限らんし(EUCも大いにありうる。Yahooオークションなんか。).....。

.NETアプリケーションの権限偽装方法

まぁ、今回はセキュリティというよりかは、どちらかというと開発ネタでの How To になるんだけど、ちょっと、前回の日記から間が空いてしまったので書いてみる。.NETのアプリケーションからリモートサーバのリソースを使いたいというときは多々あると思われる。たとえば、共有フォルダのファイルをいぢりたいとか...などなど。
そのときに問題になるのが権限回り。Webアクセスされた匿名ユーザは IUSER_XXXX だし、IISのワーカープロセスの実行権限は NETWROK SERVICE だ(IIS6以降の場合)。このWebアプリケーションの実行権限回りの話はややこしいのが、いずれにしても Guest 並みの低い権限でしかない。
このようなWebアプリケーションからリモートのリソースにアクセスしようとする場合、そのリソースをいぢれる権限に偽装して処理を行わなければならない。ただ単純にこれを実現しようと思ったら、実際のところこれはそれほど難しくない。
まず評価環境の説明から。あるサーバ(192.168.11.146)に共有フォルダ(test_work)を設定。その配下にサブフォルダ (subfolder) を設置。このフォルダにはいくつかのファイルがある。こんな感じ。




で、このサブフォルダには、192.168.11.146 のローカルユーザ(test)のみがアクセス可能となっている。こんな感じ。



では、Webアプリケーションからこの共有フォルダ内のファイル一覧を取得することをやってみよう。結論を言ってしまえば advapi32.dll の LogonUser() を使えばよい。というだけになってしまうんだけど、以外と動くサンプルが少ないみたいで使い方がイマイチ分かりづらいみたい。一応、以下がC#で書いた場合のサンプルコードになる。

  1: <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
  2: <%@ Import Namespace="System.Security.Principal" %>
  3: <%@ Import Namespace="System.Runtime.InteropServices" %>
  4: <%@ Import Namespace="System.IO" %>
  5:
  6: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  7:
  8: <script runat="server">
  9:  [DllImport("advapi32.dll", SetLastError = true)]
 10:   static extern bool LogonUser(
 11:   string principal,
 12:   string authority,
 13:   string password,
 14:   LogonSessionType logonType,
 15:   LogonProvider logonProvider,
 16:   out IntPtr token);
 17:    
 18:  [DllImport("advapi32.dll", SetLastError = true)]
 19:   static extern bool DuplicateToken(
 20:   IntPtr existingTokenHandle,
 21:   int SECURITY_IMPERSONATION_LEVEL,
 22:   ref IntPtr duplicateTokenHandle);
 23:  
 24:  [DllImport("kernel32.dll", SetLastError = true)]
 25:   static extern bool CloseHandle(IntPtr handle);
 26:   enum LogonSessionType : uint
 27:   {
 28:    Interactive = 2,
 29:    Network,
 30:    Batch,
 31:    Service,
 32:    NetworkCleartext = 8,
 33:    NewCredentials
 34:   }
 35:   enum LogonProvider : uint
 36:   {
 37:     Default = 0,
 38:     WinNT35,
 39:     WinNT40,
 40:     WinNT50
 41:   }
 42:
 43:  protected void getbtn_Click(object sender, EventArgs e)
 44:  {
 45:    IntPtr token = IntPtr.Zero;
 46:    IntPtr duptoken = IntPtr.Zero;
 47:       
 48:    try
 49:    {
 50:      bool ret = LogonUser("test", "192.168.11.146", "testpass", LogonSessionType.NewCredentials, LogonProvider.Default, out token);
 51:      if (ret)
 52:      {
 53:        ret = DuplicateToken(token, 2, ref duptoken);
 54:        if (ret)
 55:        {
 56:            WindowsIdentity newIdentity = new WindowsIdentity(duptoken);
 57:            WindowsImpersonationContext impersonatedUser = newIdentity.Impersonate();
 58:            DirectoryInfo dirInfo = new DirectoryInfo(@"\\\\192.168.11.146\\test_work\\subfolder");
 59:            FileInfo[] files = dirInfo.GetFiles();
 60:            if (impersonatedUser != null)
 61:               impersonatedUser.Undo();
 62:
 63:            Response.Write("</p>Files: <br/>");
 64:
 65:            foreach (FileInfo file in files)
 66:            {
 67:               Response.Write(file.FullName + "<br/>");
 68:            }
 69:         }
 70:         else
 71:         {
 72:            Response.Write("</p>Failed: " +
 73:            Marshal.GetLastWin32Error().ToString());
 74:         }
 75:       }
 76:       else
 77:       {
 78:          Response.Write("</p>Failed: " +
 79:          Marshal.GetLastWin32Error().ToString());
 80:       }
 81:     }
 82:     catch
 83:     {
 84:     }
 85:     finally
 86:     {
 87:       if (token != IntPtr.Zero)
 88:         CloseHandle(token);
 89:     }
 90:   }
 91:   
 92:</script>
 93:
 94:<html xmlns="http://www.w3.org/1999/xhtml" >
 95: <head runat="server">
 96:   <title>Test</title>
 97: </head>
 98: <body>
 99:   <form id="form1" runat="server">
100:   <div>
101:    <asp:Button ID="getbtn" runat="server" OnClick="getbtn_Click" Text="Get file list" />
102:   </div>
103:  </form>
104: </body>
105:</html>


要は50行目の LogonUser() に以下を与えればよい。


1st arg ユーザ名
2nd arg ドメイン名あるいはコンピュータ名
3rd arg パスワード
4th arg ログオン動作タイプ
5th arg ログオンプロバイダ
6th arg トークンハンドル(out)


MSDN:http://msdn.microsoft.com/ja-jp/library/cc447468.aspx

(当然ながら、サーバ側でクレデンシャル情報を保持するのなら暗号化が必要になると思われる)

そして、上のコードでいえば、57行目から Undo() が呼ばれるまでの処理が偽装された権限で動作することになる。今回の場合はサーバの共有フォルダ内のファイル一覧を取得する処理だ。実行した結果が以下で、ファイルリストの取得ができた。



さて、この方法。今回はリモードの共有フォルダのファイル一覧を取得するだけで、偽装するユーザもリモードマシンの一般ユーザだったけど、当然、管理者権限などをもつユーザにも(クレデンシャルがわかっていれば)権限昇格できる。Webアプリケーションに対して、たとえ一部の処理だけとは言え高権限を持たせることは、セキュリティ上好ましくないという向きもある。
そこで、高権限を持たせる処理をWebアプリケーションから分離し、ATL COMインターフェースを持つNTサービスなどの外部プロセスに持たせ、そのプロセスを高権限で実行させ、そのプロセスへのアタッチ権のみをWebアプリケーションに与えるような方法もある。


その方法については.......いずれ機会があれば。