回答:
テーブル名と列名は、PDOのパラメーターで置き換えることはできません。
その場合は、単にデータを手動でフィルタリングしてサニタイズする必要があります。これを行う1つの方法は、クエリを動的に実行する関数に省略形のパラメーターを渡し、switch()
ステートメントを使用して、テーブル名または列名に使用する有効な値のホワイトリストを作成することです。これにより、ユーザー入力がクエリに直接入力されることはありません。だから例えば:
function buildQuery( $get_var )
{
switch($get_var)
{
case 1:
$tbl = 'users';
break;
}
$sql = "SELECT * FROM $tbl";
}
デフォルトのケースを残さないか、エラーメッセージを返すデフォルトのケースを使用することで、使用したい値のみが使用されるようになります。
default
。このパターンを使用している場合、あなたはどちらかあなたの1ラベルを付ける必要がありますcase
秒としてdefault
、またはのような明示的なエラーケースを追加default: throw new InvalidArgumentException;
if ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }
。アイデアをありがとう。
mysql_real_escape_string()
。多分ここに私は誰かが飛び込んで「PDOではそれを必要としない」と言わなくてもそれを言うことができます
理解するために、なぜ表(または列)を結合名前が仕事をしない、あなたは準備された文の仕事にどのようにプレースホルダを理解する必要があります:彼らは単に(適切にエスケープ)文字列としてで置換されていない、そして得られたSQLを実行します。代わりに、ステートメントを「準備」するように要求されたDBMSは、どのテーブルとインデックスを使用するかを含む、そのクエリの実行方法に関する完全なクエリプランを作成します。これは、プレースホルダーの入力方法に関係なく同じです。
の計画はSELECT name FROM my_table WHERE id = :value
、何を置き換えても同じになりますが、DBMSは実際にどのテーブルから選択するのかわからないため、:value
一見似ているSELECT name FROM :table WHERE id = :value
ように計画することはできません。
これは、PDOのような抽象ライブラリではなく、回避する必要があります。これは、準備されたステートメントの2つの主要な目的を無効にするためです。複数回計画する; 2)クエリのロジックを変数の入力から分離することにより、セキュリティの問題を防止する。
TOP
/ LIMIT
/ OFFSET
句のSQL生成を提供しないため、これは機能としては少々場違いです。
これは古い投稿ですが、便利だと思い、@ 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);
}
$pdo->query($sql)
前者を使用することは、後者よりも本質的に安全ではないため、パラメーター配列の一部であっても、単純な変数であっても、入力を無害化する必要があります。ですから、後者のフォームをで使用しても問題はありません。使用する前に$table
のコンテンツ$table
が安全であることを確認してください(英数字とアンダースコア?)。
(遅い答え、私のサイドノートを参照してください)。
「データベース」を作成しようとするときにも同じルールが適用されます。
準備済みステートメントを使用してデータベースをバインドすることはできません。
すなわち:
CREATE DATABASE IF NOT EXISTS :database
動作しないでしょう。代わりにセーフリストを使用してください。
サイドノート:私はこの回答を(コミュニティWikiとして)追加しました。これは、質問を閉じるためによく使用されていたためです。テーブルや列ではなくデータベースをバインドしようとして、これに似た質問を投稿した人もいます。
私の一部は、次のように簡単な独自のカスタム消毒機能を提供できるかどうか疑問に思っています。
$value = preg_replace('/[^a-zA-Z_]*/', '', $value);
私は本当にそれを熟考していませんが、文字とアンダースコア以外は何も削除しないとうまくいくようです。
MyLongTableName
名前を格納します。たとえば、正しく読み取るのは簡単ですが、格納された名前を確認すると、(おそらく)MYLONGTABLENAME
あまり読みにくいためMY_LONG_TABLE_NAME
、実際にはより読みやすくなります。
Select * From $table
。ホワイトリストまたは厳密なパターンマッチ(たとえば、「report_で始まり、1から3桁のみが続く名前」)は、ここでは本当に重要です。
このスレッドの主な質問については、他の投稿で、ステートメントを準備するときになぜ値を列名にバインドできないのかが明らかになったので、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));
上記は一例ですので、言うまでもなくcopy-> pasteは機能しません。ニーズに合わせて調整します。これで100%のセキュリティは提供されない可能性がありますが、動的文字列として「入ってくる」ときにユーザー名で変更できるように、列名をある程度制御できます。さらに、それらはinformation_schemaから抽出されるため、テーブルの列名とタイプを含む配列を構築する必要はありません。
array('u'=>'users', 't'=>'table', 'n'=>'nonsensitive_data')
等)に対応するキーを持つ配列にマッピングすることかもしれません