なんと2年半ぶりの復活、第4弾です。

今回は配布プラグインのセキュリティ対策がダメダメだった件に関するメモ(ちょこちょこ記述を書き足し中)。

セキュリティ対策で大事なのは「意識」だそうですが、僕の場合でいくと

  • そもそもまったくの基本(入力データチェック)がなってない、意識すっぽ抜けの例
  • 意識してたらしいが、機能追加した部分に基本対策の適用を忘れたっぽい例
  • 意識してたらしいが、入力データのサニタイズだけで満足してしまい、処理時のログインチェックを怠っていた例
  • Nucleusコアにからむものは信用できるものと考えて扱ってた例

こんな感じでした・・・。
以下はやや具体的な処置例。

基本

外から来る値に対して内容をチェックし、不正なものは修正する。もしくは適切にエスケープする、エラーにする、など。 これを意識してプラグインを書く。

入力メソッドが決まってるなら絞り込む

$_REQUEST よりも、$_GET or $_POST
Nucleusの場合、requestVar() よりも、getVar() or postVar()

整数で受けるべき値に対する処理

intval($value)
(int)$value

intGetVar('value')     //nucleus
intPostVar('value')    //nucleus
intRequestVar('value') //nucleus
intCookieVar('value')  //nucleus

ちなみに最近出たパッチプラグインの役割の1つは、コア側できちんと整数チェックされてなかった $blogidおよび$archivelistを整数化すること。

文字列を期待する値に対する処理

入力値が期待するパターンに収まってなかったらデフォルト値を適用するとか、空にしてしまうとか。
それが面倒だったら有無を言わさずエスケープ処理してしまうとか。

入力値をHTMLに出力するときのエスケープ方法

クロスサイト・スクリプティング(XSS)対策として。

strip_tags($value) //タグを除去
htmlspecialchars($value, ENT_QUOTES) //タグに使われる特定文字をエスケープ※

htmlspecialchars:シングルクォートも対象にすると良い(タグの属性値を埋め込む際にシングルクォートを使う場合もあるため)

この入力値は数値で来るはずだからエスケープは必要ない、とか思わず(そういうところを狙われる)、画面に出力する値はなんでもかんでもエスケープしとけ、くらいの勢いがいいっぽい。 サーバーの環境変数にも注意が必要らしい。$_SERVER、Nucleusでは serverVar() で拾った値についてもチェック。

MySQLに入力値を格納するときのエスケープ方法

SQLインジェクション対策として。というか、そもそもSQLエラーを出さないために特定文字のエスケープは必要。

addslashes($value)          //対象:', ", \, \x00(NULLバイト)
mysql_escape_string($value) //対象:', ", \, \x00, \n, \r, \x1a(EOF=ファイル終端文字)
mysql_real_escape_string($value) //カレントの文字セットを考慮※

mysql_real_escape_string:無印よりこっちのほう推奨らしい。ただしDBに文字コードの違うデータを無理に格納してる環境ではまずかったような・・(MySQL v4.0.xの環境で、EUC-JPのDBにUTF-8でデータを格納してる場合、つまりウチの場合など・・) でもこのへんはまだちゃんと検証してないです。

\n, \r をエスケープする理由はなんなのかが良くわかってなかったりするけど・・。

PHPマニュアル mysql_real_escape_string()より

//安全性を確保するために変数をクオートする
function quote_smart($value)
{
    // Stripslashes
    if (get_magic_quotes_gpc()) {
        $value = stripslashes($value);
    }
    // 数値あるいは数値形式の文字列以外をクオートする
    if (!is_numeric($value)) {
        $value = "'" . mysql_real_escape_string($value) . "'";
    }
    return $value;
}

//安全なクエリの生成
$query = sprintf("SELECT * FROM users WHERE user=%s AND password=%s",
            quote_smart($_POST['username']),
            quote_smart($_POST['password']));

とりあえず僕の修正版ではこの quote_smart() を流用しつつ、いっこ上の注釈の理由により、mysql_real_escape_string() ではなく mysql_escape_string() を呼ぶようにしてます。

追記:数値か文字列かで処理を分けず、どの値も一律にクオートしてしまう方がもっと良いらしい。数値か文字列かの判定で漏れがありうるのと、数値にクオートしてもMySQLが自動判別してくれて、オーバーヘッドも気になるほどではないことから。

MySQLから値を取り出したときのアンエスケープ方法

エスケープ済みのデータをもとに戻してあげる。 間違い。値が格納されるときにDB側でアンエスケープされるから必要なし。
これが必要なのは、Magic Quoteが有効になってる時に元にもどすとき。

stripslashes($value)
stripslashes_array($value)  //nucleus

PHPマニュアル stripslashes()より

//配列に考慮する方法
function stripslashes_deep($value) {
    $value = is_array($value) ?
        array_map('stripslashes_deep', $value) :
        stripslashes($value);

    return $value;
}

Nucleusではこれと同じことをstripslashes_array()が担当している。

ログインチェック

最初に挙げていた例の話。これはメンバーがログインしてるときのみ表示されるフォーム処理での「頭隠して尻隠さず」の例。

要するに、入力フォームの表示のとき(doSkinVar()doTemplateVar())はログインチェックをしてるのに、実際のデータ処理時(doAction())でのログインチェックを怠っていた、ということ。

function doAction($type) {
    global $member;

    //アクションにログイン状態が必須のとき
    if (! $member->isLoggedIn()) return;

    //以降、処理を進める
}

抜け漏れあると思うけど、とりあえず今自分でまとめられる基本をまとめてみました。