테이블 이름을 준비된 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"
답변
테이블 및 열 이름은 PDO의 매개 변수로 대체 될 수 없습니다.
이 경우 단순히 데이터를 수동으로 필터링하고 삭제해야합니다. 이를 수행하는 한 가지 방법은 쿼리를 동적으로 실행하는 함수에 속기 매개 변수를 전달한 다음 switch()
명령문을 사용하여 테이블 이름 또는 열 이름에 사용할 유효한 값의 화이트리스트를 작성하는 것입니다. 이렇게하면 사용자 입력이 쿼리로 직접 전달되지 않습니다. 예를 들어 :
function buildQuery( $get_var )
{
switch($get_var)
{
case 1:
$tbl = 'users';
break;
}
$sql = "SELECT * FROM $tbl";
}
기본 사례를 그대로 두거나 오류 메시지를 반환하는 기본 사례를 사용하면 사용하려는 값만 사용할 수 있습니다.
답변
테이블 (또는 열) 이름 바인딩이 작동하지 않는 이유 를 이해하려면 준비된 명령문의 플레이스 홀더가 작동하는 방식을 이해해야합니다. 즉, 적절하게 이스케이프 된 문자열로 대체되지 않고 결과 SQL이 실행됩니다. 대신, 문을 “준비”하도록 요청한 DBMS는 사용하는 테이블과 인덱스를 포함하여 해당 쿼리를 실행하는 방법에 대한 완전한 쿼리 계획을 제시하며, 이는 자리 표시자를 채우는 방법에 관계없이 동일합니다.
에 대한 계획은 SELECT name FROM my_table WHERE id = :value
당신이 대신하는 것과 동일 :value
하지만 SELECT name FROM :table WHERE id = :value
, DBMS는 실제로 어떤 테이블을 선택할지 모르기 때문에 비슷한 것으로 계획 할 수 없습니다.
이것은 PDO와 같은 추상화 라이브러리가 준비된 명령문의 두 가지 주요 목적을 무효화하기 때문에 해결할 수 있거나 해결할 수있는 것이 아닙니다 .1) 데이터베이스가 쿼리 실행 방법을 미리 결정하고 동일한 것을 사용하도록합니다. 여러 번 계획; 2) 쿼리의 논리를 변수 입력에서 분리하여 보안 문제를 방지합니다.
답변
나는 이것이 오래된 게시물 인 것을 보았지만 유용하다고 생각하고 @kzqai가 제안한 것과 비슷한 해결책을 공유 할 것이라고 생각했다.
나는 두 개의 매개 변수를받는 기능이 있습니다 …
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);
}
답변
전자를 사용하는 것이 전자를 사용하는 것보다 본질적으로 안전하지는 않으므로 입력이 매개 변수 배열이든 간단한 변수이든 상관없이 입력을 삭제해야합니다. 내가 함께 후자의 양식을 사용하여 아무것도 잘못 표시되지 않습니다 그래서 $table
, 당신의 내용이 있는지 확인 제공 $table
안전 (영숫자 플러스 밑줄?)를 사용하기 전에.
답변
(늦은 대답, 내 쪽 참고를 참조하십시오).
“데이터베이스”를 만들려고 할 때도 같은 규칙이 적용됩니다.
준비된 명령문을 사용하여 데이터베이스를 바인드 할 수 없습니다.
즉 :
CREATE DATABASE IF NOT EXISTS :database
작동 안 할 것이다. 대신 수신 허용 목록을 사용하십시오.
참고 사항 : 이 답변은 (커뮤니티 위키로) 종종 질문을 닫는 데 사용 되었기 때문에 추가되었습니다. 일부 사람들은 테이블 및 / 또는 열이 아닌 데이터베이스 를 바인딩하려고 할 때 이와 비슷한 질문을 게시했습니다 .
답변
내 일부는 다음과 같이 간단한 사용자 정의 살균 기능을 제공 할 수 있는지 궁금합니다.
$value = preg_replace('/[^a-zA-Z_]*/', '', $value);
나는 그것을 실제로 생각하지 않았지만 문자와 밑줄을 제외한 것을 제거하는 것이 효과가있는 것 같습니다.
답변
이 스레드의 주요 질문에 대해서는 다른 게시물에서 명령문을 준비 할 때 값을 열 이름에 바인딩 할 수없는 이유를 분명히 밝혔으므로 여기에 하나의 해결책이 있습니다.
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에서 추출되므로 테이블 열 이름과 유형으로 배열을 만들 필요가 없습니다.