259

準備された PDO ステートメントにテーブル名を渡すことができないのはなぜですか?

$stmt = $dbh->prepare('SELECT * FROM :table WHERE 1');
if ($stmt->execute(array(':table' => 'users'))) {
    var_dump($stmt->fetchAll());
}

テーブル名を SQL クエリに挿入する別の安全な方法はありますか? 安全とは、やりたくないという意味です

$sql = "SELECT * FROM $table WHERE 1"
4

8 に答える 8

233

PDO では、テーブル名と列名をパラメーターで置き換えることはできません。

その場合、データを手動でフィルタリングしてサニタイズするだけです。これを行う 1 つの方法は、クエリを動的に実行する関数に短縮パラメーターを渡し、switch()ステートメントを使用して、テーブル名または列名に使用される有効な値のホワイト リストを作成することです。そうすれば、ユーザー入力がクエリに直接入力されることはありません。たとえば、次のようになります。

function buildQuery( $get_var ) 
{
    switch($get_var)
    {
        case 1:
            $tbl = 'users';
            break;
    }

    $sql = "SELECT * FROM $tbl";
}

デフォルトのケースを残さないか、エラー メッセージを返すデフォルトのケースを使用することで、使用したい値のみが使用されるようになります。

于 2008-10-08T11:57:15.897 に答える
13

これは古い投稿だと思いますが、便利だと思ったので、@kzqai が提案したのと同様の解決策を共有したいと思いました。

次のような2つのパラメーターを受け取る関数があります...

function getTableInfo($inTableName, $inColumnName) {
    ....
}

内部では、設定した配列をチェックして、「祝福された」テーブルを持つテーブルと列のみがアクセス可能であることを確認します。

$allowed_tables_array = array('tblTheTable');
$allowed_columns_array['tblTheTable'] = array('the_col_to_check');

次に、PDO を実行する前の PHP チェックは次のようになります...

if(in_array($inTableName, $allowed_tables_array) && in_array($inColumnName,$allowed_columns_array[$inTableName]))
{
    $sql = "SELECT $inColumnName AS columnInfo
            FROM $inTableName";
    $stmt = $pdo->prepare($sql); 
    $stmt->execute();
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
于 2013-04-30T17:30:06.143 に答える
4

前者を使用することは、後者より本質的に安全ではありません。入力がパラメーター配列の一部であるか単純な変数であるかにかかわらず、入力をサニタイズする必要があります。したがって、後者のフォームをで使用しても問題はありません。ただし、使用する前$tableに、のコンテンツ$tableが安全であることを確認してください(英数字とアンダースコア?)。

于 2008-10-08T11:46:45.263 に答える
0

このスレッドの主な質問については、他の投稿で、ステートメントを準備するときに値を列名にバインドできない理由が明らかになったので、1 つの解決策を次に示します。

class myPdo{
    private $user   = 'dbuser';
    private $pass   = 'dbpass';
    private $host   = 'dbhost';
    private $db = 'dbname';
    private $pdo;
    private $dbInfo;
    public function __construct($type){
        $this->pdo = new PDO('mysql:host='.$this->host.';dbname='.$this->db.';charset=utf8',$this->user,$this->pass);
        if(isset($type)){
            //when class is called upon, it stores column names and column types from the table of you choice in $this->dbInfo;
            $stmt = "select distinct column_name,column_type from information_schema.columns where table_name='sometable';";
            $stmt = $this->pdo->prepare($stmt);//not really necessary since this stmt doesn't contain any dynamic values;
            $stmt->execute();
            $this->dbInfo = $stmt->fetchAll(PDO::FETCH_ASSOC);
        }
    }
    public function pdo_param($col){
        $param_type = PDO::PARAM_STR;
        foreach($this->dbInfo as $k => $arr){
            if($arr['column_name'] == $col){
                if(strstr($arr['column_type'],'int')){
                    $param_type = PDO::PARAM_INT;
                    break;
                }
            }
        }//for testing purposes i only used INT and VARCHAR column types. Adjust to your needs...
        return $param_type;
    }
    public function columnIsAllowed($col){
        $colisAllowed = false;
        foreach($this->dbInfo as $k => $arr){
            if($arr['column_name'] === $col){
                $colisAllowed = true;
                break;
            }
        }
        return $colisAllowed;
    }
    public function q($data){
        //$data is received by post as a JSON object and looks like this
        //{"data":{"column_a":"value","column_b":"value","column_c":"value"},"get":"column_x"}
        $data = json_decode($data,TRUE);
        $continue = true;
        foreach($data['data'] as $column_name => $value){
            if(!$this->columnIsAllowed($column_name)){
                 $continue = false;
                 //means that someone possibly messed with the post and tried to get data from a column that does not exist in the current table, or the column name is a sql injection string and so on...
                 break;
             }
        }
        //since $data['get'] is also a column, check if its allowed as well
        if(isset($data['get']) && !$this->columnIsAllowed($data['get'])){
             $continue = false;
        }
        if(!$continue){
            exit('possible injection attempt');
        }
        //continue with the rest of the func, as you normally would
        $stmt = "SELECT DISTINCT ".$data['get']." from sometable WHERE ";
        foreach($data['data'] as $k => $v){
            $stmt .= $k.' LIKE :'.$k.'_val AND ';
        }
        $stmt = substr($stmt,0,-5)." order by ".$data['get'];
        //$stmt should look like this
        //SELECT DISTINCT column_x from sometable WHERE column_a LIKE :column_a_val AND column_b LIKE :column_b_val AND column_c LIKE :column_c_val order by column_x
        $stmt = $this->pdo->prepare($stmt);
        //obviously now i have to bindValue()
        foreach($data['data'] as $k => $v){
            $stmt->bindValue(':'.$k.'_val','%'.$v.'%',$this->pdo_param($k));
            //setting PDO::PARAM... type based on column_type from $this->dbInfo
        }
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);//or whatever
    }
}
$pdo = new myPdo('anything');//anything so that isset() evaluates to TRUE.
var_dump($pdo->q($some_json_object_as_described_above));

上記は一例ですので、言うまでもなくコピー→ペーストは効きません。ニーズに合わせて調整してください。現在、これは 100% のセキュリティを提供しない可能性がありますが、列名が動的文字列として「入力」されたときに列名をある程度制御でき、ユーザー側で変更される可能性があります。さらに、テーブルの列名と型は information_schema から抽出されるため、配列を作成する必要はありません。

于 2014-09-09T15:36:05.027 に答える
0

私の一部は、次のように簡単な独自のカスタムサニタイズ機能を提供できるかどうか疑問に思っています。

$value = preg_replace('/[^a-zA-Z_]*/', '', $value);

あまり考えていませんが、文字とアンダースコア以外を削除するとうまくいくようです。

于 2014-04-29T00:21:15.373 に答える