gallu’s blog

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

不必要情報の実例とヘッジ例

許可を頂いたのである意味遠慮無くw


EC-CUBEというソフトがあります。今回の実例です。
http://www.ec-cube.net/
そうですねぇ…installして、ってのが真っ当なのでそっちで行ってみていただきたいのですが。まぁ適宜、環境などご用意くださいませ。
間違っても構築事例などから「実際に運営しているところ」に対して試すとアタック*1になるのでやめておきたいところです。
私は、以前に構築していた環境(1.3.4)と、最新のソースの2種類で実験をしています。


ユーザログインのところに多くあるのですが。「パスワードを忘れちゃったよテヘ」なあわてんぼうさんのために、いわゆるパスワードリマインダという機能がありまして、それは無論このEC-CUBEにも存在をいたします。
いくつか作りには種類があるのですが。尤もポピュラーなのは「emailアドレスを入れるとそのemailに対してパスワードを返信」してくれます。
ちなみに今回の範疇からは外れるので簡単なmemoにとどめますが。返信されるパスワードが「新しいモノ」である場合の方が、その業者を安心しやすいです。というか「あなたのパスワードはXXでした」なんて返してくるところは、酷いとパスワードを「DBの中に平文でそのまま」持ってたりするので怖いです。ちなみにEC-CUBEは前者。


おいといて。


さて。実際にemailを入れてみましょう。
ここで、おおよそ2種類の挙動に種類分けをする事が出来ます。
一つは「何を入力しようとも出力が全く同一である」パターンと。
もう一つは、例えば入力ミスなどの場合に、親切に教えてくれるパターンとがあります。
EC-CUBEは後者に属します。最新のソースで確認をしてみましょう。
eccube-2.1.2a/data/class/pages/forgot/LC_Page_Forgot.php
というプログラムを眺めています。
簡単に書きますと。まず

            $sql = "SELECT * FROM dtb_customer WHERE (email = ? OR email_mobile = ?) AND status = 2 AND del_flg = 0";

というSQLによって、emailの存在確認が行われています。
また。本会員としての登録がない場合に

                $sql = "SELECT customer_id FROM dtb_customer WHERE (email = ? OR email_mobile = ?) AND status = 1 AND del_flg = 0";	//仮登録中の確認

という処理も行われています。
結果として

$this->errmsg = "ご入力のemailアドレスは現在仮登録中です。
登録の際にお送りしたメールのURLにアクセスし、
本会員登録をお願いします。";
$this->errmsg = "ご入力のemailアドレスは登録されていません";

といったエラーメッセージが出力される可能性があります。
もうちょっとほかにも処理はありますが省略。


いわゆる「ユーザビリティ」として考えた場合に、これはそれなりに便利です。
というのも、emailを手打ちする以上、一定確率でのミスの可能性があります。それに対して「登録されていないよう」と教えてくれるのは、これはなるほどユーザビリティです。
さて問題です。この「ユーザビリティ向上」によって、どのようなメリットが誰に対して付与されるのでしょうか。


暗転。
では。ちと角度を変えて眺めてみましょう。
どうやらこれらの処理は、POSTメソッドで「mode=mail_check」+emailに適当なパラメタ、で動くようです。
えとですねぇもの凄く斜めな、これだけでは如何ともしがたいであろうイメージだけを記述します。

function emailアドレスっぽい文字列を次々に作成する関数();

/* -- */
while(1) {
  $uri_post_param['mode'] = 'mail_check';
  $uri_post_param['email'] = emailアドレスっぽい文字列を次々に作成する関数();
  $ret_html = POSTで適当なURIに対してHTTPして返り値をとる関数($uri, $uri_post_param);
  if ($ret_html =~ /ご入力のemailアドレスは登録されていません/) { continue; }
  if ($ret_html =~ /ご入力のemailアドレスは現在仮登録中です/) { $仮登録メールアドレス群[] = $uri_post_param['email']; continue; }
  // else
  $生きてるってかここの会員さんのメールアドレス群[] = $uri_post_param['email'];
}

ドキドキですねぇ。
いやまぁこのままじゃどうにもならんのですがね。もうちょっと小細工したり以下検閲削除。


明転。
なにやら真っ黒い会話が一瞬行われましたが。
はい結論を書きましょう「該当サイトの全会員のメールアドレスを、その気になれば抜き出せる」が今回の正解になります。より正しくは「メールアドレスの一斉取得をクラッカーに対して提供している機能である」と言えます。


さて。なにやらナチュラルに危険な発言書いてますが。どのようにヘッジをしたらよいのでしょうか。


入り組んだ処理やらなにやらはとりあえず置いておくとしてですな。
まずは「テンプレート修正」で速やかに逃げ切りましょう。

いち。余計な文言の出力原因である「」という文字列を徹底的に駆除/削除します。

に。一応ユーザビリティを考慮してこういった類の文言を付与するとよいです。「なお、メールがn分たっても届かない場合、メールアドレスの入力ミスが考えられます。n分たってメールが届かなければ、もう一度メールアドレスを入力してみてください」。
とりあえずこれで最低限防御できます。あとは、EC-CUBEさんがそのうち何とかしてくださるであろう事を待ちましょう。


セキュリティでは。こういったものを「不必要情報」と言います。
情報は、確かに多くの場合にユーザビリティを向上させますが。それは時として「クラッカーに対してより一層のユーザビリティを」与える結果になります。
技術者たるもの。常に「アタックの目線」を忘れずにものを作らなくてはいけません。


…って書くとEC-CUBEさんがけちょんけちょんなので、少しだけフォローをw
この話は、事前にmailでのやりとりで許可を頂いてます。で、そのときの彼らの態度なのですが、少なくとも公表その他に対して大変に紳士的でした。
うんまぁ正直、クラスの切り方ディレクトリの散らかりっぷりDB&SQLの駄目っぷり今回の件のようなセキュリティその他、正直なところあまり評価をしているソフトではないのですが。
少なくとも、その真摯な態度は評価に値するのではないかと思います。っつか「隠せばばれないよねぇ」セキュリティをしないだけ、そこらの凡百の以下略よりは遙かにましなのではなかろうかと思うわけです。


…いやしかしなんですなこの手の記事、バランスが難しい orz
突っ込みその他有りましたら歓迎いたしますので是非お願いいたします。

*1:これが不正アクセス禁止法に引っかかるかというと多分ひっかかりませんが、多分業務威力妨害とかそういう方向では有罪になるのではないかと思われ。詳細は法律家ではないので不明

トラブルヘッジ magic_quotes_gpc変

いくつかの…とりあえず管理系画面ですが、たぶんそれ以外でも発生しそうなトラブル。
"が\"になって\\\"になって\\\\\\\"になる、っていういわゆるパターン。
magic_quotes_gpcをoffることで直るので、php.iniなり.htaccessなりhttpd.confなり適当に修正しませう。


こーゆーのを見るとやっぱり$_GETとか$_POSTとかを直接触らず、一枚ラッピングしたほうがいいなぁとしみじみ思うのだけど(MWだとcgi_requestクラス。一応magic_quote対策はすんでると思うけどBUGってたら気づいた人突っ込んで)。
ただ、これを「EC-CUBE側のバグ」とするには何ぼなんでも。瑕疵を問う相手はむしろPHPだと僕思う。
対応はしてほしいと思うんだけど。

ログイン周りとか

んと。とりあえず

$objCustomer = new SC_Customer();

インスタンス作ると、コンストラクタで自動的に

  • セッション開始したり
  • DBハンドルげとってきたり

してくれる。
あとは

$objCustomer->isLoginSuccess()

とかで簡単にログインチェックできるっぽなんだけど。
どうもログインした時点で

var_dump($_SESSION['customer']);

ここにユーザ一式情報が入ってるっぽ。


いぢょ。

思いっきり覚書

最近触ってるので、とりあえずせっかくなので覚書くらい、と思って。
で、…まぁぶっちゃけTipsです。
また、まじめにホゲったわけではないので、随所に「いい加減さ」が見え隠れしますので、自己責任の下に読んでください(いつものことですが)。


…で、ちょいと私信失礼します。
とりあえずこっちにいろいろ書いておくのでチェックしてください(笑 < 大将さん


大前提として。
おおむね「Webからたたくプログラム」は
install_dir/html/
配下にある*.php
で、テンプレートは、
install_dir/data/Smarty/template/
配下にある*.tpl
がとりあえず対応してます。


また、data/logs/ ディレクトリにエラーログちっくなものがあるので、見てみると面白いかも。


DBハンドルはとりあえず

$conn = new SC_DBConn();

でゲトります。
んでもって。
selectは最悪、自己責任でSQL作ってから(おいらはMWのsecurityクラス使ってるざます)

$res = $conn->getAll($sql);

こんな感じ。
$resには

入ってるっぽいです。
ほかにもいろいろあるですが最悪これだけ把握しとけばど〜にかなります。
2007/10/29 追記
レコードがない場合、array(0)が返ってくるみたいです。


後は、とりあえず

$conn->query($sql);

ひとつあればどうにかなるです。


で、もうちょいこゆいTips。
お題は「顧客情報」。
パスワードの文字列。んと………

$db用string = sha1(ぱすわぁどもぢれつ . ":" . AUTH_MAGIC);

こんな処理がなされています。


ついでに。何に使ってるかよくわからんのですが。secret_keyなるカラムがありまして。
Excelには「暗号化顧客ID」と書かれてるですが、実際には顧客IDをごにょごにょするわけではなく、

  do {
    $secret = sfGetUniqRandomId("r");
//print "$secret \n";
  } while( ($result = $conn->getOne("SELECT COUNT(*) FROM dtb_customer WHERE secret_key = ?", array($secret)) ) != 0);

という処理によってまかなわれています。
また、sfGetUniqRandomIdは
data/lib/slib.php
というファイルで

/* 一意かつ予測されにくいID */
function sfGetUniqRandomId($head = "") {
  // 予測されないようにランダム文字列を付与する。
  $random = gfMakePassword(8);
  // 同一ホスト内で一意なIDを生成
  $id = uniqid($head);
  return ($id . $random);
}

という風に宣言されています。


もひとつ。顧客情報まわりのプログラムを触る場合。
たとえば管理画面でユーザ情報を出力すると

<input type="password" name="password" value="UAhgGR3L" size="30" class="box30"  />

となることに留意しましょう。ここでvalueに設定されているのはdata/conf/conf.phpにあります

define("DEFAULT_PASSWORD", "UAhgGR3L");

という設定です。
また、たとえば html/admin/customer/edit.php などにおいて。

  //-- パスワードの更新がある場合は暗号化。(更新がない場合はUPDATE文を構成しない)
  if ($array["password"] != DEFAULT_PASSWORD) {
    $arrRegist["password"] = sha1($array["password"] . ":" . AUTH_MAGIC);
  } else {
    unset($arrRegist['password']);
  }

といった処理があるのもTipsです。


…って内容、書いとかないと確定で忘れる自信が山盛りにあるので、メモ。