Kısa cevap NO, PDO tüm olası SQL Injection saldırılarına karşı savunmak değil hazırlamasıdır. Belli belirsiz kenar durumlarda.
I this answer PDO hakkında konuşmak için adapte ediyorum ...
Uzun cevabı o kadar kolay değildir. Bu bir saldırı dayanıyor demonstrated here.
The Attack
Yani, saldırı göstererek kapalı 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));
Bazı durumlarda, bu fazla 1 satır döndürür. Burada neler oluyor inceleyelim:
Selecting a Character Set
$pdo->query('SET NAMES gbk');
Bu saldırı çalışması için, biz de yani ASCII gibi '
kodlamak için 0x27
and olan bazı karakter için sunucu bağlantısı bekliyor kodlama gerekir son byte bir ASCII olan \
, yani 0x5c
. {big5
, cp932
, gb2312
, bgk
ve: bu çıkıyor gibi, orada varsayılan MySQL 5.6 desteklenen 5 gibi kodlamalar [(9)]}. Biz burada gbk
seçersiniz.
Şimdi, burada SET NAMES
kullanımına dikkat etmek çok önemlidir. Bu karakter kümesi ON THE SERVER ayarlar. Orada bunu yapmanın başka bir yoludur, ama biz yakında orada alırsınız.
The Payload
Bu enjeksiyon için kullanmak için gidiyoruz yükü bayt dizisi ile başlar 0xbf27
. gbk
, geçersiz bir Çokbaytlı karakter var; in latin1
, bu dize var ¿'
. latin1
and gbk
, 0x27
o kendi başına bir sayıl '
karakter olduğunu unutmayın.
Bunun üzerine addslashes()
denilen eğer, biz daha önce, \
, yani 0x5c
bir ASCII eklemek istiyorum, çünkü bu yük seçmiş {[(3)] } karakter. 0xbf5c
0x27
izledi: Yani biz gbk
bir iki karakter dizisi olan 0xbf5c27
ile rüzgar ediyorum. Veya başka bir deyişle, bir valid karakteri çıkmamış ardından '
. Ama biz kullanarak addslashes()
. Değiliz Yani bir sonraki adıma ...
$stmt->execute()
Burada gerçekleştirmek için önemli şey PDO varsayılan NOT true hazırlanmış deyimleri yapıyor olmasıdır. Bu (MySQL için) onlara öykünür. Bu nedenle, PDO içten her bağlı dize değeri mysql_real_escape_string()
(MySQL C API işlevi) çağırarak, sorgu dizesi oluşturur.
Bu bağlantı karakter kümesini bildiği 'de C API çağrısı mysql_real_escape_string()
addslashes()
farklıdır. Bu yüzden karakteri için doğru kaçan sunucu bekliyor olduğunu ayarlayabilirsiniz gerçekleştirebilirsiniz. Ancak, bu noktaya kadar, istemci biz bunun aksi hiç söylemedim çünkü biz hala, bağlantı için latin1
kullandığınızı düşünüyor. Biz söylemek server biz gbk
kullanıyorsanız, ancak client hala düşünüyor latin1
. Yaptım
Bu nedenle mysql_real_escape_string()
için çağrı ters eğik çizgi ekler ve biz bizim içerik "kaçtı" bir serbest asılı '
karakter var! Biz $var
gbk
karakter setinde bakmak olsaydı Aslında, biz görmek istiyorum:
縗' OR 1=1 /*
Hangi saldırı gerektirir tam olarak ne olduğunu.
The Query
Bu bölüm sadece bir formalite olduğunu, ancak burada oluşturulan sorgu var:
SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
Tebrikler, sadece başarılı PDO Hazırlanmış deyimleri kullanarak bir program saldırdı ...
The Simple Fix
Şimdi, bu taklit hazırlanmış deyimleri devre dışı bırakarak bu önleyebilirsiniz fazlalaştı:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Bu usually (sorgu ayrı bir paket içinde üzerinden gönderilen verinin yani) bir gerçek hazırlanmış deyimi neden olacaktır. Ancak, farkında olmak PDO olacak sessizce fallback MySQL yerel hazırlamak olamaz ifadeleri taklit etmek: Bu kılavuzda listeed vardır ki bu, ancak uygun sunucu sürümünü seçmek için dikkat .)
The Correct Fix
Burada sorun, biz C API mysql_set_charset()
yerine SET NAMES
aramadı olmasıdır. Biz yaptıysak, biz iyi biz 2006 yılından bu yana bir MySQL sürümü kullanıyorsanız temin olurdu.
Daha önceki bir MySQL sürümü kullanıyorsanız ediyorsanız, o zaman bir bug mysql_real_escape_string()
böyle bizim yükü olanlar gibi geçersiz bir çokbaytlı karakterler kaçan amaçlar {[(2 için tek bayt olarak tedavi edildi anlamına geliyordu )]} ve dolayısıyla bu saldırı yine başarılı olur. Bug MySQL 4.1.20, 5.0.22 ve 5.1.11 giderilmiştir.
But the worst part is that PDO
didn't expose the C API for mysql_set_charset()
until 5.3.6, so in prior versions it cannot prevent this attack for every possible command!
It's now exposed as a DSN parameter, which should be used instead of SET NAMES
...
The Saving Grace
Biz başta söylediğim gibi, bu saldırı için veritabanı bağlantısı savunmasız bir karakter kümesi kullanılarak kodlanmış olması gerekir çalışmak. utf8mb4
is not vulnerable ve henüz every Unicode karakter destekleyebilir: yani bu kullanmayı tercih olabilir yerine-ama sadece MySQL 5.5.3 beri mevcut olmuştur. Alternatif bir utf8
, which is also not vulnerable ve Unicode bütün destekleyebilir Basic Multilingual Plane.
Alternatif olarak, NO_BACKSLASH_ESCAPES
SQL mode, which (amongst other things) alters the operation of mysql_real_escape_string()
etkinleştirebilirsiniz. Bu etkin modu ile, 0x27
0x2727
oldukça 0x5c27
göre ve bu nedenle kaçan işlem cannot herhangi birinde geçerli karakterleri oluşturmak ile değiştirilecektir daha önce yoktu savunmasız kodlamaları-yani sunucu hala dize geçersiz olarak reddedecektir (yani 0xbf27
hala 0xbf27
vs) olduğunu. Ancak, @eggyal's answer (değil PDO ile de olsa) bu SQL modunu kullanarak ortaya çıkabilir, farklı bir güvenlik açığı için bkz.
Safe Examples
Aşağıdaki örnekler güvenli:
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");
Çünkü sunucunun bekliyor 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");
Biz düzgün karakteri çok istemci ve sunucu maç set ettik çünkü.
$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 /*"));
Biz taklit hazırlanmış deyimleri kapalı ettik çünkü.
$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 /*"));
Biz düzgün karakter seti ettik çünkü.
$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();
MySQLi gerçek hazırlanan beyanlara her zaman yapar çünkü.
Wrapping Up
Eğer:
- (PHP ≥ 5.3.6) MySQL Modern sürümleri (geç 5.1, tüm 5.5, 5.6, vb) AND PDO'su DSN charset parametresi kullanın
OR
- (Sadece
utf8
/ latin1
/ ascii
/ vs kullanmak) bağlantısı kodlama için hassas bir karakter kümesi kullanmak etmeyin
OR
- Enable
NO_BACKSLASH_ESCAPES
SQL modu
Sen% 100 güvenli konum.
Aksi takdirde, savunmasız even though you're using PDO Prepared Statements... konum
Addendum
Ben yavaş yavaş taklit değil varsayılan değiştirmek için bir yama üzerinde çalışıyor oldum PHP'nin sonraki sürümü için hazırlar. Ben çalıştırıyorum sorun bunu yaparken testlerin bir LOT kırmak olduğunu. Bir sorun Taklit sadece execute sözdizimi hataları atacağım hazırlar olduğunu, ama gerçek hazırlamak hataları atacağım hazırlar. Böylece sorunları neden (ve testler borking edilir nedenle bir parçasıdır) olabilir.