Mysql_real_escape_string () etrafında dolaşan SQL enjeksiyonu


645

mysql_real_escape_string()Fonksiyonu kullanırken bile bir SQL enjeksiyon olasılığı var mı ?

Bu örnek durumu ele alalım. SQL PHP'de şu şekilde oluşturulmuştur:

$login = mysql_real_escape_string(GetFromPost('login'));
$password = mysql_real_escape_string(GetFromPost('password'));

$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";

Çok sayıda insanın bana böyle bir kodun hala tehlikeli ve mysql_real_escape_string()kullanılan işlevle bile kesmek mümkün olduğunu söylediğini duydum . Ama olası bir istismar düşünemiyorum?

Bunun gibi klasik enjeksiyonlar:

aaa' OR 1=1 --

çalışma.

Yukarıdaki PHP kodundan geçebilecek olası bir enjeksiyon biliyor musunuz?


34
@ThiefMaster - Geçersiz kullanıcı / geçersiz şifre gibi ayrıntılı hatalar vermemeyi tercih ediyorum ... kaba kuvvet tüccarlarına geçerli bir kullanıcı kimliğine sahip olduklarını ve tahmin etmeleri gereken şifre olduğunu söylüyor
Mark Baker

18
Yine de kullanılabilirlik açısından korkunç. Bazen ana takma adınızı / kullanıcı adınızı / e-posta adresinizi kullanamazsınız ve bir süre sonra bunu unutamazsınız veya site etkin olmadığından hesabınızı silmiş olabilir. Parolaları denemeye devam ederseniz ve hatta geçersiz olan kullanıcı adınız olsa bile IP'nizin engellenmesini sağlamanız son derece can sıkıcıdır.
ThiefMaster

50
Lütfen mysql_*yeni kodda işlevleri kullanmayın . Artık bakımları yapılmıyor ve kullanımdan kaldırılma süreci başladı. Bkz kırmızı kutuyu ? Bunun yerine hazırlanmış ifadeler hakkında bilgi edininve PDO veya MySQLi kullanın - bu makale hangisine karar vermenize yardımcı olacaktır. PDO'yu seçerseniz, iyi bir öğretici İşte .
tereško

13
machineaddict, 5.5'ten beri (son zamanlarda piyasaya sürüldü) mysql_*işlevler zaten E_DEPRECATEDuyarı üretiyor . ext/mysqlUzatma daha sonra 10 yıl boyunca muhafaza edilmemiştir. Gerçekten bu kadar yanıltıcı mısın?
tereško

13
@machineaddict PHP 7.0 üzerinde bu uzantıyı kaldırmışlar ve henüz 2050 değil.
GGG

Yanıtlar:


379

Aşağıdaki sorguyu düşünün:

$iId = mysql_real_escape_string("1 OR 1=1");    
$sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string()sizi buna karşı korumaz. Sorgunuzdaki değişkenlerinizin etrafında tek tırnak ( ' ') kullanmanız sizi buna karşı koruyan şeydir. Aşağıdakiler de bir seçenektir:

$iId = (int)"1 OR 1=1";
$sSql = "SELECT * FROM table WHERE id = $iId";

9
Ancak bu gerçek bir sorun olmaz, çünkü mysql_query()birden fazla ifade çalıştırmaz, değil mi?
Pekka

11
@Pekka, Her zamanki örnek olmasına rağmen, DROP TABLEpratikte saldırgan daha olasıdır SELECT passwd FROM users. İkinci durumda, ikinci sorgu genellikle bir UNIONyan tümce kullanılarak yürütülür .
Jacco

58
(int)mysql_real_escape_string- bu anlamlı değil. Hiç farklı değil (int). Ve her girdi için aynı sonucu üretecekler
zerkms

28
Bu, fonksiyonun her şeyden çok yanlış kullanımından ibarettir. Sonuçta, adlandırılmış mysql_real_escape_string, değil mysql_real_escape_integer. Tamsayı alanlarıyla kullanılmak anlamına gelmez.
NullUserException

11
@ircmaxell, Ancak cevap tamamen yanıltıcı. Açıkçası soru tırnak içindeki içeriği soruyor . "Alıntılar yok" bu sorunun cevabı değil .
Pacerier

629

Kısa cevap evet, evet etrafta dolaşmanın bir yolu varmysql_real_escape_string() .

Çok OBSCURE KENAR DURUMLARı için !!!

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

mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Belirli durumlarda, bu 1'den fazla satır döndürür. Burada olanları inceleyelim:

  1. Karakter Kümesi Seçme

    mysql_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, gbkve sjis. Seçeceğizgbk .

    Şimdi, SET NAMESburada kullanımını not etmek çok önemlidir . Bu, SUNUCU ÜZERİNDEKİ karakter setini ayarlar . C API işlevine yapılan çağrıyı kullansaydık mysql_set_charset()iyi olur (2006'dan beri MySQL sürümlerinde). Ama neden bir dakika içinde daha fazla ...

  2. Yük

    Bu enjeksiyon için kullanacağımız yük, bayt dizisi ile başlar 0xbf27. Bu gbkgeçersiz bir çokbaytlı karakterdir; içinde latin1, bu dize ¿'. İçinde olduğunu unutmayın latin1 ve gbk , 0x27kendi 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 0xbf5c27olan bir rüzgârla karşılaşırız gbk: 0xbf5carkası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 ...

  3. mysql_real_escape_string ()

    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 latin1bağlantı için kullandığımızı düşünüyor , çünkü bunu asla aksini söylemedik. Biz söyledin sunucu Kullandığımız gbkancak 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, $variçinde gbkkarakter kümesi, biz görürdük:

    OR 'VEYA 1 = 1 / *

    Bu tam olarak saldırının gerektirdiği şeydir .

  4. Sorgu

    Bu bölüm sadece bir formalite, ancak işlenmiş sorgu:

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1

Tebrikler, başarıyla bir programa başarıyla saldırdınız mysql_real_escape_string()...

Kötü

Daha da kötüleşiyor. PDOvarsayılan taklit MySQL ile hazırlanan ifadeleri. Bu, istemci tarafında, temel olarak bir mysql_real_escape_string()C sprintf (C kütüphanesinde) yaptığı anlamına gelir, bu da aşağıdakilerin başarılı bir enjeksiyonla sonuçlanacağı anlamına gelir:

$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Ş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 deyimle 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.

Çirkin

Ben Kullandığımız olsaydı biz tüm bu engelleyebilecek çok başlayarak söyledi mysql_set_charset('gbk')yerine SET NAMES gbk. 2006'dan beri bir MySQL sürümü kullanmanız koşuluyla bu doğrudur.

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 PDOC 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! Artık bir DSN parametresi olarak gösterilmektedir .

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. utf8mb4olduğ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 utf8de 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, 0x27değiştirilecek 0x2727ziyade 0x5c27kaçan süreci dolayısıyla ve olamaz , daha önce var olmayan savunmasız kodlamalarla geçerli karakterleri oluşturmak (yani 0xbf27hala 0xbf27vb.) - Sunucu yüzden hala geçersiz olarak dize reddedecektir . Bununla birlikte, bu SQL modunu kullanmaktan kaynaklanabilecek farklı bir güvenlik açığı için @ eggyal'ın cevabına bakın .

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:

  • Modern MySQL Sürümlerini (geç 5.1, tümü 5.5, 5.6 vb.) VE mysql_set_charset() / $mysqli->set_charset()/ 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)

% 100 güvendesin.

Aksi halde, kullanmanıza rağmenmysql_real_escape_string() savunmasızsınız ...


3
PDO öykünmesi gerçekten MySQL için ifadeler hazırlamak? Sürücü bunu doğal olarak desteklediğinden, bunu yapmasının nedenini göremiyorum. Hayır?
netcoder

16
Öyle. Belgelerde öyle olmadığını söylüyorlar. Ancak kaynak kodunda açıkça görülebilir ve düzeltilmesi kolaydır. Bunu geliştiricilerin yetersizliğine tebeşirlendiriyorum.
Theodore R. Smith

5
@ TheodoreR.Smith: Düzeltmek o kadar kolay değil. Varsayılanı değiştirmeye çalışıyorum, ancak değiştirildiğinde bir tekne yükü testinde başarısız oluyor. Yani göründüğünden daha büyük bir değişiklik. Hala 5.5 ile bitirmeyi umuyorum ...
ircmaxell

14
@shadyyx: Hayır, açıklanan makalenin güvenlik açığıydı addslashes. ben merkezli olan birinde bu açığı. Kendin dene. Git MySQL 5.0 alın ve bu istismar çalıştırın ve kendiniz görün. Bunu PUT / GET / POST'a nasıl koyacağımız kadarıyla, TRIVIAL. Girdi verileri sadece bayt akışlarıdır. char(0xBF)bayt üretmenin okunaklı bir yoludur. Bu güvenlik açığını birden fazla konferansın önünde canlı olarak gösterdim. Bana bu konuda güven ... Ama eğer yapmazsan, kendin dene. Çalışıyor ...
ircmaxell

5
@shadyyx: ?var=%BF%27+OR+1=1+%2F%2AURL'de, $var = $_GET['var'];kodda $ _GET'de böyle bir eğlence geçirmeye gelince ve Bob amcan.
cHao

183

TL; DR

mysql_real_escape_string()aşağıdaki durumlarda hiçbir koruma sağlamayacaktır (ve ayrıca verilerinizi daha da engelleyebilir):

  • MySQL en NO_BACKSLASH_ESCAPES SQL modu etkindir ( her bağlandığınızda açıkça başka bir SQL modu seçmediğiniz sürece olabilir ); ve

  • SQL dizgi değişmezleriniz çift tırnak "karakterleri kullanılarak alıntılanır .

Bu, hata # 72458 olarak dosyalandı ve MySQL v5.7.6'da düzeltildi (aşağıdaki " The Saving Grace " başlığına bakın).

Bu başka, (belki daha az?) Belirsiz KENAR ÖRNEK !!!

Hürmeten @ ircmaxell mükemmel bir cevap (gerçekten, bu dalkavukluk değil intihal olması gerekiyordu!), Onun biçimini kabul edecek:

Saldırı

Bir gösteri ile başlıyor ...

mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"'); // could already be set
$var = mysql_real_escape_string('" OR 1=1 -- ');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

Bu, tüm kayıtları testtablodan döndürür . Bir diseksiyon:

  1. SQL Modu Seçme

    mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');

    Dize Değişmezleri altında belgelendiği gibi :

    Bir dizeye tırnak işareti karakterleri eklemenin birkaç yolu vardır:

    • '” İle alıntılanan bir dizenin içindeki “ '”, “'' .

    • "” İle alıntılanan bir dizenin içindeki “ "”, “"" .

    • Alıntı karakterinden önce bir kaçış karakteri (“\ ”) .

    • '” İle belirtilen bir dize içindeki “ "” özel bir işleme ihtiyaç duymaz ve iki katına çıkarılmamalı veya kaçmamalıdır. Aynı şekilde, “ "” ile belirtilen bir ipin içindeki “ '” özel bir işleme ihtiyaç duymaz.

    Sunucunun SQL modu şunları içeriyorsa NO_BACKSLASH_ESCAPES , bu seçeneklerin üçüncüsü - ki benimsediği genel yaklaşım bu mysql_real_escape_string()değildir - bunun yerine ilk iki seçenekten biri kullanılmalıdır. Dördüncü merminin etkisinin, kişinin verilerini munging yapmaktan kaçınmak için edebi alıntı yapmak için kullanılacak karakteri mutlaka bilmesi gerektiğine dikkat edin.

  2. Yük

    " OR 1=1 -- 

    Yük, bu enjeksiyonu kelimenin tam anlamıyla " karakterle . Belirli bir kodlama yok. Özel karakter yok. Tuhaf bayt yok.

  3. mysql_real_escape_string ()

    $var = mysql_real_escape_string('" OR 1=1 -- ');

    Neyse ki, mysql_real_escape_string()SQL modunu kontrol eder ve davranışını buna göre ayarlar. Bakınız libmysql.c:

    ulong STDCALL
    mysql_real_escape_string(MYSQL *mysql, char *to,const char *from,
                 ulong length)
    {
      if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES)
        return escape_quotes_for_mysql(mysql->charset, to, 0, from, length);
      return escape_string_for_mysql(mysql->charset, to, 0, from, length);
    }

    Böylece farklı bir altta yatan işlev, escape_quotes_for_mysql() , NO_BACKSLASH_ESCAPESSQL modu kullanılıyorsa çağrılır . Yukarıda belirtildiği gibi, böyle bir fonksiyonun, diğer tırnak karakterinin kelimenin tam anlamıyla tekrarlanmasına neden olmadan tekrar edebilmek için değişmezi alıntılamak için hangi karakterin kullanılacağını bilmesi gerekir.

    Ancak, bu işlev isteğe bağlı olarak dizenin tek tırnak karakteri kullanılarak tırnak içine alınacağını varsayar' . Bakınız charset.c:

    /*
      Escape apostrophes by doubling them up
    
    // [ deletia 839-845 ]
    
      DESCRIPTION
        This escapes the contents of a string by doubling up any apostrophes that
        it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
        effect on the server.
    
    // [ deletia 852-858 ]
    */
    
    size_t escape_quotes_for_mysql(CHARSET_INFO *charset_info,
                                   char *to, size_t to_length,
                                   const char *from, size_t length)
    {
    // [ deletia 865-892 ]
    
        if (*from == '\'')
        {
          if (to + 2 > to_end)
          {
            overflow= TRUE;
            break;
          }
          *to++= '\'';
          *to++= '\'';
        }

    Böylece, değişmezi alıntılamak için kullanılan gerçek karakterden bağımsız olarak çift ​​tırnak "karakterlerine dokunulmaz (ve tüm tek tırnak 'karakterlerini ikiye katlar ) ! Bizim durumda sağlandığı argüman olarak tamamen aynı kalıntılar kaçış yok yerini almıştır sanki -Bu hiç .$varmysql_real_escape_string()

  4. Sorgu

    mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

    Resmiyetle ilgili bir şey, işlenen sorgu:

    SELECT * FROM test WHERE name = "" OR 1=1 -- " LIMIT 1

Öğrenilen arkadaşımın söylediği gibi: tebrikler, bir programı başarıyla kullanarak saldırdınız mysql_real_escape_string()...

Kötü

mysql_set_charset()bunun karakter kümeleriyle ilgisi olmadığı için yardım edemez; ne de olabilir mysqli::real_escape_string(), çünkü bu aynı işlevin etrafında sadece farklı bir sargıdır.

Sorun, daha açık değilse, daha sonraki bir zamanda karar vermek için geliştiriciye bırakıldığı için, değişmezin hangi karakterle alıntılanacağını mysql_real_escape_string() bilememe çağrısında bulunulmasıdır . Bu nedenle, NO_BACKSLASH_ESCAPESmodda, bu işlevin keyfi tırnaklarla kullanmak için her girdiden güvenle kaçabilmesinin hiçbir yolu yoktur (en azından, iki katına çıkmayı gerektirmeyen ve böylece verilerinizi munginglemeyen karakterleri iki katına çıkarmadan).

Çirkin

Daha da kötüleşiyor. NO_BACKSLASH_ESCAPESstandart SQL ile uyumluluk gerekliliği nedeniyle vahşi doğada nadiren görülmeyebilir (örneğin, SQL-92 spesifikasyonunun bölüm 5.3'e , yani <quote symbol> ::= <quote><quote>dilbilgisi üretimine ve ters eğik çizgiye verilen özel bir anlamın eksikliğine bakınız). Dahası, kullanımı açıkça ircmaxell'in gönderisinin açıkladığı (uzun süredir sabit olan) hataya geçici bir çözüm olarak önerildi. Kim bilir, bazı DBA'lar varsayılan olarak açık olacak şekilde yapılandırılabilir.addslashes() .

Ayrıca, yeni bir bağlantının SQL modu sunucu tarafından yapılandırmasına göre ayarlanır ( SUPERkullanıcının istediği zaman değiştirebilir); bu nedenle, sunucunun davranışından emin olmak için her zaman bağlandıktan sonra istediğiniz modu açıkça belirtmeniz .

Kurtarıcı Lütuf

SQL modunu her zaman açıkçaNO_BACKSLASH_ESCAPES tek tırnak karakteri kullanarak MySQL dizgi değişmezlerini içermeyecek veya teklif etmeyecek şekilde ayarladığınız sürece , bu hata çirkin başını arkadan ayıramaz:escape_quotes_for_mysql() kullanılmayacak veya hangi alıntı karakterlerinin tekrarlanmasını gerektireceği varsayımı doğru ol.

Bu nedenle, ben kullanarak o kimseye tavsiye NO_BACKSLASH_ESCAPESde sağlarANSI_QUOTES tek tırnaklı dize değişmezlerinin alışılmış kullanımını zorlayacağından modu modu . Bunun, çift tırnaklı değişmez değerlerin kullanılması durumunda SQL enjeksiyonunu engellemediğini unutmayın; yalnızca bunun gerçekleşme olasılığını azaltır (çünkü normal, kötü amaçlı olmayan sorgular başarısız olur).

PDO'da, hem eşdeğer işlevi PDO::quote()hem de hazırlanan ifade öykünücüsü mysql_handle_quoter(), tam olarak bunu yapan: kaçan hazır bilginin tek tırnak içinde verilmesini sağlar, böylece PDO'nun bu hatadan her zaman bağışık olduğundan emin olabilirsiniz.

MySQL v5.7.6'dan itibaren bu hata düzeltildi. Değişiklik günlüğüne bakın :

İşlevsellik Eklendi veya Değiştirildi

Güvenli Örnekler

İrcmaxell tarafından açıklanan hata ile birlikte alındığında, aşağıdaki örnekler tamamen güvenlidir (birinin 4.1.20, 5.0.22, 5.1.11'den sonraki MySQL kullandığını veya bir GBK / Big5 bağlantı kodlaması kullanmadığını varsayarsak) :

mysql_set_charset($charset);
mysql_query("SET SQL_MODE=''");
$var = mysql_real_escape_string('" OR 1=1 /*');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

... çünkü açıkça içermeyen bir SQL modu seçtik NO_BACKSLASH_ESCAPES.

mysql_set_charset($charset);
$var = mysql_real_escape_string("' OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

... çünkü dizgi değişmezimizi tek tırnakla alıntılıyoruz.

$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(["' OR 1=1 /*"]);

... PDO tarafından hazırlanan ifadeler bu güvenlik açığından etkilenmediğinden (ve ircmaxell'in PHP5.5.6 kullanmanız ve karakter kümesinin DSN'de doğru bir şekilde ayarlanmış olması ya da hazırlanan ifade öykünmesinin devre dışı bırakılması şartıyla) .

$var  = $pdo->quote("' OR 1=1 /*");
$stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");

... çünkü PDO'nun quote()işlevi sadece değişmez kelimeden kaçmakla kalmaz, aynı zamanda tırnak içine alır (tek tırnaklı 'karakterlerle); Bu durumda ircmaxell en hatayı önlemek için not, sen gerekir PHP≥5.3.6 kullanıyor ve doğru DSN karakter kümesi belirledik.

$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "' OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

... çünkü MySQLi tarafından hazırlanan ifadeler güvenlidir.

Paketleme

Böylece, eğer:

  • yerel hazırlanmış ifadeleri kullan

VEYA

  • MySQL v5.7.6 veya üstünü kullan

VEYA

  • içinde ilave en az biri ircmaxell özetinde çözümlerden bir, kullanım istihdam etmek:

    • PDO;
    • tek tırnaklı dize değişmezleri; veya
    • içermeyen açıkça ayarlanmış bir SQL modu NO_BACKSLASH_ESCAPES

... o zaman tamamen güvende olmalısınız (dize kaçan dize kapsamı dışındaki güvenlik açıkları).


10
Yani, TP, siz tek tırnak kullanmıyorsanız enjeksiyonu yol açabilecek NO_BACKSLASH_ESCAPES mysql server modu yoktur" gibi DR olurdu.
Sizin Common Sense

Ben erişim mümkün değilim bugs.mysql.com/bug.php?id=72458 ; Sadece erişim reddedildi sayfası alıyorum. Güvenlik sorunu olduğu için halktan gizleniyor mu? Ayrıca, bu yanıttan, güvenlik açığının keşfedicisi olduğunuzu doğru anladım mı? Eğer öyleyse, tebrikler.
Mark Amery

1
İnsanlar "en başta dizeler için kullanmamalıdır . SQL bunun tanımlayıcılar için olduğunu söylüyor. Ama eh ... "vida standartları, istediğimi yapacağım" diyen MySQL'in başka bir örneği. (Neyse ki, ANSI_QUOTESalıntı kırığını düzeltme moduna dahil edebilirsiniz . Bununla birlikte, standartların açık göz ardı edilmesi, daha ciddi önlemler gerektirebilecek daha büyük bir konudur.)
cHao

2
@DanAllen: cevabım sen bununla, biraz daha geniş olduğunu olabilir kaçınmak bu özel PDO'su aracılığıyla hata quote()genellikle önlemek enjeksiyonuna fonksiyon-ama hazırlanan tabloların olan çok daha güvenli ve daha uygun bir yol. Tabii ki, kaçan değişkenleri doğrudan SQL'inize birleştirdiyseniz, daha sonra hangi yöntemleri kullanırsanız kullanın kesinlikle enjeksiyona karşı savunmasızsınız demektir.
eggyal

1
@eggyall: Sistemimiz yukarıdaki 2. güvenli örneğe dayanıyor. Mysql_real_escape_string öğesinin atlandığı hatalar var. Bunları acil durum modunda düzeltmek, düzeltmelerden önce nükleer silah almamamızı umarak ihtiyatlı bir yol gibi görünüyor. Benim mantığım hazırlanmış ifadelere dönüştürmek, bundan sonra gelmesi gereken çok daha uzun bir süreç olacaktır. Hazırlanan ifadelerin nedeni, hataların güvenlik açığı oluşturmadığı gerçeğinden daha mı güvenli? Başka bir deyişle, doğru şekilde uygulanmış mı? Yukarıdaki 2. örnek hazırlanan ifadeler kadar güvenli midir?
DanAllen

18

Aslında, %joker karakter dışında bundan geçebilecek hiçbir şey yok . LIKESaldırganın bunu %filtrelemezseniz giriş olarak girebileceği ve kullanıcılarınızdan herhangi birinin parolasını bruteforce etmesi gerektiği için deyimi kullanıyorsanız tehlikeli olabilir . Veriler sorgunun kendisine bu şekilde müdahale edemeyeceğinden, insanlar genellikle% 100 güvenli hale getirmek için hazırlanan ifadeleri kullanmanızı önerir. Ancak bu kadar basit sorgular için, muhtemelen böyle bir şey yapmak daha verimli olacaktır.$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);


2
+1, ancak joker karakterler LIKE yan tümcesi içindir, basit eşitlik için değildir.
Dor

7
more efficientHazırlanan ifadeleri kullanmaktan ziyade hangi önlemi almayı düşünüyorsunuz ? (Hazırlanan ifadeler her zaman çalışır, saldırı durumunda kütüphane hızlı bir şekilde düzeltilebilir, insan hatasını [tam değiştirme dizesini yanlış yazmak gibi] ortaya çıkarmaz ve ifade yeniden kullanılırsa önemli performans avantajlarına sahiptir.)
MatBailie

7
@Slava: Kullanıcı adlarını ve şifreleri yalnızca kelime karakterleriyle etkili bir şekilde sınırlandırıyorsunuz. Güvenlik hakkında bir şey bilen çoğu kişi, arama alanını önemli ölçüde daralttığı için kötü bir fikir olduğunu düşünür. Elbette, açık metin şifrelerini veritabanında saklamanın da kötü bir fikir olduğunu düşünüyorlardı, ancak sorunu birleştirmemize gerek yok. :)
cHao

2
@cHao, önerim sadece girişlerle ilgili. Açıkçası şifreleri filtrelemenize gerek yok, üzgünüm cevabımda açıkça belirtilmedi. Ama aslında bu iyi bir fikir olabilir. Hatırlanması zor ve tipi "a4üua3! @V \" ä90; 8f "yerine" taş cahil ağaç alanı "kullanmak kaba kuvvet için çok daha zor olurdu. tam olarak 4 kelime kullandınız - bu hala kabaca 3.3 * 10 ^ 12 kombinasyon olurdu. :)
Slava

2
@Slava: Bu fikri daha önce görmüştüm; bkz. xkcd.com/936 . Sorun şu ki, matematik tam olarak bunu desteklemiyor. Örnek 17 karakterlik şifrenizin 96 ^ 17 olasılıkları vardır ve bu, umlautları unuttuysanız ve kendinizi yazdırılabilir ASCII ile sınırladıysanız. Bu yaklaşık 4.5x10 ^ 33. Tam anlamıyla kaba kuvvet için milyar trilyon kat daha fazla işten bahsediyoruz . 8 karakterlik bir ASCII şifresinin bile 7,2x10 ^ 15 olasılıkları vardır - 3 bin kat daha fazla.
cHao
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.