プログラミング言語では関数はすべて外部的なものとして存在する。
一方、変数の場合は外部的なものと内部的なものがある。したがって参照できるスコープとそうでないスコープが変数にはある。


関数などのブロック構造の外側で変数を定義すればグローバルなスコープになるし、ブロック内で変数を定義すればローカルなスコープとなる。


一般的に関数はすべての関数の外側で定義することになっている。つまり関数の内部に別の関数を定義できない。
しかしPHPの場合は関数内に別の関数を定義することができる仕様になっている。
でもこのことは関数がすべて外部的なものとして存在していることに変わりはない。




■ ケース1
<?php

function foo() {

}

function foo() {

}

同じfoo()という関数名で定義しています。
これは関数の二重宣言という重大なエラー(Fatal error: Cannot redeclare)となる。




■ ケース2
<?php

function foo() {

}

function bar()
{
    function foo() {

    }
}

bar();

外側にfoo()関数定義をして、bar()関数内部に同じfoo()という関数名で定義しています。
これはbar()関数を使うと内部のfoo()も定義されてやはり二重宣言のエラーになってしまいます。


このことは関数内部で定義した関数でも、外部からそのまま使うことができることを意味しています。
つまり関数内部に定義した関数も外部的と言えます。


なのでこうすれば内部で定義された関数でも使うことができます。
<?php

function bar()
{
    function foo() {
        echo "foo()<br />\n";
    }
}
bar();  // これで内部のfoo()関数を定義
foo();  // foo()関数が実行できる




■ ケース3
<?php

function foo() {

}

class bar
{
    function fuga()
    {
        function foo() {

        }
    }
}
$obj = new bar(); // インスタンス化
$obj->fuga();  // bar::fuga()メソッド使用時にfoo()関数を定義

クラスの場合も同様、メソッド内に定義した関数は外部の関数と同じ名前の時、二重定義エラーとなります。
つまり関数は関数内に定義しても、同じ外部的なスコープで存在することになると言えます。


では関数を定義してそれを隠蔽したい場合はどうすればいいかと言うと、無名関数(クロージャ)を使います。
無名なので関数名がカブりようがないですね。




■ 無名関数(クロージャ)
<?php
$noName = function ($param) { echo $param * 10; };

$noName(3);   // 30

変数に、無名の関数定義を代入した後、その変数を関数的に使うことができるようになります。
コールバック関数を引数にとるような関数へは、変数代入すること無しに直接引数として記述できたりします。



--- 奇数のみをフィルタリングする例
<?php

$array = range(1, 10);
$newArray = array_filter($array,
    function($element){ return($element % 2 === 1); }
);

これで関数のスコープを自在に操ることができそうです。
特に関数内部に関数を定義したい場合など、外部の関数と競合しないようにできることには意義があります。




■ 無名関数を使った再帰処理

再帰処理ということで、配列のすべての値同士を結合することを考えてみます。


一次元の配列ならimplode()関数を使って以下のようにすれば簡単に結合できます。
<?php

$array = array(1, 3, 5, 7, 9);
$join = implode($array);

が、一次元とは限らない配列($_GET $_POSTなどでも有り得ます)の場合は恐らく専用の関数は用意されていないので、再帰を使った関数を自作することになります。


<?php

$noName = function ($array) {
    $join = '';

    foreach ($array as $value) {
        if (is_array($value)) {
            $join .= $noName($value);
        } else {
            $join .= $value;
        }
    }

    return $join;
};

$array = array(1, 3, 5, array(7, 9));;
$result = $noName($array);    // 使用中にエラー発生
echo $result;

結果これは致命的なエラー(Fatal error: Function name must be a string)となります。
無名関数のブロック外で定義した$noNameをそのままブロック内でも再帰用に使っているので当然です。


このような場合にはブロック内でも外部変数を使うことを知らせる必要があります。
そのためにはuseキーワードを使って親スコープから引き継ぐことを関数ヘッダで宣言をする必要があります。


<?php

$noName = function (&$array) use (&$noName) {
    $join = '';

    foreach ($array as &$value) {
        if (is_array($value)) {
            $join .= $noName($value);
        } else {
            $join .= $value;
        }
    }

    return $join;
};

$array = array(1, 3, 5, array(7, 9));  // 多次元配列セット
$result = $noName($array);    // 定義した無名関数を使う
echo $result;                 // "13579"

useキーワードを使うことで、無名関数を代入した変数を引き継ぐ宣言をしています。
その他、値コピーが頻発しないように関数引数やforeachでも参照を使っています。


この配列の値同士を結合する処理は、mb_convert_variables()関数が文字コード判別のために内部で行っているような動作です。


なのでより厳密な文字コード判定をしたい場合は以下のようになります。
$enc = mb_detect_encoding($noName($_REQUEST), "ASCII,JIS,UTF-8,SJIS-win,CP51932", true);

攻撃のメカニズムを理解することは、強靭な防御につながります。
ということでメジャーすぎて今更な攻撃についてまとめてみます。





■ CSRF(XSRF)という攻撃


会員がログインして使うようなサイト(仮にサイトAとします)では、ログイン中である情報(セッションID)をクッキーに持たせる場合が多い。
ログイン認証が成功すれば、一旦どこか他のサイトBを見てきた後でも再びサイトAに戻れば今度はログインの手続き抜きに自分の管理ページなどをいきなり見ることができてとても便利です。


こうしたサイトはログイン中であればどの会員でも、登録情報の更新や会員用に用意された書き込みなどが自由にできる便利な作りになっていたりします。



つまり書き込みが出来る条件は、ログイン中(セッションIDを保持している)であるということだけです。



そんな中まったく別のサイトCに、サイトAの更新を自動的に行うスクリプトが埋め込まれていたとします。
そしてたまたまサイトCを訪れた人がサイトAログイン中であった場合に、スクリプトが勝手にサイトAの更新処理を進めてしまう過程で正規のセッションIDも送ってしまうので、更新処理が最終段階まで行く可能性が考えられます。
なので一体なにが起こったのか原因を特定するまでに時間がかかると思います。



□ ユーザ側の防衛策


これを防ぐために会員の方でできることは、他サイトに行く前にクッキーを消すことぐらいです。
どちらかというとこれは会員よりもサイトのシステム設計の問題なので防ぐ対策がサーバ側には必要です。



□ サーバ側の防衛策


ログイン中であっても情報更新の際はIDとパスワード入力を強制する。


特定URLへのセッション情報送信だけでいきなり更新確定させないで、間に確認画面をワンクッション置いてそこにワンタイムトークンを発行しておき、最後にワンタイムトークンが正規のものかをチェックしてから更新確定させる。





■ XSS(CSS)という攻撃


HTMLタグの入力がそのままタグとして反映される作りになっているサイトがあった場合、何らかの意図しない処理を実行するスクリプトを埋め込むことができてしまいます。
つまりHTMLタグを使ってでできることはなんでもできることになります。



まず考えられるのはそこがもしクッキーを使ってセッション管理しているサイトの場合、クッキーを他サーバに送って盗まれることによるセッションハイジャックがあります。
その他、いきなりダメージ(PC的・精神的)を与えるサイトに飛ばされる場合もあります。


あとは先ほどのCSRFにつながるスクリプトを埋め込まれる場合も考えられます。
他にはそのページそっくりの画面(フィッシングサイト)に切り替えられて、気づかないでそこに入力した情報が盗まれたり、ボタンを押した瞬間他サイトを攻撃し出したり様々なことができてしまうでしょう。



□ ユーザ側の防衛策


HTMLタグがそのまま反映されるサイトには近づかない。


PC的なダメージの場合はブラウザを素早く閉じる訓練を積む。
精神的ダメージの場合は表示される前に目を閉じる訓練を積む。見慣れる。


CSRFの場合は先ほど同様、クッキーを小まめに消す。


そっくりの画面に誘導された場合は被害が広からないようにサイト管理人に知らせましょう。
HTMLタグがそのまま反映されてしまう時点で管理人に知らせるのはいずれの場合も必要ですね。



□ サーバ側の防衛策


入力されてきたものはそのまま表示させない。
必ず表示に反映されるデータは「< ⇒ &lt;」「> ⇒ &gt;」「& ⇒ &amp;」「" ⇒ &quot;」のエンティティ変換を行う。





■ SQLインジェクションという攻撃


インジェクションとは、注入という意味 ドドスコ

想定外の入力がされてそれがプログラムに害を及ぼす攻撃法をインジェクション攻撃と言います。

ユーザ側から送られてきた値(GET POST COOKIE サーバ変数などの情報)でデータベース操作を行う場合に、適切にエスケープがされてないと引き起こされる可能性があります。


例えば以下のSQL文がシステム内で使われていたとします。


$sql = "SELECT * FROM tablename WHERE foo = '{$_POST['foo']}'";

このときもしfooに「' OR 'x' = 'x」と入力されてしまうと、


$sql = "SELECT * FROM tablename WHERE foo = '' OR 'x' = 'x'";

と展開されてしまい、SQL文の条件が常に真となる。



$sql = "SELECT * FROM userinfo WHERE id ='{$_POST['id']}' AND password='{$_POST['password']}'";

もしpasswordに「' OR 'id' = 'other」が入力されてしまうと


$sql = "SELECT * FROM userinfo WHERE (id ='username' AND password='') OR 'id' = 'other'";

このような意味になり、otherというIDのユーザがいればその情報が検索されてしまうことになる。


更新の場合もユーザ入力で更新対象が常に真となる条件にされれば、全レコードの値を更新されてしまう。


UPDATE userinfo SET password = 'dodosuko' WHERE id = '' OR 'x' = 'x';



□ 防衛策


適切なSQLエスケープを施すか、プリペアドステートメントみたいなエスケープを自動的に施してくれる方法を使う。





要するに、セッション中はセッションIDだけで一気に確定までできてしまう作りにしないようにして、ユーザ入力値はすべてバリデーション(検証)を行い、SQLに渡す入力値は確実にエスケープを行い、画面表示に反映される入力値は表示させる時にサニタイズ(無毒化)しましょうというお話でした。



PHP開発環境もできたことだし、せっかくeclipseを使うので他言語の環境も同じeclipseで動かしてしまおうと欲張ってみることにします。
とは言ってもJavaは初めから入ってるんですよね。。


もう一つ他の言語としてCDTというC/C++の開発環境プラグインがメジャーっぽいのでこれ入れてみる


幸い去年入れていたMinGW+MSYS環境があったのでコンパイラはこれで決まり。




■ CDTプラグインの追加インストール


「ヘルプ」「新規ソフトウェアのインストール」で現れた画面の作業対象リストの中から[--すべての使用可能なサイト--]を選び、 「プログラミング言語」の頭▲を押してリストの中から「C/C++ Development Tools」にチェックを入れて「次へ」ボタンを押す。


更に「次へ」ボタンを押しライセンスをハイスピードで読んだ刹那「●使用条件の条項に同意します。」を選択し「完了」ボタンを押す。
インストールが終わると再起動を促すメッセージが表示されるので「今すぐ再始動」を押す。



再起動すると画面いっぱいに概要わらわらが表示されてるのでとりあえず上部の「ようこそ」タブのX印を押して消す。




■ 設定


まずはC/C++の設定を行うべく「ウィンドウ」メニューから「設定」画面表示
左サイドに新たにC/C++というメニューが加わっているのでここの中身を設定していきます。


「デバッグ」画面の文字エンコードをShift_JISに変更(とりあえず全てをシフトJISで行う宗教なもので)


「新規CDTプロジェクト」「Makefileプロジェクト」で表示された画面のバイナリー・パーサーの中から以下の3つをチェック

  • Elfパーサー
  • PE Windowsパーサー
  • Cygwin PEパーサー

これがバイナリファイル検索用に使われるらしい




■ Cプロジェクト作成


「新規」プロジェクトからCプロジェクトを選んで「次へ」ボタンを押してプロジェクト名として "hello" を入力
デフォルトロケーションが前回PDT設定によりドキュメントルートになっているのでこれは使わないようにチェックを外して
MSYSの仮想ホームディレクトリ直下にプロジェクト名と同名ディレクトリで作成するべく
C:\MinGW\msys\1.0\home\Username\helloに設定


※1つのプロジェクトに対して1つのアプリケーション開発なので、単なる「Hello world.」表示アプリケーションに対して1つのプロジェクトを作ることになる。


プロジェクト・タイプとして、実行可能リストの中から「Hello World ANSI Cプロジェクト」を選ぶ


これで「完了」ボタンを押す



そうするとC/C++のパースペクティブ(画面構成)に切り替わる。切り替わらなければ右上から手動で選ぶ


Cプロジェクトにはインクルードが作成されていて、その中はMinGWのヘッダファイルとライブラリに通じている。


helloプロジェクトには既にソース・ファイルhello.cが作成されている。(先ほどHello World ANSI Cプロジェクトを選んだので)


-- Cソースコード[hello/src/hello.c] --
/*
 ===========================================================
 Name        : hello.c
 Author      :
 Version     :
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style
 ===========================================================
 */

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    puts("!!!Hello World!!!");
    return EXIT_SUCCESS;
}



■ 実行


では実行ボタンを押してこのソースを実行してみます。
でもその前に、C言語はコンパイルして実行ファイル生成した後でないと実行できません。
でもCDTでは始めから「プロジェクト」メニューの「自動的にビルド」にチェックが入っているので実行ボタンを押すと「コンパイル⇒実行ファイル生成⇒実行」の流れ作業を自動でやってくれるようなので実行ボタン1つ押すだけで実行までできてしまいます。


もし実行ファイル生成と実行を分けたい場合は自動的にビルドのチェックを外してから金づちのアイコンのボタンで実行ファイル生成を先に行うことになると思います。



「!!!Hello World!!!」が表示されたら成功です。
もし失敗したらいろいろな原因があるので片っ端から確認していくしかありません。


○まずMinGWの場所が認識できてない場合は
「ウィンドウ」メニュー「設定」の「C/C++」「ビルド」「環境」画面から「追加」ボタンで新規変数追加

名前:path
値:C:\MinGW\bin;C:\MinGW\msys\1.0\bin



○実行時に「起動に失敗しました。バイナリーが見つかりません」と表示されたら
実行ファイル(EXEファイル)が作成されていないことになります。
これはCプロジェクト作成時にプロジェクトのタイプとしてstaticライブラリーを選んだ可能性があります。
これだとコンパイル止まりなのでEXEファイルが作成されません。




■ 日本語を表示させてみる


-- Cソースコード[hello/src/hello.c] --
/*
 ===========================================================
 Name        : hello.c
 Author      :
 Version     :
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style
 ===========================================================
 */

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    puts("!!!日本語テスト表示!!!");
    return EXIT_SUCCESS;
}



これを実行してみると、コンパイル途中に警告を表示するようになりました。
ただ、実行ファイルは作成できました。


-- 警告 --
..\src\hello.c: In function 'main':
..\src\hello.c:15:7: warning: unknown escape sequence: '\216'


これはシフトJISでよく起こる0x5C問題です。0x5Cコード(\文字)の次に続くコードがエスケープされてると言っています。


試しに実行してみるとやはり化けています。


-- 実行[hello/バイナリー/hello.exe] --
!!!日本語テスト侮ヲ!!!


なのでMinGWのコンパイル時にシフトJISとして読み取って実行するオプションがあるのでこれを設定してみます。



helloプロジェクトを右クリックしてプロパティー画面を表示させ、「C/C++ビルド」メニューの「設定」画面でツール設定タブを選んでGCC C Compilerの その他のフラグとして "-finput-charset=cp932"と"-fexec-charset=cp932"の2つのオプションを追加して「適用」「OK」とします。


-- 設定 --
-c -fmessage-length=0 -finput-charset=cp932 -fexec-charset=cp932


これでコンパイル時の警告が出なくなりました。なので表示も問題ないです。


とりあえずこれでCDTの環境もできたということにしておきます。