PHP PDO Deyimleri tablo veya sütun adını parametre olarak kabul edebilir mi?


243

Tablo adını neden hazırlanmış bir PDO deyimine iletemiyorum?

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

SQL sorgusuna tablo adı eklemenin başka güvenli bir yolu var mı? Kasayla, demek istemiyorum

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

Yanıtlar:


212

Tablo ve Sütun adları PDO'daki parametrelerle değiştirilemez.

Bu durumda, verileri manuel olarak filtrelemek ve sterilize etmek isteyeceksiniz. Bunu yapmanın bir yolu, sorguyu dinamik olarak çalıştıracak işleve steno parametreleri iletmek ve sonra switch()tablo adı veya sütun adı için kullanılacak geçerli değerlerin beyaz bir listesini oluşturmak için bir deyim kullanmaktır . Bu şekilde hiçbir kullanıcı girişi doğrudan sorguya girmez. Yani mesela:

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

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

Hiçbir varsayılan durum bırakmadan veya hata iletisi döndüren varsayılan bir vaka kullanarak, yalnızca kullanmak istediğiniz değerlerin kullanılmasını sağlarsınız.


17
Herhangi bir dinamik yöntem kullanmak yerine beyaz liste seçenekleri için +1. Başka bir alternatif, kabul edilebilir tablo adlarını, potansiyel kullanıcı girişine karşılık gelen anahtarlarla (örneğin, array('u'=>'users', 't'=>'table', 'n'=>'nonsensitive_data')vb.) Bir
diziyle eşlemektir

4
Bunu okurken, buradaki örnek, kötü giriş için geçersiz SQL üretiyor, çünkü yok default. Bu modeli kullanarak, aşağıdakilerden birini senin birini tanımlayıp onlara cases olarak defaultveya bu şekilde açık bir hata durumda eklemekdefault: throw new InvalidArgumentException;
IMSoP

3
Basit bir şey düşünüyordum if ( in_array( $tbl, ['users','products',...] ) { $sql = "SELECT * FROM $tbl"; }. Fikir için teşekkürler.
Phil Tune

2
Özledim mysql_real_escape_string(). Belki burada birisi atlama ve "Ama PDO ile gerek yok" demeden söyleyebilirim
Rolf

Diğer bir konu dinamik tablo isimlerinin SQL denetimini bozmasıdır.
Acyra

143

Anlamak için neden bir tablo (veya sütun) adını bağlayıcı değildir çalışır, sen hazırlanmış tablolar çalışmalarında yer tutucular anlamak zorunda: basitçe olarak (uygun şekilde kaçtı) dizeleri ikame edilmez, ve elde edilen SQL idam. Bunun yerine, bir deyimin "hazırlaması" istenen bir DBMS, sorguyu nasıl çalıştıracağına, hangi tabloları ve dizinleri kullanacağına ilişkin, yer tutucuları nasıl doldurduğunuza bakılmaksızın aynı olacak tam bir sorgu planı ile birlikte gelir.

Şunun için plan SELECT name FROM my_table WHERE id = :valuene olursa olsun aynı olacaktır :value, ancak görünüşte benzer SELECT name FROM :table WHERE id = :valueplanlanamaz, çünkü DBMS'nin hangi tablodan gerçekten seçeceğiniz konusunda hiçbir fikri yoktur.

Bu, PDO gibi bir soyutlama kütüphanesinin de hazırlanabileceği veya çözmesi gereken bir şey değildir, çünkü hazırlanan ifadelerin 2 temel amacını yenecektir: 1) veritabanının bir sorgunun nasıl çalıştırılacağına önceden karar vermesine izin vermek ve aynı şeyi kullanmak birden çok kez planlama; ve 2) sorgu mantığını değişken girdiden ayırarak güvenlik sorunlarını önlemek.


1
Doğru, ama PDO dikkate almaz deyimi emülasyonu hazırlamak var (ki olabilir makul Parametreleme SQL nesne tanımlayıcıları, hala muhtemelen olmamalı kabul olsa).
eggyal

1
@eggyal Öykünmenin, tamamen yeni işlevler eklemek yerine standart işlevlerin tüm DBMS lezzetlerinde çalışmasını sağlamayı amaçladığını tahmin ediyorum. Tanımlayıcılar için bir yer tutucunun, herhangi bir DBMS tarafından doğrudan desteklenmeyen ayrı bir sözdizimine de ihtiyacı olacaktır. PDO oldukça düşük seviyeli bir pakettir ve örneğin TOP/ LIMIT/ OFFSETcümleleri için SQL üretimi ve sunumu yapmaz , bu nedenle bu bir özellik olarak biraz yersiz olacaktır.
IMSoP

13

Bunun eski bir yazı olduğunu görüyorum, ancak yararlı buldum ve @kzqai'nin önerdiğine benzer bir çözüm paylaşacağımı düşündüm:

Ben gibi iki parametre alır bir işlevi var ...

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

İçinde sadece "mübarek" tabloları olan tablo ve sütunların erişilebilir olduğundan emin olmak için ayarladığım dizilere karşı kontrol ediyorum:

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

Sonra PDO çalıştırmadan önce PHP kontrolü gibi görünüyor ...

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);
}

2
kısa çözüm için iyi, ama neden sadece$pdo->query($sql)
jscripter

Bir değişkeni bağlamak zorunda olan sorguları hazırlarken çoğunlukla alışkanlık dışındadır. Ayrıca tekrarlanan çağrıları burada yürütmek daha hızlıdır stackoverflow.com/questions/4700623/pdos-query-vs-execute
Don


4

Birincisini kullanmak, ikincisinden daha güvenli değildir, ister bir parametre dizisinin parçası ister basit bir değişken olsun, girişi sterilize etmeniz gerekir. Bu nedenle, kullanmadan $tableönce içeriğinin $tablegüvenli (alfanum artı alt çizgi?) Olduğundan emin olmanız şartıyla , ikinci formu kullanırken yanlış bir şey görmüyorum .


İlk seçeneğin işe yaramayacağı düşünüldüğünde, bir tür dinamik sorgu oluşturma kullanmanız gerekir.
Noah Goodrich

Evet, bahsedilen soru işe yaramayacak. Bu şekilde yapmaya çalışmanın neden çok önemli olmadığını anlatmaya çalışıyordum.
Adam Bellaire

3

(Geç cevap, yan notuma bakın).

Bir "veritabanı" oluşturmaya çalışırken de aynı kural geçerlidir.

Bir veritabanını bağlamak için hazırlanmış bir deyimi kullanamazsınız.

yani:

CREATE DATABASE IF NOT EXISTS :database

çalışmayacak. Bunun yerine bir safelist kullanın.

Yan not: Bu yanıtı (topluluk wiki'si olarak) ekledim, çünkü bazı insanların bir tabloyu ve / veya sütunu değil, bir veritabanını bağlamaya çalışırken buna benzer sorular yayınladığı sorular sık ​​sık kullanıyordu .


0

Bir parçam, bu kadar basit kendi özel sanitasyon fonksiyonunuzu sağlayabileceğinizi merak ediyor:

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

Bunu gerçekten düşünmedim, ancak karakterler ve alt çizgiler hariç herhangi bir şeyi kaldırmak gibi görünüyor.


1
MySQL tablo adları başka karakterler içerebilir. Bkz. Dev.mysql.com/doc/refman/5.0/en/identifiers.html
Phil

@PhilLaNasa aslında bazı savunmaları gerekir (referans gerekir). DBMS'nin çoğu, adın farklılaştırılmamış bir karakterde saklanmasına büyük / küçük harfe duyarlı olmadığından, örneğin: MyLongTableNamedoğru okunması kolaydır, ancak depolanan adı kontrol ederseniz (muhtemelen) MYLONGTABLENAMEçok okunamayan, yani MY_LONG_TABLE_NAMEdaha okunabilir olacaktır.
mloureiro

Bunu bir işlev olarak görmemek için çok iyi bir neden var: çok nadiren rastgele girdiye dayalı bir tablo adı seçmelisiniz. Neredeyse kesinlikle kötü niyetli bir kullanıcının "kullanıcılar" veya "rezervasyonlar" yerine geçmesini istemezsiniz Select * From $table. Beyaz liste veya katı bir desen eşleşmesi (örn. "Rapor_ ile başlayan adlar ve yalnızca 1 ila 3 basamak arası") burada gerçekten önemlidir.
17'de IMSoP

0

Bu konudaki ana soruya gelince, diğer gönderiler, ifadeleri hazırlarken değerleri neden sütun adlarına bağlayamadığımızı açıkladı, bu yüzden burada bir çözüm var:

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));

Yukarıdaki sadece bir örnektir, yani kopyala-yapıştır çalışmaz. İhtiyaçlarınıza göre ayarlayın. Şimdi bu% 100 güvenlik sağlamayabilir, ancak dinamik dizeler olarak "geldiklerinde" sütun adları üzerinde bazı denetimlere izin verir ve kullanıcıların sonunda değiştirilebilir. Ayrıca, information_schema öğesinden ayıklandıkları için tablo sütun adlarınız ve türlerinizle bir dizi oluşturmanıza gerek yoktur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.