Unknown::Programming このページをアンテナに追加 RSSフィード

2014-07-07 百折

PHPのクエリパラメーターの扱い方について

3年ぶりの更新というわけで、まさしく3年寝太郎状態なわけですが。

今日は今巷を賑わせているJSON SQL Injectionについてです。

徳丸さんの記事で紹介されていたように、僕が作ったSQL_Abstractでも同様の問題が発生するとのことです。

SQL::Makerのようにstrictモードを導入するかどうかも一応考えたのですが、PHP版SQL_Abstract自体PHP5でも動作はしますがPHP4時代のコードですし、そもそもそんなに使われてないだろう(泣)いうことで今回は見送ることにしました。(ちょっと今すぐ時間が取れないという状況もありまして・・・)

またstrictモードを導入したとしてもSQL_Abstractを配列で使いたいケースが結構多いと思うので結局strictモードを外して運用になるのではないかと思うので効果としては薄いのかなぁとも感じています。

でまぁSQL_Abstractではstirctモード導入しないよ!勝手に対策してよ!だとあまりに無責任な気がするので、この問題に対して僕が実際にやってる対策の一つをここで紹介しておきます。

PHPクエリ配列で取れてしまう問題というのは、これはSQL_Abstractとの相性の問題というよりは、クエリ配列で来くることを想定していないのが問題であり、そこの部分を根本的に解決しない限り他のあらゆる箇所でバグが発生する原因になるとおもいます。

徳丸さんの記事では

strictモードが使えない場合は、条件設定に与える引数の型チェックを行う方法があります。


      $user_name = $_GET['user_name'];
      if (! is_string($user_name)) {
        # エラー処理
        exit;
      }

このようにis_string等を使ってチェックする必要があるとのことでしたが、僕は根っからの面倒臭がり屋なのでいちいち毎回チェックしてエラー処理したりするのが面倒なので面倒を避けるために以下のように処理しています。

<?php

// そのまま返すやーつ
function get_param($key) {
    if ( isset($_GET[$key]) ) {
        return $_GET[$key];
    }
    return null;
}

// 常にひとつだけ返すやーつ
function get_value($key) {
    return _get_scalar(get_param($key));
}

function _get_scalar($param) {
    return is_array($param) ? _get_scalar(array_shift($param)) : $param;
}

// hoge.php?user_name[<>]=1とか渡されても大丈夫!
$where = array(
    'user_name'=> get_value('user_name')
);

上記のようなラップ関数を用意し、get_value関数を使ってクエリパラメータがどういう状態で渡されても常にひとつの値だけを取る様にしてます。



以上、ありあしたッ!

2011-12-18 不逞

MDB2で複数のDB接続を行う場合の恐ろしい罠

MDB2を使って複数のDB接続したいなんてことあるよね。

ところがどっこい、実際にコードを書いてみると恐ろしいことが起きた。それは二つ目のDB接続をすると一つ目のDB接続が二つ目のDB接続に乗っ取られてしまうという!

<?php
// fooというデータベースへ接続し、hoge_tblのカウントする
$dbh = MDB2::factory('mysql://user:pass@localhost/foo?charset=utf8');
$res = $dbh->query('SELECT COUNT(*) FROM hoge_tbl');
$row = $res->fetchRow();
$res->free();
print $row[0]; // 5件

// で次にbarへ接続し同様の処理
$dbh2 = MDB2::factory('mysql://user:pass@localhost/bar?charset=utf8');
$res = $dbh2->query('SELECT COUNT(*) FROM hoge_tbl');
$row = $res->fetchRow();
$res->free();
print $row[0]; // 2件

// そして、fooに接続してるDBハンドルでもういちどカウントしてみると・・・
$res = $dbh->query('SELECT COUNT(*) FROM hoge_tbl');
$row = $res->fetchRow();
$res->free();
print $row[0]; // 2件

このコードが5、2、2と表示されるわけです。なんでこんなことに・・・、と思って色々調べてみると、どうやら同一ホストへの接続の場合、いろいろキャッシュ的な感じでごにょごにょむにょむにょして同一の接続扱いになってしまうようです。

でまぁ原因はわかったけど、じゃあどうやって複数接続すればいいんだよ!ということで、これを明示的に別の接続として扱うためのnew_linkってオプションを発見。

<?php
// new_link=trueをつけましょう!
$dbh2 = MDB2::factory('mysql://user:pass@localhost/bar?charset=utf8&new_link=true');

こうすることで二つ目のDB接続は新しい接続だよ!と明示され、晴れて同一の接続じゃなくなりました。

いやぁnew_linkなんていう便利なオプションがあるんですね() # デフォでtrueにしとけよ!って感じですが。

ということで今頃気付いたのかよって思う人もいるかもしれませんが、知らなかった人はもしかしたら気付かない間に別のDBに接続されててバグだらけになってた、なんてことになってるかもしれないのでご愁傷様です。

2011-06-16 脱却

クラス内で定義した定数の一覧を得る方法

<?php

class Foo {
    const BAR = 1;
    const BAZ = 2;
}

get_defined_constantsを使えば全ての定数が取れると思いきや、上記のようなクラス内で定義した定数は取れなかった。

どうやったら取れるのか。PHPお得意の大量の関数の中に一覧取れるヤツが必ずあるはず!と思いきや、なかった。

諦めかけたその時、ReflectionClassというクラスを使えば取れることがわかったのだ!

<?php

class Foo {
    const BAR = 1;
    const BAZ = 2;
}
$reflect = new ReflectionClass('Foo');
print_r( $reflect->getConstants() );
$ php test.php
Array
(
    [BAR] => 1
    [BAZ] => 2
)

PHP5になってから、PHPさんは関数からの脱却を図っている模様だ。

目的のものが標準関数に存在しなかったら、標準クラスを調べてみようというお話でした。

2011-03-16 地震

Text::ASCIITableによるアスキーテーブルレイアウト

MySQLの出力結果みたいなのが必要になったのでCPAN漁ってたらこんなの発見。

Text::ASCIITable - search.cpan.org

地味に凄いめちゃ便利

use Text::ASCIITable;

my @rows = (
    [1,'foo','2011-03-16 11:22:33'],
    [2,'hogehogehoge','2011-03-17 11:22:33'],
    [3,'uwaaaaa','2011-03-18 11:22:33'],
    [4,'dio','2011-03-19 11:22:33'],
    [5,'jojo','2011-03-20 11:22:33'],
);

my $t = Text::ASCIITable->new();
$t->setCols('id','name','created');
$t->addRow($_) for @rows;
print $t;
 $ perl asciitable.pl
 .-----------------------------------------.
 | id | name         | created             |
 +----+--------------+---------------------+
 |  1 | foo          | 2011-03-16 11:22:33 |
 |  2 | hogehogehoge | 2011-03-17 11:22:33 |
 |  3 | uwaaaaa      | 2011-03-18 11:22:33 |
 |  4 | dio          | 2011-03-19 11:22:33 |
 |  5 | jojo         | 2011-03-20 11:22:33 |
 '----+--------------+---------------------'

いいですねー。

あとサクサクっとハッシュ配列構造のデータを出力する場合はこんなかんじか

use Text::ASCIITable;

my @rows = (
    {'id' => 1,'name' => 'foo', 'created' => '2011-03-16 11:22:33'},
    {'id' => 2,'name' => 'hogehogehoge','created' => '2011-03-17 11:22:33'},
    {'id' => 3,'name' => 'uwaaaaa','created' => '2011-03-18 11:22:33'},
    {'id' => 4,'name' => 'dio','created' => '2011-03-19 11:22:33'},
    {'id' => 5,'name' => 'jojo','created' => '2011-03-20 11:22:33'},
);

if ( @rows ) {
    my @keys = keys %{$rows[0]};
    my $t = Text::ASCIITable->new();
    $t->setCols(@keys);
    $t->addRow(@$_{@keys}) for @rows;
    print $t;
}

いいですねー。

他にもオプション等で色々指定できるみたいですが、とりあえずこれだけできりゃもうおkですね。

2011-03-11 精緻

PHP5.2とPHP5.3でArrayObjectの挙動が違う話

ハマッタのでメモ。

<?php

$a = new ArrayObject(array('test' => 1));

print_r($a);

これをPHP5.2とPHP5.3で実行すると以下のようになる

# PHP5.2
ArrayObject Object
(
    [test] => 1
)

# PHP5.3
ArrayObject Object
(
    [storage:ArrayObject:private] => Array
        (
            [test] => 1
        )

)

PHP5.3の場合、なにやらデータの持ち方が新しくなったのかstorage:ArrayObject:privateとか言うのが増えてる。

これはおそらくstorageというprivate変数配列のデータを持つようになったという感じか。まぁそれ自体は問題ない。

問題は以下のコード

<?php

$a = new ArrayObject(array('test' => 1));
$b = new ArrayObject($a);

print_r($b);

実行結果

# PHP5.2
ArrayObject Object
(
    [test] => 1
)

# PHP5.3
ArrayObject Object
(
    [storage:ArrayObject:private] => ArrayObject Object
        (
            [storage:ArrayObject:private] => Array
                (
                    [test] => 1
                )

        )

)

PHP5.2のときにはArrayObjectにArrayObjectを渡しても配列として処理されていたが、PHP5.3からはArrayObjectがネストされてしまう。

正直ArrayObjectってのは配列シュミレートするもののはずなのでArrayObjectをオブジェクトとして保存してしまうPHP5.3の仕様には聊か首を捻らざるを得ない。

さらに怖いのが、この状態でもちゃんと配列として動いてしまうのでネストされてることに気付かないということだ。

<?php

$a = new ArrayObject(array('test' => 1));
$b = new ArrayObject($a);

print($b['test']); # ちゃんと「1」と表示される

つまり普通に扱ってる分には配列として動いてくれるので、めちゃくちゃネストしちゃっててもぜんぜん気付かない。

気付いたときには

ArrayObject Object
(
    [storage:ArrayObject:private] => ArrayObject Object
        (
            [storage:ArrayObject:private] => ArrayObject Object
                (
                    [storage:ArrayObject:private] => ArrayObject Object
                        (
                            [storage:ArrayObject:private] => ArrayObject Object
                                (
                                    [storage:ArrayObject:private] => ArrayObject Object
                                        (
                                            [storage:ArrayObject:private] => ArrayObject Object
                                                (
                                                    [storage:ArrayObject:private] => ArrayObject Object
                                                        (
                                                            [storage:ArrayObject:private] => Array
                                                                (
                                                                    [test] => 1
                                                                )

                                                        )

                                                )

                                        )

                                )

                        )

                )

        )

)

こんなことになってるかもしれない。(ってかなってたw)

ArrayObjectを良く使う人はPHP5.3では注意しましょう!

ちなみに解決方法としては色々あるだろうけど、ArrayObjectに渡すときにarrayにキャストするとかしましょう。

<?php

$a = new ArrayObject(array('test' => 1));
$b = new ArrayObject((array)$a); // array型へキャスト