PDO SQL enjeksiyonu önlemek için yeterli tablolar hazırlanır?

6 Cevap php

Diyelim ki, bu gibi bir kod var diyelim:

$dbh = new PDO("blahblah");

$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

PDO belgelerine diyor

Hazırlanan tablolara parametreler kote olması gerekmez; Sürücü sizin için yönetir.

Is that truly all I need to do to avoid SQL injections? Is it really that easy?

Bir fark yaparsa MySQL varsayabiliriz. Ayrıca, SQL enjeksiyon karşı hazırlanan tabloların kullanımı hakkında gerçekten sadece merak ediyorum. Bu bağlamda, ben XSS veya diğer olası güvenlik açıkları umurumda değil.

6 Cevap

Hazırlanan tablolar / parametreli sorgular bu beyanı üzerine 1st order enjeksiyonu önlemek için yeterlidir. Eğer uygulama başka bir yerde un-kontrol dinamik sql kullanıyorsanız yine 2nd order enjeksiyon açıktır.

2. sıra enjeksiyon veri bir kez sorguya dahil edilmeden önce veritabanı sayesinde sağlanıncaya olmuştur anlamına gelir, ve koparmak için çok daha zordur. Sosyal mühendislik yolda içeri genellikle daha kolaydır gibi AFAIK, sen, gerçek 2. sıra saldırılarını görmek hemen hemen hiç

Bir değer daha sonraki bir sorguda bir değişmez olarak kullanılan bir veritabanında saklanır neden olabilir zaman 2. sıra enjeksiyon saldırısı gerçekleştirebilirsiniz. Bir örnek olarak, (Bu soru için MySQL DB varsayarak) bir web sitesinde bir hesap oluşturarak zaman size yeni kullanıcı adınız olarak aşağıdaki bilgileri girin diyelim:

' + (SELECT UserName + '_' + Password FROM Users LIMIT 1) + '

Username başka kısıtlamalar varsa, bir hazır deyim hala yukarıda gömülü sorgu ekin zamanında yürütmek ve doğru veritabanında değerini saklamak değil emin olur. Ancak, daha sonra uygulama veritabanından adınızı alır ve bu değer yeni bir sorgu eklemek için dize birleştirme kullandığı düşünün. Sen başkasının şifresini görmek için alabilirsiniz. Kullanıcıların tablodaki ilk birkaç isim adminleri olma eğilimindedir beri, ayrıca sadece çiftlik uzakta vermiş olabilir. (Ayrıca not: Bu düz metin parolaları saklamak için değil bir neden daha!)

Biz tablolar, tek bir sorgu için yeterli hazırlıklı olduğunu, daha sonra, bakın, ama onlar zorlamak için bir mekanizma eksikliği nedeniyle kendileri de, tüm uygulama boyunca SQL injection saldırılarına karşı korumak için not yeterli olduğu için tüm erişim uygulama içinde bir veritabanı güvenli bir kod kullanır. Bununla birlikte, iyi bir uygulama tasarımının bir parçası olarak kullanılır - Böyle dinamik sql sınırlayan bir ORM, veri katmanı, ya da servis katmanının kod inceleme veya kullanımı gibi uygulamaları içerebilir - Hazırlanan tablolar SQL Injection sorunun çözümü için birincil araçtır. Veri erişim programın geri kalanından ayrılır, öyle ki iyi bir uygulama tasarım ilkeleri, izlerseniz, her sorgu doğru parametrelendirmesini kullandığı zorlamak için kolay ya da denetim olur. Bu durumda, SQL enjeksiyonu (hem birinci ve ikinci dereceden) tamamen önlenir.

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:

  1. 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.

  2. 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 ...

  3. $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.

  4. 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.

Hayır, her zaman değil.

Bu kullanıcı girişi sorgu kendi içinde yerleştirilmesine izin vermesine bağlıdır. Örneğin:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];

$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

kullanıcı girişi, bir tanımlayıcı olarak kullanılır değil çünkü veri olarak, işe yaramaz, SQL enjeksiyon karşı savunmasız ve bu örnekte hazırlanmış deyimleri kullanarak olacaktır. Burada doğru cevap filtreleme / doğrulama gibi çeşit kullanmak olacaktır:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];
$allowedTables = array('users','admins','moderators');
if (!in_array($tableToUse,$allowedTables))    
 $tableToUse = 'users';

$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

Not: Eğer bu işe yaramazsa, yani DDL (Data Definition Language) dışında gider veri bağlamak için PDO kullanamazsınız:

$stmt = $dbh->prepare('SELECT * FROM foo ORDER BY :userSuppliedData');

Yukarıda neden çalışmıyor nedeni DESC ve ASC data değildir çünkü. PDO sadece data için kaçabilir. İkincisi, hatta bunun etrafında ' tırnak koyamazsınız. Kullanıcının seçtiği sıralama izin vermek için tek yolu elle filtre ve o da var DESC veya ASC kontrol etmektir.

Evet, yeterlidir. Enjeksiyon tip saldırıları çalışmak yolu, her nasılsa kod sanki, veri olması gereken bir şey, değerlendirmek için bir tercüman (veritabanı) alma gereğidir. (Eğer bir dize olarak bir sorgu oluşturmak zaman Örn.) aynı ortamda kod ve veri karıştırabilirsiniz Bu yalnızca mümkündür.

Parameterised sorguları ayrı ayrı kod ve verileri göndererek çalışır, bu yüzden never o bir delik bulmak mümkün olacaktır.

Ancak yine de diğer enjeksiyon tipi saldırılara karşı savunmasız olabilir. Bir HTML sayfasındaki verileri kullanmak Örneğin, XSS saldırılarını tabi olabilir.

Hayır bu (bazı özel durumlarda) yeterli değildir! Bir veritabanı sürücüsü gibi MySQL kullanarak varsayılan olarak PDO taklit hazırlanmış ifadeleri kullanır. MySQL ve PDO kullanarak, her zaman hazır deyimleri taklit devre dışı olmalıdır:

$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Hep yapılması gereken başka bir şey veritabanının doğru kodlamayı ayarlayın:

$dbh = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

Best way to prevent SQL Injection in PHP: Ayrıca bu ilgili soruya bakın

Ayrıca sadece hala verileri görüntüleyen kendinizi izlemek gerekir şeylerin veritabanı tarafı hakkında olduğunu unutmayın. Örneğin Doğru kodlama ile tekrar htmlspecialchars() kullanarak ve stil alıntı yaparak.

Şahsen ben her zaman önce veri sanitasyon çeşit aday olacağını siz ancak girilen veriyi bağlama tutucular / parametresini kullanırken sql deyimi ayrı ayrı sunucuya gönderilir ve daha sonra birlikte binded, güven kullanıcı girişi asla mümkün. Burada anahtar, bu belirli bir türde ve belirli bir kullanım için sağlanan verileri bağlar ve SQL deyiminin mantığını değiştirmek için herhangi bir fırsatı ortadan kaldırır olmasıdır.