Kısa cevap HAYIR , PDO hazırlar sizi tüm olası SQL-Enjeksiyon saldırılarına karşı savunmayacaktır. Belirli belirsiz kenar durumlar için.
Bu cevabı PDO hakkında konuşmak için uyarlıyorum ...
Uzun cevap o kadar kolay değil. Burada gösterilen bir saldırıya dayanıyor .
Saldırı
Öyleyse, saldırıyı göstererek başlayalım ...
$pdo->query('SET NAMES gbk');
$var = "\xbf\x27 OR 1=1 /*";
$query = 'SELECT * FROM test WHERE name = ? LIMIT 1';
$stmt = $pdo->prepare($query);
$stmt->execute(array($var));
Belirli durumlarda, bu 1'den fazla satır döndürür. Burada olanları inceleyelim:
Karakter Kümesi Seçme
$pdo->query('SET NAMES gbk');
İşe bu saldırı için, sunucunun hem kodlamak için bağlantıda bekliyor o kodlamayı ihtiyaç '
ASCII ie olarak 0x27
ve kimin nihai bayt bir ASCII bazı karakter var \
yani 0x5c
. Sonradan anlaşıldı ki, varsayılan olarak MySQL 5.6 desteklenen 5 tür kodlamalar vardır: big5
, cp932
, gb2312
, gbk
ve sjis
. Burada seçeceğiz gbk
.
Şimdi, SET NAMES
burada kullanımını not etmek çok önemlidir . Bu, SUNUCU ÜZERİNDEKİ karakter setini ayarlar . Bunu yapmanın başka bir yolu var, ama yakında oraya ulaşacağız.
Yük
Bu enjeksiyon için kullanacağımız yük, bayt dizisi ile başlar 0xbf27
. Bu gbk
geçersiz bir çokbaytlı karakterdir; içinde latin1
, bu dize ¿'
. İçinde olduğunu unutmayın latin1
ve gbk
, 0x27
kendi hazır bilgi üzerine '
karakteri.
Bu yükü seçtik, çünkü eğer çağırırsak, addslashes()
ASCII'yi \
yani karakterden 0x5c
önce '
eklerdik. Bu yüzden iki karakter dizisi 0xbf5c27
olan bir rüzgârla karşılaşırız gbk
: 0xbf5c
arkasından 0x27
. Ya da başka bir deyişle, geçerli bir karakter ve ardından çıkış karakteri yok '
. Ama kullanmıyoruz addslashes()
. Bir sonraki adıma geçelim ...
$ Stmt->) (yürütmek
Burada gerçekleştirmek için önemli olan yok varsayılan olarak o PDO olduğu DEĞİL gerçek hazırlanmış deyimleri yapmak. Onlara öykünür (MySQL için). Bu nedenle, PDO dahili olarak sorgu dizesini oluşturur ve mysql_real_escape_string()
her bağlı dize değerinde (MySQL C API işlevi) çağırır .
C API çağrısı , bağlantı karakter kümesini bildiğinden mysql_real_escape_string()
farklıdır addslashes()
. Böylece sunucunun beklediği karakter kümesi için kaçmayı düzgün bir şekilde gerçekleştirebilir. Ancak, bu noktaya kadar, müşteri hala latin1
bağlantı için kullandığımızı düşünüyor , çünkü bunu asla aksini söylemedik. Biz söyledin sunucu Kullandığımız gbk
ancak istemci hala sanıyor latin1
.
Bu nedenle mysql_real_escape_string()
ters eğik çizgi ekleme çağrısı ve '
"kaçan" içeriğimizde serbest asılı bir karakterimiz var ! Biz bakmak için olsaydı Aslında, $var
içinde gbk
karakter kümesi, biz görürdük:
OR 'VEYA 1 = 1 / *
Bu tam olarak saldırının gerektirdiği şeydir.
Sorgu
Bu bölüm sadece bir formalite, ancak işlenmiş sorgu:
SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
Tebrikler, PDO Hazırlanan Deyimler kullanarak bir programa başarıyla saldırdınız ...
Basit Düzeltme
Şimdi, taklit hazırlanmış ifadeleri devre dışı bırakarak bunu önleyebileceğinizi belirtmek gerekir:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Bu genellikle doğru hazırlanmış bir bildirimle sonuçlanır (yani veriler sorgudan ayrı bir pakette gönderilir). Ancak, PDO sessizce olacağını unutmayın çare o edilmektedir edebilirsiniz olanlar bu: ifadeleri taklit etmek MySQL doğal olarak hazırlamak olamayacağını listelenen kılavuzda ama) uygun sunucu sürümünü seçmek için dikkatli olun.
Doğru Düzeltme
Buradaki sorun, mysql_set_charset()
bunun yerine C API'lerini çağırmadık SET NAMES
. Eğer yapsaydık, 2006'dan beri bir MySQL sürümü kullanmamız koşuluyla iyi olur.
Daha eski bir MySQL salınımını, ardından kullanıyorsanız hata içinde mysql_real_escape_string()
böyle bizim yükü içinde olanlar gibi geçersiz baytlı karakterler amaçları kaçmak için tek bayt olarak tedavi edildi anlamına geliyordu istemci doğru bağlantı kodlama haberdar olmuştu bile bu yüzden bu saldırı olur ve hala başarılı. Hata MySQL giderilmiştir 4.1.20 , 5.0.22 ve 5.1.11 .
Ama en kötüsü olduğunu PDO
C API maruz vermedi mysql_set_charset()
böylece önceki sürümlerinde bu 5.3.6 kadar olamaz , mümkün olan her komut için bu saldırıyı önlemek! Şimdi bunun yerine kullanılması gereken bir DSN parametresi olarak ortaya çıkar ... SET NAMES
Kurtarıcı Lütuf
Başlangıçta söylediğimiz gibi, bu saldırının çalışması için veritabanı bağlantısı savunmasız bir karakter seti kullanılarak kodlanmalıdır. utf8mb4
olduğunu savunmasız değil destekleyebilir henüz ve her MySQL 5.5.3 beri yerine-ama sadece olmuştur kullanılabilir kullanmayı seçebilir böylece: Unicode karakter. Bir alternatif utf8
de savunmasız olmayan ve Unicode Temel Çok Dilli Düzlem'in tamamını destekleyebiliyor .
Alternatif olarak, NO_BACKSLASH_ESCAPES
(diğer şeylerin yanı sıra) çalışmasını değiştiren SQL modunu etkinleştirebilirsiniz mysql_real_escape_string()
. Bu mod etkinleştirildiğinde, 0x27
değiştirilecek 0x2727
ziyade 0x5c27
kaçan süreci dolayısıyla ve olamaz , daha önce var olmayan savunmasız kodlamalarla geçerli karakterleri oluşturmak (yani 0xbf27
hala 0xbf27
vb.) - Sunucu yüzden hala geçersiz olarak dize reddedecektir . Ancak, @ eggyal'in bu SQL modundan (PDO ile olmasa da) kaynaklanabilecek farklı bir güvenlik açığı konusundaki cevabına bakınız .
Güvenli Örnekler
Aşağıdaki örnekler güvenlidir:
mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
Sunucu bekliyor çünkü utf8
...
mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
Çünkü karakter setini istemci ve sunucu eşleşecek şekilde doğru şekilde ayarladık.
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
Çünkü taklit hazırlanmış ifadeleri kapattık.
$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
Çünkü karakter setini doğru ayarladık.
$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();
Çünkü MySQLi her zaman doğru hazırlanmış ifadeler yapar.
Paketleme
Eğer sen:
- MySQL'in Modern Sürümlerini (geç 5.1, tümü 5.5, 5.6 vb.) VE PDO'nun DSN karakter parametresini (PHP ≥ 5.3.6'da) kullanın
VEYA
- Bağlantı kodlaması için savunmasız bir karakter seti kullanmayın (yalnızca
utf8
/ latin1
/ ascii
/ etc kullanıyorsunuz)
VEYA
NO_BACKSLASH_ESCAPES
SQL modunu etkinleştir
% 100 güvendesin.
Aksi takdirde, PDO Hazırlık Bildirimleri kullanıyor olsanız bile savunmasızsınız ...
ek
Yavaş yavaş PHP gelecekteki bir sürümü için hazırlar taklit değil varsayılan değiştirmek için bir yama üzerinde çalışıyorum. Benim karşılaştığım sorun, bunu yaptığımda bir sürü test kırılması. Bir sorun öykünmüş hazırlanmaların sadece yürütme sırasında sözdizimi hatalarını atmasıdır, ancak gerçek hazırlıklar hazırlanma sırasında hatalar atar. Böylece bu sorunlara neden olabilir (ve testlerin canlanmasının nedeninin bir parçasıdır).