Security

セキュリティー対策

2007年12月24日

とりあえず、3つの対策を用意。

1)リモートコードインサーション対策
2)XSS対策
3)SQLインジェクション対策

何も考えずにコアの機能を利用することで、これらのセキュリティー対策が行えるようなツールを目指す。理想としては、意識せずにコードを書けば脆弱性はでず、何か特別なことを仕様としたときだけに脆弱性が出てしまうようなものにしたい。規約として、echo 命令はライブラリやプラグインでは使用禁止にするつもり。

1)リモートコードインサーション対策

 __autoload()関数を活用することで、ほとんどのケースにおいて include() require() などの関数を利用することは無いはず。__autoload()周りは、いまのところ次のとおり。

// There is only one global function.
function __autoload($className) {
    core::autoload($className);
}
class core {
    static function autoload($className){
        switch(substr($className,0,3)){
        case 'jp_': // plugins
            if (file_exists($file=DIR_PLUGINS.$className.'.php')) {
                require_once($file);
            } else exit ('Class PHP file not found.');
            break;
        default:
            if (file_exists($file=DIR_LIBS.$className.'.php')) {
                require_once($file); // libraries
            } elseif (file_exists($file=$className.'.php')) {
                require_once($file); // file in current directory.
            } else exit ('Class PHP file not found.');
            break;
        }
        // Call init() method if exists.
        @call_user_func(array($className,'init'));
    }

クラスが必要なときは、自動的に jeans/libs ディレクトリもしくはプラグインディレクトリから呼び出される。

2)XSS対策

 コアに、次のようなメソッドを用意した。

class core {
    static function p(){
        foreach(func_get_args() as $text){
            echo htmlspecialchars($text,ENT_QUOTES,_CHARSET);
        }
    }
    static function fill(string $html,$data){
        if (!is_array($data)) $data=array($data);
        $search=array();
        $replace=array();
        foreach($data as $key=>$value){
            $search[]='<%'.(is_integer($key)?$key+1:$key).'%>';
            $replace[]=htmlspecialchars($value,ENT_QUOTES,_CHARSET);
        }
        return str_replace($search,$replace,$html);
    }
    static function echohtml(string $html,$data){
        echo self::fill($html,$data);
    }

 htmlspecialcharsを通して出力したい場合は、core::p()メソッドを用いればよい。毎回『htmlspecialchars, ENT_QUOTES, _CHARSET』を打ち込む必要が無いので、プログラミングもずいぶん楽になるはず。また、HTMLタグを含めた出力には、core::echohtml()メソッドを用いる。2番目の引数に与えたデータは、htmlspecialcharsを通して挿入され、表示される。例えば、次のように記述する。
core::echohtml('<b>ID: <%1%>, title:<%2%></b>',array($itemid,$title));


3)SQLインジェクション対策

sqlクラスに、次のメソッドを用意。

class sql {
    static public function query ($query,$data=null){
        if ($data!==null) $query=self::fill($query,$data);
        self::checkQuery($query);
        $res=sqlite_query (self::$obj->db,$query);
        return $res;
    }
    static function fill($query,$data){
        if (!is_array($data)) $data=array($data);
        $search=array();
        $replace=array();
        foreach($data as $key=>$value){
            $search[]='<%'.(is_integer($key)?$key+1:$key).'%>';
            if (is_integer($value)) {
                $replace[]=(int)$value;
            } elseif(is_numeric($value)) {
                $replace[]=(float)$value;
            } else {
                $replace[]="'".sqlite_escape_string($value)."'";
            }
        }
        return str_replace($search,$replace,$query);
    }
    static private function checkQuery($query){
        $query=preg_replace('/;[\s]*$/','',$query); // remove last ';';
        if (strpos($query,';')===false) return; // if there isn't ';', OK.
        foreach(preg_split("/'/",$query) as $key=>$value){
            if ($key%2==1) continue; // ignore inside string
            if (strpos($value,';')===false) continue; // if there isn't ';', OK.
            exit('Cannot divide SQL query by ";"');
        }
    }

 sql::query()メソッドを呼び出すとき、2番目の引数にデータを与えれば、自動的にサニタイズされたデータが挿入される仕組み。sqlite_escape_stringなどを使う必要が無い。例えば、次のように記述する。
$res=sql::query('SELECT * FROM jeans_item WHERE category=<%1%> AND title=<%2%>', array($itemid,$title));

 なお、sql::checkQuery()メソッドは現在のところ、『 ; 』を用いた2つ以上のクエリーを実行することを禁止するために用いている。

コメント

コメントはありません

コメント送信