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ı test
tablodan döndürür . Bir diseksiyon:
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.
Yük
" OR 1=1 --
Yük, bu enjeksiyonu kelimenin tam anlamıyla "
karakterle . Belirli bir kodlama yok. Özel karakter yok. Tuhaf bayt yok.
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_ESCAPES
SQL 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ç .$var
mysql_real_escape_string()
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_ESCAPES
modda, 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_ESCAPES
standart 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 ( SUPER
kullanı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_ESCAPES
de 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
... o zaman tamamen güvende olmalısınız (dize kaçan dize kapsamı dışındaki güvenlik açıkları).