복붙노트

PHP PDO 문이 테이블 또는 열 이름을 매개 변수로 허용 할 수 있습니까?

PHP

PHP PDO 문이 테이블 또는 열 이름을 매개 변수로 허용 할 수 있습니까?

준비된 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"

해결법

  1. ==============================

    1.

    PDO에서 테이블 및 열 이름을 매개 변수로 바꿀 수 없습니다.

    이 경우 데이터를 수동으로 필터링하고 위생 처리하기 만하면됩니다. 이를 수행하는 한 가지 방법은 쿼리를 동적으로 실행하는 함수에 축약 매개 변수를 전달한 다음 switch () 문을 사용하여 테이블 이름이나 열 이름에 사용할 유효한 값의 흰색 목록을 만드는 것입니다. 그렇게하면 사용자 입력이 쿼리에 직접 적용되지 않습니다. 그래서 예를 들면 :

    function buildQuery( $get_var ) 
    {
        switch($get_var)
        {
            case 1:
                $tbl = 'users';
                break;
        }
    
        $sql = "SELECT * FROM $tbl";
    }
    

    기본 값을 그대로 두거나 오류 메시지를 반환하는 기본 값을 사용하면 사용하려는 값만 사용되도록합니다.

  2. ==============================

    2.

    테이블 (또는 열) 이름 바인딩이 작동하지 않는 이유를 이해하려면 준비된 명령문의 자리 표시 자 작동 방식을 이해해야합니다. 즉, (적절하게 이스케이프 된) 문자열로 대체되지 않고 결과 SQL이 실행됩니다. 대신, DBMS는 "준비"하라는 질문에 어떤 테이블과 인덱스를 포함하여 쿼리를 실행하는 방법에 대한 완전한 쿼리 계획을 제시합니다.이 테이블은 자리 표시자를 채우는 방법에 관계없이 동일합니다.

    SELECT 이름 FROM my_table WHERE id = : value에 대한 계획은 값을 대체 할 수있는 값과 동일합니다. 값은 같지만 비슷한 SELECT 이름 FROM : 테이블 WHERE id = : 값을 계획 할 수 없습니다. DBMS는 어떤 테이블 실제로 선택할 것입니다.

    이는 PDO와 같은 추상화 라이브러리가 준비된 명령문의 2 가지 주요 목적을 무효화 할 수 있기 때문에 불가능합니다. 1) 데이터베이스가 미리 쿼리를 실행하는 방법을 결정하고이를 사용하도록 허용합니다. 여러 번 계획하라. 2) 질의의 논리를 변수 입력과 분리하여 보안 문제를 방지한다.

  3. ==============================

    3.

    나는 이것이 오래된 게시물이라고 생각하지만 유용하다는 것을 알았고 @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);
    }
    
  4. ==============================

    4.

    전자를 사용하는 것이 본질적으로 후자보다 안전하지는 않습니다. 매개 변수 배열 또는 단순 변수의 일부인지 여부에 관계없이 입력을 위생해야합니다. 그래서 나는 $ table에 후자의 양식을 사용하는 것에있어 잘못된 것을 보지 않는다. 사용하기 전에 $ table의 내용이 안전한지 (영숫자와 밑줄?) 있는지 확인한다.

  5. ==============================

    5.

    나 자신은 다음과 같이 간단한 커스텀 위생 기능을 제공 할 수 있을지 궁금해한다.

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

    나는 그것을 통해 실제로 생각하지 못했지만, 문자를 제외하고는 아무 것도 제거하는 것처럼 보이고 밑줄은 효과가있을 수 있습니다.

  6. ==============================

    6.

    이 스레드의 주된 질문에 관해서, 다른 글은 왜 문장을 준비 할 때 컬럼 이름에 값을 바인딩 할 수 없는지 분명하게 했으므로 여기에 하나의 해결책이 있습니다 :

    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에서 추출되기 때문에 테이블 열 이름과 유형으로 일부 배열을 빌드 할 필요가 없습니다.

  7. from https://stackoverflow.com/questions/182287/can-php-pdo-statements-accept-the-table-or-column-name-as-parameter by cc-by-sa and MIT lisence