PHP'de SQL enjeksiyonunu nasıl önleyebilirim?


2773

Kullanıcı girişi bir SQL sorgusuna değişiklik yapılmadan eklenirse, uygulama aşağıdaki örnekte olduğu gibi SQL enjeksiyonuna karşı savunmasız hale gelir :

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

Bunun nedeni, kullanıcı gibi bir şey girebilmesidir value'); DROP TABLE table;--ve sorgu şu hale gelir:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

Bunun olmasını önlemek için ne yapılabilir?

Yanıtlar:


8959

Hazırlanan ifadeleri ve parametreli sorguları kullanın. Bunlar, herhangi bir parametreden ayrı olarak veritabanı sunucusuna gönderilen ve veritabanı sunucusu tarafından ayrıştırılan SQL ifadeleridir. Bu şekilde bir saldırganın kötü amaçlı SQL enjekte etmesi imkansızdır.

Bunu başarmak için temel olarak iki seçeneğiniz vardır:

  1. PDO kullanma (desteklenen herhangi bir veritabanı sürücüsü için):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute([ 'name' => $name ]);
    
    foreach ($stmt as $row) {
        // Do something with $row
    }
  2. MySQLi kullanma (MySQL için):

    $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
    $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
    $stmt->execute();
    
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        // Do something with $row
    }

Eğer MySQL dışında bir veritabanına bağlanıyorsanız, (örneğin, referans verebilmek bir sürücüye özel ikinci bir seçenek yoktur pg_prepare()ve pg_execute()PostgreSQL için). PDO evrensel seçenektir.


Bağlantıyı doğru şekilde yapma

PDOBir MySQL veritabanına erişmek için kullanıldığında , gerçek hazırlanmış ifadelerin varsayılan olarak kullanılmadığını unutmayın . Bunu düzeltmek için hazırlanan ifadelerin öykünmesini devre dışı bırakmanız gerekir. PDO kullanarak bağlantı oluşturmanın bir örneği:

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

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Yukarıdaki örnekte hata modu kesinlikle gerekli değildir, ancak eklenmesi önerilir . Bu şekilde komut dosyası bir Fatal Errorşeyler ters gittiğinde durmaz . Ve geliştiriciye n gibi s catcholan hatalara şans verir .throwPDOException

Nedir zorunlu Ancak ilk setAttribute()devre dışı Taklit hazırlanmış tablolara ve kullanımı PDO söyler hattı, gerçek hazırlanmış deyimleri. Bu, ifadenin ve değerlerin MySQL sunucusuna gönderilmeden önce PHP tarafından ayrıştırılmadığından emin olur (olası bir saldırgana kötü amaçlı SQL enjekte etme şansı vermez).

charsetYapıcı seçeneklerinde ayarlayabilseniz de , PHP'nin 'eski' sürümlerinin (5.3.6'dan önce) DSN'deki charset parametresini sessizce yok saydığını unutmamak önemlidir .


açıklama

Geçtiğiniz SQL deyimi prepare, veritabanı sunucusu tarafından ayrıştırılır ve derlenir. Parametreleri belirterek ( yukarıdaki örnekte olduğu ?gibi a veya adlandırılmış bir parametre :name) veritabanı motoruna nereye filtre uygulamak istediğinizi bildirirsiniz. Sonra aradığın zamanexecute , hazırlanmış deyim belirttiğiniz parametre değerleri ile birleştirilir.

Burada önemli olan, parametre değerlerinin SQL dizesi ile değil, derlenmiş deyimle birleştirilmesidir. SQL enjeksiyonu, veritabanına göndermek üzere SQL oluşturduğunda komut dosyasını kötü amaçlı dizeler dahil etmek için kandırarak çalışır. Dolayısıyla, gerçek SQL'i parametrelerden ayrı olarak göndererek, niyet etmediğin bir şeyle sonuçlanma riskini sınırlarsın.

Hazırlanan bir deyimi kullanırken gönderdiğiniz parametreler yalnızca dize olarak ele alınacaktır (veritabanı motoru bazı optimizasyonlar yapabilir, ancak parametreler elbette sayı olarak da sonuçlanabilir). Yukarıdaki örnekte, $namedeğişken içeriyorsa 'Sarah'; DELETE FROM employees, sonuç yalnızca dize için bir arama olur "'Sarah'; DELETE FROM employees"ve boş bir tabloyla sonuçlanmazsınız .

Hazırlanan ifadeleri kullanmanın diğer bir yararı, aynı ifadeyi aynı oturumda birçok kez yürütmeniz durumunda, yalnızca bir kez ayrıştırılıp derlenerek size bazı hız kazanımları vermesidir.

Oh, ve bir ekleme için nasıl yapılacağını sorduğunuz için, bir örnek (PDO kullanarak):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute([ 'column' => $unsafeValue ]);

Hazırlanan ifadeler dinamik sorgular için kullanılabilir mi?

Sorgu parametreleri için hazırlanmış ifadeleri kullanmaya devam edebilmenize rağmen, dinamik sorgunun yapısı parametrelendirilemez ve bazı sorgu özellikleri parametrelendirilemez.

Bu özel senaryolar için yapılacak en iyi şey, olası değerleri kısıtlayan bir beyaz liste filtresi kullanmaktır.

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}

86
Sadece eklemek için burada başka bir yerde görmedim, başka bir savunma hattı sql enjeksiyon saldırıları aramak için ayarlanmış kuralları olabilir bir web uygulaması güvenlik duvarı (WAF):
jkerak

37
Ayrıca, mysql_query resmi belgeleri sadece bir sorgu yürütmek için izin verir, bu yüzden yanı sıra herhangi bir sorgu; yoksayılır. Bu zaten kullanımdan kaldırılmış olsa bile, PHP 5.5.0 altında birçok sistem vardır ve bu işlevi kullanabilir. php.net/manual/tr/function.mysql-query.php
Randall Valenciano

12
Bu kötü bir alışkanlıktır, ancak sorun sonrası bir çözümdür: Sadece SQL enjeksiyonu için değil, herhangi bir enjeksiyon türü için (örneğin F3 framework v2'de bir görünüm şablonu enjeksiyon deliği vardı), eski bir web siteniz veya uygulamanız varsa Enjeksiyon kusurlarından bir çözüm, supperglobal önceden tanımlanmış değişkenlerinizin değerlerini bootstrap'ta kaçan değerlerle $ _POST gibi yeniden atamaktır. PDO ile hala kaçmak mümkündür (bugünkü çerçeveler için de): substr ($ pdo-> quote ($ str, \ PDO :: PARAM_STR), 1, -1)
Alix

15
Bu cevap, hazırlanmış bir ifadenin ne olduğu - tek bir şey - açıklamasından yoksundur, talebiniz sırasında çok sayıda hazırlanmış ifade kullanırsanız ve bazen 10x performans isabını açıklar. Daha iyi bir durum, parametre bağlama kapalı PDO kullanımı, ancak deyim hazırlama kapalı olacaktır.
Donis

6
PDO kullanmak daha iyidir, doğrudan sorgu kullanıyorsanız mysqli :: escape_string
Kassem

1652

Kullanımdan Kaldırıldı Uyarı: Bu yanıtın örnek kodu (sorunun örnek kodu gibi), MySQLPHP 5.5.0'da kaldırılan ve tamamen PHP 7.0.0'da kaldırılan PHP'nin uzantısını kullanır .

Güvenlik Uyarısı : Bu yanıt en iyi güvenlik uygulamalarıyla uyumlu değildir. Kaçış SQL enjeksiyonunu önlemek için yetersizdir , bunun yerine hazırlanmış ifadeleri kullanın. Aşağıda özetlenen stratejiyi kullanmak kendi sorumluluğunuzdadır. (Ayrıca, mysql_real_escape_string()PHP 7'de kaldırıldı.)

PHP'nin yeni bir sürümünü kullanıyorsanız, mysql_real_escape_stringaşağıda özetlenen seçenek artık kullanılamayacaktır ( mysqli::escape_stringmodern bir eşdeğer olsa da). Bu günlerde mysql_real_escape_stringseçenek sadece eski bir PHP sürümünde eski kod için anlamlıdır.


İki seçeneğiniz var - özel karakterlerden kaçmak unsafe_variableveya parametreli bir sorgu kullanmak. Her ikisi de sizi SQL enjeksiyonundan koruyacaktır. Parametreli sorgu daha iyi uygulama olarak kabul edilir, ancak kullanabilmeniz için PHP'de daha yeni bir MySQL uzantısına geçmeniz gerekir.

İlk önce kaçan düşük çarpma ipini ele alacağız.

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

Ayrıca, mysql_real_escape_stringişlevin ayrıntılarına bakın .

Parametreli sorguyu kullanmak için MySQL işlevleri yerine MySQLi kullanmanız gerekir . Örneğinizi yeniden yazmak için aşağıdakine benzer bir şeye ihtiyacımız var.

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

Orada okumak isteyeceğiniz temel fonksiyon olacaktır mysqli::prepare.

Ayrıca, diğerlerinin önerdiği gibi, PDO gibi bir şeyle soyutlama katmanını yükseltmenin yararlı / kolay olduğunu görebilirsiniz .

Sorduğunuz vakanın oldukça basit olduğunu ve daha karmaşık vakaların daha karmaşık yaklaşımlar gerektirebileceğini lütfen unutmayın. Özellikle:

  • SQL'in yapısını kullanıcı girdisine göre değiştirmek isterseniz, parametreli sorgular yardımcı olmaz ve gereken çıkış kaçınılmazdır mysql_real_escape_string. Bu tür bir durumda, yalnızca 'güvenli' değerlere izin verildiğinden emin olmak için kullanıcının girişini beyaz listeden geçirmek daha iyi olur.
  • Bir koşulda kullanıcı girişinden tamsayılar kullanır ve mysql_real_escape_stringyaklaşımı kullanırsanız, Polinom tarafından aşağıdaki yorumlarda açıklanan sorundan muzdarip olursunuz . Tamsayı tırnaklarla çevrili olmadığından bu durum daha zordur, bu nedenle kullanıcı girişinin yalnızca rakam içerdiğini doğrulayarak başa çıkabilirsiniz.
  • Farkında olmadığım başka durumlar da var. Sen bulabilir bu karşılaşabileceğiniz daha ince sorunların bazıları üzerinde yararlı bir kaynaktır.

1
kullanarak mysql_real_escape_stringyeterli veya ben de parametreli kullanmak gerekir?
peiman F.8

5
@peimanF. yerel bir projede bile parametreli sorgular kullanma konusunda iyi bir uygulama sürdürün. Parametrelendirilmiş sorgular ile SQL enjeksiyonu yapılmayacağı garanti edilir. Ama birlikte (örneğin bir metinde HTML kodunu koyarak olarak, yani XSS enjeksiyonu) sahte alma önlemek için, veri sterilize edilmelidir akılda tutmak htmlentitiesörneğin
Goufalite

2
@peimanF. Parametrelenmiş sorgulara ve bağlama değerlerine iyi uygulama, ancak gerçek kaçış dizesi şimdilik iyi
Richard

Tamlık için dahil edilmesini anlıyorum mysql_real_escape_string(), ancak önce en hataya açık yaklaşımı listelemenin hayranı değilim. Okuyucu hızlı bir şekilde ilk örneği alabilir. İyi bir şey şu anda kullanımdan kaldırıldı :)
Steen Schütt

1
@ SteenSchütt - Tüm mysql_*işlevler kullanımdan kaldırıldı. Bunların yerini benzer mysqli_* işlevler aldı mysqli_real_escape_string.
Rick James

1073

Buradaki her cevap sorunun sadece bir kısmını kapsamaktadır. Aslında, dinamik olarak SQL'e ekleyebileceğimiz dört farklı sorgu parçası vardır:

  • dizi
  • bir sayı
  • bir tanımlayıcı
  • bir sözdizimi anahtar kelimesi

Hazırlanan ifadeler sadece ikisini kapsar.

Ancak bazen operatörler veya tanımlayıcılar ekleyerek sorgumuzu daha da dinamik hale getirmeliyiz. Bu yüzden farklı koruma tekniklerine ihtiyacımız olacak.

Genel olarak, böyle bir koruma yaklaşımı beyaz listeye dayanır .

Bu durumda, her dinamik parametrenin kodunuzda sabit kodlanması ve bu kümeden seçilmesi gerekir. Örneğin, dinamik sipariş vermek için:

$orders  = array("name", "price", "qty"); // Field names
$key = array_search($_GET['sort'], $orders)); // if we have such a name
$orderby = $orders[$key]; // If not, first one will be set automatically. 
$query = "SELECT * FROM `table` ORDER BY $orderby"; // Value is safe

Süreci kolaylaştırmak için tüm işi tek bir satırda yapan bir beyaz liste yardımcı işlevi yazdım :

$orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name");
$query  = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe

Tanımlayıcıları korumanın başka bir yolu var - kaçıyor, ancak daha güçlü ve açık bir yaklaşım olarak beyaz listeye bağlı kalmayı tercih ediyorum. Ancak, alıntılanmış bir tanımlayıcıya sahip olduğunuz sürece, alıntı karakterini güvenli hale getirmek için kaçabilirsiniz. Örneğin, varsayılan olarak mysql için tırnak işareti kaçmak için çift ​​karakter gerekir . Diğer diğer DBMS için kaçış kuralları farklı olacaktır.

Yine de, orada SQL'ın sözdizimi anahtar kelimeler ile ilgili bir sorun (örneğin gibidir AND, DESCve bu tür), ancak beyaz listeleme bu durumda sadece bir yaklaşım gibi görünüyor.

Bu nedenle, genel bir öneri şu şekilde ifade edilebilir:

  • Bir SQL veri değişmezini temsil eden herhangi bir değişken (veya basitçe söylemek gerekirse - bir SQL dizesi veya bir sayı) hazırlanmış bir ifade ile eklenmelidir. İstisna yok.
  • SQL anahtar sözcüğü, tablo veya alan adı veya işleç gibi diğer sorgu bölümlerinin beyaz listeden filtrelenmesi gerekir.

Güncelleme

SQL enjeksiyon korumasına ilişkin en iyi uygulamalar konusunda genel bir anlaşma olmasına rağmen, yine de birçok kötü uygulama var. Ve bazıları çok derinden PHP kullanıcılarının zihninde kök salmıştı. Örneğin, bu sayfada (çoğu ziyaretçi için görünmez olsa da) 80'den fazla silinmiş cevap var - hepsi kötü kalite veya kötü ve güncel olmayan uygulamaları teşvik etme nedeniyle topluluk tarafından kaldırıldı. Daha da kötüsü, bazı kötü cevaplar silinmez, daha ziyade başarılı olur.

Örneğin, (1) orada (2) vardır hala (3) birçok (4) cevapları (5) de dahil olmak üzere, ikinci en upvoted cevap güvensiz olduğu kanıtlanmış eski bir yaklaşım - Size kaçan manuel dize düşündüren.

Ya da dize biçimlendirmesinin sadece başka bir yöntemini öneren ve hatta bunu en üst düzey her derde deva olarak övünen biraz daha iyi bir cevap var . Tabii ki değil. Bu yöntem normal dize biçimlendirmesinden daha iyi değildir, ancak tüm dezavantajlarını korur: sadece dizeler için geçerlidir ve diğer manuel biçimlendirmeler gibi, temelde isteğe bağlı, zorunlu olmayan ölçüdür, her türlü insan hatasına eğilimlidir.

Tüm bunların, "kaçan" ve SQL enjeksiyonlarından korunma arasındaki eşitliği bildiren OWASP veya PHP kılavuzu gibi otoriteler tarafından desteklenen çok eski bir batıl inanç nedeniyle düşünüyorum .

PHP kılavuzunun çağlar boyunca söylediklerine bakılmaksızın, *_escape_stringhiçbir şekilde verileri güvenli hale getirmez ve asla tasarlanmamıştır. Dize dışında herhangi bir SQL parçası için işe yaramaz olmasının yanı sıra, manuel çıkış kaçınılmazdır, çünkü manuelin tersi manueldir.

Ve OWASP, daha da saçma olan kullanıcı girişine vurgu yaparak daha da kötüleştirir : enjeksiyon koruması bağlamında böyle bir kelime olmamalıdır. Her değişken potansiyel olarak tehlikelidir - kaynak ne olursa olsun! Ya da başka bir deyişle - kaynak ne olursa olsun, her değişkenin bir sorguya yerleştirilmek üzere düzgün biçimlendirilmesi gerekir. Önemli olan yer. Bir geliştiricinin koyunları keçilerden ayırmaya başladığı anda (belirli bir değişkenin "güvenli" olup olmadığını düşünerek) felakete doğru ilk adımını atar. İfadelerin bile, giriş noktasında kaçan, çok sihirli tırnak özelliklerine benzeyen, zaten hor görülen, kullanımdan kaldırılan ve kaldırılanları önerdiğinden bahsetmiyoruz.

Yani, ne olursa olsun farklı olarak hazırlanmış deyimleri "kaçan" olduğunu gerçekten SQL enjeksiyonu gelen koruduğunu ölçüsü (varsa).


848

Parametreli SQL sorguları çalıştırmak için PDO (PHP Veri Nesneleri) kullanmanızı tavsiye ederim .

Bu sadece SQL enjeksiyonuna karşı koruma sağlamakla kalmaz, aynı zamanda sorguları da hızlandırır.

Ve yerine PDO kullanarak mysql_, mysqli_ve pgsql_fonksiyonları, başvurunuzun daha Düğmeyi veritabanı sağlayıcıları zorunda ender bulunmasından, ritabanından elde biraz olun.


619

Kullanım PDOve hazırlanmış sorgular.

($conn bir PDOnesnedir)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();

549

Gördüğünüz gibi, insanlar hazırlanmış ifadeleri en fazla kullanmanızı önerir. Yanlış değil, ancak sorgunuz işlem başına yalnızca bir kez yürütüldüğünde , hafif bir performans cezası olacaktır.

Bu sorunla karşı karşıyaydım, ama bence çözdüm çok karmaşık bir şekilde çözdüm - bilgisayar korsanlarının tırnak kullanmaktan kaçınmak için kullandıkları yol. Ben bunu taklit hazırlanmış ifadelerle birlikte kullandım. Her türlü olası SQL enjeksiyon saldırısını önlemek için kullanıyorum .

Benim yaklaşımım:

  • Girdinin tamsayı olmasını bekliyorsanız, gerçekten tamsayı olduğundan emin olun . PHP gibi değişken tip bir dilde bu çok önemlidir. Örneğin bu çok basit ama güçlü çözümü kullanabilirsiniz:sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • Eğer tamsayı hex başka bir şey bekliyorsanız . Onaltıldığında, tüm girdilerden mükemmel bir şekilde kaçacaksınız. C / C ++ 'da bir fonksiyon varmysql_hex_string() PHP'de kullanabileceğinizbin2hex() .

    Kaçan dizenin orijinal uzunluğunun 2x boyutuna sahip olacağından endişelenmeyin, çünkü kullansanız bile mysql_real_escape_string PHP aynı kapasiteyi tahsis etmek zorundadır ((2*input_length)+1).

  • Bu hex yöntemi genellikle ikili verileri aktarırken kullanılır, ancak SQL enjeksiyon saldırılarını önlemek için tüm verilerde neden kullanılmaması için hiçbir neden göremiyorum. Verilerin başına0xUNHEX yerine MySQL işlevini veya MySQL işlevini kullanmanız gerektiğini unutmayın.

Örneğin, sorgu:

SELECT password FROM users WHERE name = 'root'

Olacak:

SELECT password FROM users WHERE name = 0x726f6f74

veya

SELECT password FROM users WHERE name = UNHEX('726f6f74')

Hex mükemmel bir kaçış. Enjeksiyon yolu yok.

UNHEX işlevi ve 0x öneki arasındaki fark

Yorumlarda bazı tartışmalar oldu, bu yüzden sonunda bunu netleştirmek istiyorum. Bu iki yaklaşım çok benzer, ancak bazı yönlerden biraz farklıdır:

** 0x ** öneki yalnızca char, varchar, text, block, binary, vb . Gibi veri sütunları için kullanılabilir .
Ayrıca, boş bir dize eklemek üzereyseniz kullanımı biraz karmaşıktır. Tamamen yerine ''koymanız gerekir , aksi takdirde bir hata alırsınız.

UNHEX () çalışır herhangi sütun ; boş dize hakkında endişelenmenize gerek yoktur.


Onaltılık yöntemler genellikle saldırı olarak kullanılır

Bu onaltılık yöntemin genellikle tamsayıların dizeler gibi olduğu ve sadece mysql_real_escape_string . Sonra tırnak kullanımından kaçınabilirsiniz.

Örneğin, sadece böyle bir şey yaparsanız:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

bir saldırı sizi çok kolay enjekte edebilir . Komut dosyanızdan döndürülen aşağıdaki enjekte edilmiş kodu düşünün:

SELECT ... NEREDE id = -1 birleşim hepsi information_schema.tables dan tablo_adı seçer

ve şimdi sadece tablo yapısını çıkarın:

SELECT ... WHERE id = -1 birleşim tümü, information_schema.column'dan sütun_adı seçer ; burada table_name = 0x61727469636c65

Ve sonra sadece istediklerini seçin. Güzel değil mi?

Ancak, enjekte edilebilir bir sitenin kodlayıcısı onu onaltılıyorsa, sorgu mümkün olmayacaktır çünkü sorgu şu şekilde görünecektir: SELECT ... WHERE id = UNHEX('2d312075...3635')


6
@SumitGupta Evet, yaptın. MySQL ile birleştirilmez +ancak ile birleştirilir CONCAT. Ve performans için: Performansı etkilediğini sanmıyorum çünkü mysql verileri ayrıştırmak zorunda ve orijin dize mi yoksa hex mi olursa olsun önemli değil
Zaffy

11
@YourCommonSense Kavramı anlamıyorsunuz ... Eğer mysql'de dizeye sahip olmak istiyorsanız, bu şekilde alıntı yaparsınız 'root'ya da 0x726f6f74bir sayı istiyorsanız ve dize olarak gönderirseniz , onaltılı olarak yazabilirsiniz, muhtemelen CHAR değil '42' yazacaksınız (42 ) ... '42' onaltılık olacağını 0x3432değil0x42
Zaffy

7
@YourCommonSense Söyleyecek bir şeyim yok ... sadece lol ... hala sayısal alanlarda hex denemek istiyorsanız, ikinci yoruma bakın. Bahse girerim işe yarayacak.
Zaffy

2
Cevap, tamsayı değerlerle bu şekilde çalışmayacağını açıkça gösteriyor, bin2hex'in geçirilen değeri bir dizeye dönüştürmesi (ve böylece bin2hex (0) 0x30 değil 0x30) - muhtemelen sizi şaşırtan kısım . Bunu takip ederseniz, mükemmel çalışır (en azından sitemde, debian makinelerde 4 farklı mysql sürümü ile test edilmiştir, 5.1.x ila 5.6.x). Sonuçta, onaltılık değer değil, sadece temsil yoludur;)
griffin

5
@ HalaCommonSense anlayamadınız mı? Eğer dize boşsa bir hata ile bitecek çünkü 0x ve concat kullanamazsınız. Sorgunuza basit bir alternatif istiyorsanız bunu deneyinSELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ')
Zaffy

499

Kullanımdan Kaldırıldı Uyarı: Bu yanıtın örnek kodu (sorunun örnek kodu gibi), MySQLPHP 5.5.0'da kaldırılan ve tamamen PHP 7.0.0'da kaldırılan PHP'nin uzantısını kullanır .

Güvenlik Uyarısı : Bu yanıt en iyi güvenlik uygulamalarıyla uyumlu değildir. Kaçış SQL enjeksiyonunu önlemek için yetersizdir , bunun yerine hazırlanmış ifadeleri kullanın. Aşağıda özetlenen stratejiyi kullanmak kendi sorumluluğunuzdadır. (Ayrıca, mysql_real_escape_string()PHP 7'de kaldırıldı.)

ÖNEMLİ

SQL Enjeksiyonunu önlemenin en iyi yolu , kabul edilen cevabın gösterdiği gibi , kaçmak yerine Hazırlanmış İfadeler kullanmaktır .

Aura.Sql ve EasyDB gibi geliştiricilerin hazırlanmış ifadeleri daha kolay kullanmasına izin veren kütüphaneler vardır . Hazırlanan ifadelerin SQL enjeksiyonunu durdurmada neden daha iyi olduğu hakkında daha fazla bilgi için, bu mysql_real_escape_string()bypass'a ve WordPress'teki son zamanlarda düzeltilmiş Unicode SQL Injection güvenlik açıklarına bakın .

Enjeksiyonun önlenmesi - mysql_real_escape_string ()

PHP bu saldırıları önlemek için özel olarak yapılmış bir işleve sahiptir. Tek yapmanız gereken bir işlevin ağzını kullanmak mysql_real_escape_string.

mysql_real_escape_stringbir MySQL sorgusunda kullanılacak bir dizeyi alır ve güvenli bir şekilde tüm SQL enjeksiyon girişimleriyle aynı dizeyi döndürür. Temel olarak, kullanıcının girebileceği zahmetli alıntıların (') yerine MySQL güvenli bir ikame, kaçan bir teklif \' konacaktır.

NOT: Bu işlevi kullanmak için veritabanına bağlı olmalısınız!

// MySQL'e bağlanın

$name_bad = "' OR 1'"; 

$name_bad = mysql_real_escape_string($name_bad);

$query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";


$name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; 

$name_evil = mysql_real_escape_string($name_evil);

$query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
echo "Escaped Evil Injection: <br />" . $query_evil;

Daha fazla bilgiyi MySQL - SQL Enjeksiyon Önleme bölümünde bulabilirsiniz .


30
Bu, eski mysql uzantısı ile yapabileceğiniz en iyisidir. Yeni kod için, mysqli veya PDO'ya geçmeniz önerilir.
Álvaro González

7
Bu 'bu saldırıları önlemek için özel olarak yapılmış bir işlev' ile aynı fikirde değilim. Bu mysql_real_escape_stringamaç her giriş veri dizesi için doğru SQL sorgusu oluşturmak için izin olduğunu düşünüyorum . Önleme sql-enjeksiyon bu fonksiyonun yan etkisidir.
sectus

4
doğru giriş veri dizelerini yazmak için işlevleri kullanmazsınız. Sadece kaçmaya ihtiyaç duymayan veya kaçmış olan doğruları yazıyorsunuz. mysql_real_escape_string (), aklınızda bahsettiğiniz amaç için tasarlanmış olabilir, ancak tek değeri enjeksiyonu önlemektir.
Nazca

17
UYARI! mysql_real_escape_string() yanılmaz değil .
eggyal

9
mysql_real_escape_stringartık kullanımdan kaldırılmıştır, bu yüzden artık geçerli bir seçenek değildir. Gelecekte PHP'den kaldırılacaktır. PHP veya MySQL kullanıcılarının önerdiklerine geçmek en iyisidir.
jww

462

Bunun gibi temel bir şey yapabilirsiniz:

$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Bu her sorunu çözmez, ama çok iyi bir basamak taşıdır. Değişkenin varlığını, biçimini (sayılar, harfler vb.) Kontrol etmek gibi belirgin öğeleri dışarıda bıraktım.


28
Örneğini denedim ve bu benim için iyi çalışıyor. "Bu her sorunu çözmeyecek" i temizleyebilir misin
Chinook

14
Eğer ipi teklif etmezsen, hala enjekte edilebilir. $q = "SELECT col FROM tbl WHERE x = $safe_var";Örneğin ele alalım. Ayar $safe_variçin 1 UNION SELECT password FROM usersçünkü tırnak eksikliği bu durumda işlerin. Bu kullanarak sorguya dizeleri enjekte etmek de mümkündür CONCATve CHR.
Polinom

4
@Polynomial Tamamen doğru, ama bunu sadece yanlış kullanım olarak görüyorum. Doğru kullandığınız sürece, kesinlikle işe yarayacaktır.
glglgl

22
UYARI! mysql_real_escape_string() yanılmaz değil .
eggyal

8
mysql_real_escape_stringartık kullanımdan kaldırılmıştır, bu yüzden artık geçerli bir seçenek değildir. Gelecekte PHP'den kaldırılacaktır. PHP veya MySQL kullanıcılarının önerdiklerine geçmek en iyisidir.
jww

380

Sonunda ne kullanırsanız yapın, girdilerinizin zaten magic_quotesbaşka bir iyi anlamdaki çöp tarafından yönetilmediğinden emin olun ve gerekirse bunu çalıştırın stripslashesveya sterilize etmek için ne olursa olsun.


11
Aslında; magic_quotes açıkken çalıştırmak zayıf uygulamayı teşvik eder. Bununla birlikte, bazen ortamı her zaman bu seviyeye kadar kontrol edemezsiniz - sunucuyu yönetmek için erişiminiz yoktur veya uygulamanızın (titreme) bu yapılandırmaya bağlı uygulamalarla bir arada bulunması gerekir. Bu nedenlerle, taşınabilir uygulamalar yazmak iyidir - dağıtım ortamını kontrol ederseniz, örneğin şirket içi bir uygulama olduğu veya yalnızca belirli ortamınızda kullanılacağı için çaba harcanır.
Rob

24
PHP 5.4'ten itibaren, 'sihirli tırnak' olarak bilinen iğrençlik öldü . Ve kötü çöplere iyi kurtuluş.
BryanH

363

Kullanımdan Kaldırıldı Uyarı: Bu yanıtın örnek kodu (sorunun örnek kodu gibi) PHP kullanıyorMySQLPHP 5.5.0'da kaldırılan ve tamamen PHP 7.0.0'da kaldırılan PHP'nin uzantısını .

Güvenlik Uyarısı : Bu yanıt en iyi güvenlik uygulamalarıyla uyumlu değildir. Kaçış SQL enjeksiyonunu önlemek için yetersizdir , bunun yerine hazırlanmış ifadeleri kullanın. Aşağıda özetlenen stratejiyi kullanmak kendi sorumluluğunuzdadır. (Ayrıca, mysql_real_escape_string()PHP 7'de kaldırıldı.)

Parametreli sorgu VE giriş doğrulaması gidilecek yoldur. Her ne kadar SQL enjeksiyonunun gerçekleşebileceği birçok senaryo varmysql_real_escape_string() vardır.

Bu örnekler SQL enjeksiyonuna karşı savunmasızdır:

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

veya

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

Her iki durumda da, 'kapsüllemeyi korumak için kullanamazsınız .

Kaynak : Beklenmedik SQL Enjeksiyonu (Kaçış Yetmediğinde)


2
Kullanıcı girişinin uzunluk, tür ve sözdizimi için bir dizi tanımlı kurala ve ayrıca iş kurallarına karşı doğrulandığı bir giriş doğrulama tekniği kullanırsanız SQL enjeksiyonunu önleyebilirsiniz.
Josip Ivic

312

Bence, PHP uygulamanızda (veya bu konuda herhangi bir web uygulamasında) SQL enjeksiyonunu genel olarak önlemenin en iyi yolu, uygulamanızın mimarisini düşünmektir. SQL enjeksiyonuna karşı korumanın tek yolu, veritabanıyla her konuştuğunuzda Doğru Şey'i yapan özel bir yöntem veya işlev kullanmayı hatırlamaksa, yanlış yapıyorsunuz demektir. Bu şekilde, sorgunuzu kodunuzun bir noktasında doğru bir şekilde biçimlendirmeyi unutana kadar sadece zaman meselesi.

MVC desenini ve CakePHP veya CodeIgniter gibi bir çerçeveyi benimsemek muhtemelen doğru yoldur: Güvenli veritabanı sorguları oluşturma gibi ortak görevler bu çerçevelerde çözülmüş ve merkezi olarak uygulanmıştır. Web uygulamanızı mantıklı bir şekilde düzenlemenize yardımcı olur ve nesneleri tek tek SQL sorguları oluşturmaktan çok, yükleme ve kaydetme hakkında daha fazla düşünmenizi sağlar.


5
İlk paragrafınızın önemli olduğunu düşünüyorum. Anlamak anahtardır. Ayrıca, herkes bir şirket için çalışmıyor. İnsanların büyük bir kısmı için çerçeveler aslında anlayış fikrine aykırıdır . Bir son tarihte çalışırken temel ilkelerle yakınlaşmak değerli olmayabilir, ancak oradaki kendileri yapmak ellerini kirletmekten zevk alır. Çerçeve geliştiricileri, herkesin eğilmemesi ve asla hata yapmadıklarını varsayması için ayrıcalıklı değildir. Karar verme gücü hala önemlidir. Çerçevemin gelecekte başka bir planın yerini almayacağını kim söyleyebilir?
Anthony Rutledge

@AnthonyRutledge Kesinlikle haklısın. Neler olup bittiğini anlamak çok önemlidir . Bununla birlikte, gerçek ve denenmiş ve aktif olarak kullanılan ve geliştirilen bir çerçevenin birçok sorunla karşılaşma ve çözme ve zaten birçok güvenlik deliği yaması olasılığı oldukça yüksektir. Kod kalitesi hakkında fikir edinmek için kaynağa bakmak iyi bir fikirdir. Test edilmemiş bir karışıklık varsa, muhtemelen güvenli değildir.
Johannes Fahrenkrug

3
Buraya. Buraya. Güzel nokta. Bununla birlikte, birçok insanın bir MVC sistemi öğrenmeyi ve öğrenmeyi öğrenebileceğini kabul edersiniz, ancak herkes bunu elle üretemez (denetleyiciler ve sunucu). Kişi bu nokta ile çok ileri gidebilir. Fıstık ezmesi cevizli kurabiyeleri ısıtmadan önce mikrodalga arkadaşımı anlamam gerekir mi? ;-)
Anthony Rutledge

3
@AnthonyRutledge Kabul ediyorum! Kullanım senaryosunun da bir fark yarattığını düşünüyorum: Kişisel ana sayfam için bir fotoğraf galerisi mi oluşturuyorum yoksa bir çevrimiçi bankacılık web uygulaması mı oluşturuyorum? İkinci durumda, güvenliğin ayrıntılarını ve kullandığım bir çerçevenin bunlara nasıl hitap ettiğini anlamak çok önemlidir.
Johannes Fahrenkrug

3
Ah, güvenlik istisnası kendiniz sonuç veriyor. Bak, hepsini riske atmaya ve parasız olmaya eğilimliyim. :-) Dalga geçmek. Yeterli zamanla, insanlar oldukça darn güvenli bir uygulama yapmayı öğrenebilirler. Çok fazla insan acele ediyor. Ellerini yukarı kaldırırlar ve çerçevelerin daha güvenli olduğunu varsayarlar . Sonuçta, bir şeyleri test etmek ve çözmek için yeterli zamanları yok. Dahası, güvenlik özel çalışma gerektiren bir alandır. Programcıların algoritmaları ve tasarım modellerini anlayarak derinlemesine bildikleri bir şey değildir.
Anthony Rutledge

298

Güvenlik açısından saklı yordamları ( MySQL 5.0'dan beri saklı yordamları desteklemiştir ) tercih ediyorum - avantajları -

  1. Çoğu veritabanı ( MySQL dahil ), kullanıcı erişiminin saklı yordamları yürütmekle kısıtlanmasını sağlar. Ayrıntılı güvenlik erişim denetimi, ayrıcalık saldırılarının artmasını önlemek için kullanışlıdır. Bu, güvenliği ihlal edilmiş uygulamaların SQL'i doğrudan veritabanına çalıştırabilmesini engeller.
  2. Ham SQL sorgusunu uygulamadan soyutlar, böylece veritabanı yapısı hakkında daha az bilgi uygulama için kullanılabilir. Bu, insanların veritabanının altında yatan yapıyı anlamasını ve uygun saldırılar tasarlamasını zorlaştırır.
  3. Yalnızca parametreleri kabul ederler, bu nedenle parametreli sorguların avantajları vardır. Tabii ki - IMO, özellikle de saklı yordamın içinde dinamik SQL kullanıyorsanız, girişinizi dezenfekte etmeniz gerekiyor.

Dezavantajları -

  1. (Saklı prosedürler) bakımı zordur ve çok hızlı çoğalma eğilimi gösterirler. Bu onları yönetmeyi bir sorun haline getirir.
  2. Dinamik sorgular için çok uygun değildirler - dinamik kodu parametre olarak kabul etmek için oluşturulmuşlarsa, birçok avantajı reddedilir.

297

SQL enjeksiyonlarını ve diğer SQL saldırılarını önlemenin birçok yolu vardır. İnternette kolayca bulabilirsiniz (Google Arama). Elbette PDO iyi çözümlerden biridir. Ama size SQL enjeksiyonundan bazı iyi bağlantılar önleme önermek istiyorum.

SQL enjeksiyonu nedir ve nasıl önlenir

SQL enjeksiyonu için PHP kılavuzu

SQL'de SQL enjeksiyonu ve önlenmesi için Microsoft açıklaması

Ve diğerleri MySQL ve PHP ile SQL enjeksiyonunu önlemek gibi .

Şimdi, sorgunuzun SQL enjeksiyonunu neden engellemeniz gerekiyor?

Size bildirmek istiyorum: Neden aşağıda kısa bir örnek ile SQL enjeksiyonunu önlemek için çalışıyoruz:

Giriş kimlik doğrulama eşleşmesi için sorgu:

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";

Şimdi, birisi (bilgisayar korsanı)

$_POST['email']= admin@emali.com' OR '1=1

ve şifre bir şey ....

Sorgu sisteme yalnızca şu değere kadar ayrıştırılacaktır:

$query="select * from users where email='admin@emali.com' OR '1=1';

Diğer kısım atılacaktır. Peki, ne olacak? Yetkili olmayan bir kullanıcı (bilgisayar korsanı) şifresi olmadan yönetici olarak oturum açabilir. Artık yönetici / e-posta kişinin yapabileceği her şeyi yapabilir. SQL enjeksiyonunun engellenmemesi çok tehlikelidir.


267

Birisi PHP ve MySQL veya başka bir dataBase sunucusu kullanmak istiyorsa düşünüyorum:

  1. PDO (PHP Veri Nesneleri) öğrenmeyi düşünün - birden çok veritabanına tek tip bir erişim yöntemi sağlayan bir veritabanı erişim katmanıdır.
  2. MySQLi öğrenmeyi düşünün
  3. Strip_tags , mysql_real_escape_string gibi yerel PHP işlevlerini kullanın veya değişken sayısal ise, sadece (int)$foo. PHP'deki değişkenlerin türü hakkında daha fazla bilgiyi buradan edinebilirsiniz . PDO veya MySQLi gibi kütüphaneler kullanıyorsanız, daima PDO :: quote () ve mysqli_real_escape_string () kullanın .

Kütüphaneler için örnekler:

---- PDO

----- Yer tutucu yok - SQL enjeksiyonu için olgun! O kötü

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

----- İsimsiz yer tutucular

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

----- Adlandırılmış yer tutucular

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

--- MySQLi

$request = $mysqliConnection->prepare('
       SELECT * FROM trainers
       WHERE name = ?
       AND email = ?
       AND last_login > ?');

    $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
    $query->execute();

Not :

PDO bu savaşı kolaylıkla kazanıyor. On iki farklı veritabanı sürücüsü ve adlandırılmış parametre desteği ile küçük performans kaybını göz ardı edebilir ve API'sine alışabiliriz. Güvenlik açısından, geliştirici bunları kullanılması gerektiği şekilde kullandığı sürece her ikisi de güvenlidir

Ancak hem PDO hem de MySQLi oldukça hızlı olmasına rağmen, MySQLi, kıyaslamalarda önemli ölçüde daha hızlı performans gösterir - hazırlanmamış ifadeler için ~% 2.5 ve hazırlanmış olanlar için ~% 6.5.

Ve lütfen veritabanınızdaki her sorguyu test edin - enjeksiyonu önlemenin daha iyi bir yoludur.


bu mysqli yanlış. İlk parametre veri türlerini ifade eder.
mickmackusa

257

Mümkünse, parametre türlerinizi yayınlayın. Ancak yalnızca int, bool ve float gibi basit türler üzerinde çalışıyor.

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

3
Bu, hazırlanmış bir ifade yerine "çıkış değeri" kullanacağım birkaç durumdan biri. Ve tamsayı tipi dönüşüm son derece verimlidir.
HoldOffHunger

233

Redis veya Memcached gibi önbellek motorlarından yararlanmak istiyorsanız , belki DALMP bir seçenek olabilir. Saf MySQLi kullanır . Şunu kontrol edin: PHP kullanarak MySQL için DALMP Veritabanı Soyutlama Katmanı.

Ayrıca, dinamik sorgular oluşturabilmeniz ve sonunda tam olarak hazırlanmış bir ifadeler sorgusu yapabilmeniz için, sorgunuzu hazırlamadan önce argümanlarınızı 'hazırlayabilirsiniz'. PHP kullanarak MySQL için DALMP Veritabanı Soyutlama Katmanı.


224

PDO'nun nasıl kullanılacağından emin olmayanlar için ( mysql_işlevlerden geliyor ), tek bir dosya olan çok, çok basit bir PDO sarıcısı yaptım . Uygulamaların yapılması gereken tüm ortak şeyleri yapmanın ne kadar kolay olduğunu göstermek için var. PostgreSQL, MySQL ve SQLite ile çalışır.

Temel olarak, okumak manuel okurken basit saklamak ve formatta değerleri almak için yapmak gerçek hayatta kullanımına PDO işlevlerini koymak nasıl görmek sen istiyorum.

Tek bir sütun istiyorum

$count = DB::column('SELECT COUNT(*) FROM `user`);

Bir dizi (anahtar => değer) sonuçları (yani bir selectbox yapmak için) istiyorum

$pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

Tek satır sonucu istiyorum

$user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

Bir dizi sonuç istiyorum

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));

222

Bu PHP işlevini kullanma mysql_escape_string() hızlı bir şekilde iyi bir önleme elde edebilirsiniz.

Örneğin:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'

mysql_escape_string - mysql_query içinde kullanmak için bir dizeden kaçar

Daha fazla önleme için, sonunda ekleyebilirsiniz ...

wHERE 1=1   or  LIMIT 1

Sonunda elde edersiniz:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1

220

SQL deyimlerinde özel karakterlerden kaçmak için birkaç kılavuz.

MySQL kullanmayın . Bu uzantı kullanımdan kaldırıldı. Kullanım MySQLi veya PDO yerine.

MySQLi

Bir karakter dizisindeki özel karakterlerden elle kaçmak için mysqli_real_escape_string işlevini kullanabilirsiniz . Mysqli_set_charset ile doğru karakter seti ayarlanmadığı sürece işlev düzgün çalışmaz .

Misal:

$mysqli = new mysqli('host', 'user', 'password', 'database');
$mysqli->set_charset('charset');

$string = $mysqli->real_escape_string($string);
$mysqli->query("INSERT INTO table (column) VALUES ('$string')");

Hazırlanan ifadelerle değerlerin otomatik olarak kaçması için mysqli_prepare ve mysqli_stmt_bind_param kullanın burada uygun bağlama değişkenleri için türlerin uygun bir dönüşüm için sağlanması gerekir:

Misal:

$stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)");

$stmt->bind_param("is", $integer, $string);

$stmt->execute();

Hazırlanmış ifadeler kullanırsanız veya mysqli_real_escape_string çalıştığınız girdi verilerinin türünü her zaman bilmek zorundasınız.

Dolayısıyla, hazırlanmış bir ifade kullanırsanız, mysqli_stmt_bind_paramişlev için değişken türlerini belirtmeniz gerekir .

Ve kullanımı mysqli_real_escape_string, adından da anlaşılacağı gibi, bir dizede özel karakterlerden kaçmak içindir, bu nedenle tamsayıları güvenli hale getirmez. Bu işlevin amacı, SQL ifadelerindeki dizelerin kırılmasını ve neden olabileceği veritabanının zarar görmesini önlemektir. mysqli_real_escape_stringözellikle birlikte kullanıldığında düzgün kullanıldığında yararlı bir işlevdir sprintf.

Misal:

$string = "x' OR name LIKE '%John%";
$integer = '5 OR id != 0';

$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5

$integer = '99999999999999999999';
$query = sprintf("SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647

3
Soru çok genel. Yukarıdaki bazı büyük cevaplar, ancak çoğu hazırlanmış ifadeler önerir. MySQLi async hazırlanmış ifadeleri desteklemediğinden, sprintf bu durum için harika bir seçenek gibi görünüyor.
Dustin Graham

183

Bu soruna basit bir alternatif, veritabanının kendisinde uygun izinler verilerek çözülebilir. Örneğin: bir MySQL veritabanı kullanıyorsanız, terminal veya sağlanan kullanıcı arabirimi aracılığıyla veritabanına girin ve şu komutu uygulayın:

 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

Bu, kullanıcının yalnızca belirtilen sorguyla sınırlı kalmasını kısıtlar. Silme iznini kaldırın, böylece veriler PHP sayfasından tetiklenen sorgudan asla silinmez. Yapılacak ikinci şey, MySQL'in izinleri ve güncellemeleri yenilemesi için ayrıcalıkları temizlemektir.

FLUSH PRIVILEGES; 

sifon hakkında daha fazla bilgi .

Kullanıcının geçerli ayrıcalıklarını görmek için aşağıdaki sorguyu tetikler.

select * from mysql.user where User='username';

GRANT hakkında daha fazla bilgi edinin .


25
Bu cevap esasen yanlıştır , çünkü enjeksiyonun önlenmesini önlemeye değil, sadece sonuçları yumuşatmaya çalışır. Boşuna.
Ortak

1
Doğru, bir çözüm sağlamaz, ancak şeylerden kaçınmak için önceden yapabileceğiniz şeydir.
Apurv Nheelar

1
@Apurv Amacım veritabanınızdan özel bilgileri okumaksa, o zaman DELETE iznine sahip olmak hiçbir şey ifade etmez.
Alex Holsgrove

1
@AlexHolsgrove: Sakin ol, sadece sonuçları yumuşatmak için iyi uygulamalar öneriyordum.
Apurv Nheelar

2
@Apurv "Sonuçları yumuşatmak" istemiyorsunuz, ona karşı korumak için mümkün olan her şeyi yapmak istiyorsunuz. Adil olmak gerekirse, doğru kullanıcı erişimini ayarlamak önemlidir, ancak OP'nin istediği şey gerçekten değildir.
Alex Holsgrove

177

Birçok yararlı cevap ile ilgili olarak, bu konuya biraz değer katmayı umuyorum.

SQL enjeksiyonu, kullanıcı girişleri (bir kullanıcı tarafından doldurulan ve daha sonra sorguların içinde kullanılan girişler) aracılığıyla yapılabilen bir saldırıdır. SQL enjeksiyon kalıpları doğru sorgu sözdizimidir, ancak bunu çağırabiliriz: kötü nedenlerden dolayı kötü sorgular ve üç güvenlik ilkesini (gizlilik) etkileyen gizli bilgileri (erişim kontrolünü atlayarak) almaya çalışan kötü bir kişi olabileceğini varsayıyoruz. , bütünlük ve kullanılabilirlik).

Şimdi, SQL enjeksiyon saldırıları gibi güvenlik tehditlerini önlemek, soruyu sormak (PHP kullanarak bir SQL enjeksiyon saldırısının nasıl önleneceği), daha gerçekçi olun, veri filtreleme veya giriş verilerini temizleme, içerideki kullanıcı giriş verilerini kullanırken böyledir PHP veya başka bir programlama dili kullanan böyle bir sorgu böyle değildir veya daha fazla insan tarafından hazırlanan ifade veya şu anda SQL enjeksiyon önleme özelliğini destekleyen diğer araçlar gibi modern teknolojiyi kullanması için önerildiği gibi, bu araçların artık mevcut olmadığını düşünüyor musunuz? Başvurunuzu nasıl güven altına alıyorsunuz?

SQL enjeksiyonuna karşı yaklaşımım: veri girişini veritabanına göndermeden önce (herhangi bir sorguda kullanmadan önce) temizlemek.

İçin veri filtreleme (güvenli olmayan verileri güvenli verilere dönüştürme)

PDO ve MySQLi'nin kullanılabilir olmadığını düşünün . Başvurunuzu nasıl güvence altına alabilirsiniz? Beni onları kullanmaya zorlar mısın? PHP dışındaki diğer diller ne olacak? Sadece belirli bir dil için değil, daha geniş sınır için kullanılabileceği için genel fikirler sunmayı tercih ediyorum.

  1. SQL kullanıcısı (kullanıcı ayrıcalığını sınırlayan): en yaygın SQL işlemleri (SELECT, UPDATE, INSERT) 'dir, o zaman UPDATE'e gerek duymayan bir kullanıcıya neden ayrıcalık verin? Örneğin, giriş ve arama sayfaları yalnızca SELECT kullanıyor, o zaman bu sayfalarda neden yüksek ayrıcalıklara sahip DB kullanıcıları kullanıyorsunuz?

KURAL: tüm ayrıcalıklar için bir veritabanı kullanıcısı oluşturmayın. Tüm SQL işlemleri için, kolay kullanım için düzeninizi (deluser, selectuser, updateuser) gibi kullanıcı adları olarak oluşturabilirsiniz.

En az ayrıcalık ilkesine bakın .

  1. Veri filtreleme: herhangi bir sorgu kullanıcı girişi oluşturmadan önce, doğrulanmalı ve filtrelenmelidir. Programcılar için, her kullanıcı-girdi değişkeni için bazı özellikler tanımlamak önemlidir: veri türü, veri deseni ve veri uzunluğu . (X ve y) arasındaki bir sayı olan bir alan, tam kural kullanılarak tam olarak doğrulanmalıdır ve dize (metin) olan bir alan için: desen söz konusudur, örneğin, bir kullanıcı adı yalnızca bazı karakterler içermelidir. deyin [a-zA-Z0-9_-.]. Uzunluk (x ve n) arasında değişir ve burada x ve n (tamsayılar, x <= n). Kural: tam filtreler ve doğrulama kuralları oluşturmak benim için en iyi uygulamalardır.

  2. Diğer araçları kullanın: Burada, hazır bir ifade (parametreli sorgu) ve saklı yordamlar konusunda da aynı fikirdeyim. Buradaki dezavantajlar, bu yolların çoğu kullanıcı için mevcut olmayan gelişmiş becerileri gerektirmesidir. Buradaki temel fikir, SQL sorgusu ile içinde kullanılan veriler arasında ayrım yapmaktır. Her iki yaklaşım da güvensiz verilerle bile kullanılabilir, çünkü buradaki kullanıcı giriş verileri orijinal sorguya (any veya x = x) gibi hiçbir şey eklemez.

Daha fazla bilgi için lütfen OWASP SQL Enjeksiyon Önleme Hile Sayfası'nı okuyun .

Şimdi, ileri düzey bir kullanıcıysanız, bu savunmayı istediğiniz gibi kullanmaya başlayın, ancak yeni başlayanlar için, depolanmış bir prosedürü hızlı bir şekilde uygulayamazlar ve ifadeyi hazırlayamazlarsa, giriş verilerini olabildiğince filtrelemek daha iyidir.

Son olarak, bir kullanıcının kullanıcı adını girmek yerine bu metni aşağıda gönderdiğini düşünelim:

[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

Bu girdi, önceden hazırlanmış herhangi bir ifade ve saklı yordam olmadan erken kontrol edilebilir, ancak kullanıcı tarafında veri filtreleme ve doğrulama işleminden sonra bunları kullanmak üzere güvenli tarafta olmak gerekir.

Son nokta, daha fazla çaba ve karmaşıklık gerektiren beklenmedik davranışları tespit etmektir; normal web uygulamaları için önerilmez.

Yukarıdaki kullanıcı girişinde beklenmeyen davranış SELECT, UNION, IF, SUBSTRING, BENCHMARK, SHA ve root'tur. Bu kelimeler algılandıktan sonra, girdiden kaçınabilirsiniz.

GÜNCELLEME 1:

Bir kullanıcı bu yazının işe yaramaz olduğunu söyledi, tamam! İşte OWASP.ORG sağladığı :

Birincil savunmalar:

Seçenek # 1: Hazırlanan Deyimlerin Kullanılması (Parametrelenmiş Sorgular)
Seçenek # 2: Saklı Yordamların Kullanılması
Seçenek # 3: Kullanıcı Tarafından Verilen Tüm Girdilerden Kaçınma

Ek savunmalar:

Ayrıca Zorla: En Küçük Ayrıcalık
Ayrıca Gerçekleştir: Beyaz Liste Giriş Doğrulaması

Bildiğiniz gibi, bir makaleyi talep etmek geçerli bir argümanla, en az bir referansla desteklenmelidir! Aksi takdirde, bir saldırı ve kötü bir iddia olarak kabul edilir!

Güncelleme 2:

PHP kılavuzundan, PHP: Hazırlanan Bildirimler - Manual :

Kaçış ve SQL enjeksiyonu

Bağlı değişkenler sunucu tarafından otomatik olarak kaçacaktır. Sunucu, kaçan değerlerini uygun yerlerde yürütmeden önce ifade şablonuna ekler. Uygun bir dönüşüm oluşturmak için, bağlı değişken türü için sunucuya bir ipucu verilmelidir. Daha fazla bilgi için mysqli_stmt_bind_param () işlevine bakın.

Sunucudaki değerlerin otomatik olarak kaçması bazen SQL enjeksiyonunu önlemek için bir güvenlik özelliği olarak kabul edilir. Giriş değerlerinden doğru bir şekilde kaçılırsa, hazırlıksız ifadelerle aynı güvenlik derecesi elde edilebilir.

Güncelleme 3:

Hazırlanan bir ifade kullanırken PDO ve MySQLi'nin MySQL sunucusuna sorguyu nasıl gönderdiğini bilmek için test senaryoları oluşturdum:

PDO:

$user = "''1''"; // Malicious keyword
$sql = 'SELECT * FROM awa_user WHERE userame =:username';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':username' => $user));

Sorgu Günlüğü:

    189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
    189 Quit

MySQLi:

$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
$stmt->bind_param("s", $user);
$user = "''1''";
$stmt->execute();

Sorgu Günlüğü:

    188 Prepare   SELECT * FROM awa_user WHERE username =?
    188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
    188 Quit

Hazırlanan bir ifadenin de verilerden kaçtığı açıktır, başka bir şey değildir.

Yukarıdaki açıklamada da belirtildiği gibi,

Sunucudaki değerlerin otomatik olarak kaçması bazen SQL enjeksiyonunu önlemek için bir güvenlik özelliği olarak kabul edilir. Giriş değerlerinden doğru bir şekilde kaçılırsa, hazırlıksız ifadelerle aynı güvenlik derecesi elde edilebilir

Bu nedenle, bu, intval()herhangi bir sorgu göndermeden önce tamsayı değerleri için veri doğrulamanın iyi bir fikir olduğunu kanıtlar . Ayrıca, sorguyu göndermeden önce kötü niyetli kullanıcı verilerinin engellenmesi doğru ve geçerli bir yaklaşımdır .

Daha fazla ayrıntı için lütfen bu soruya bakın: PDO, MySQL'e ham sorgu gönderirken, Mysqli hazırlanmış sorgu gönderirken her ikisi de aynı sonucu verir

Referanslar:

  1. SQL Enjeksiyon Hile Sayfası
  2. SQL Enjeksiyonu
  3. Bilgi Güvenliği
  4. Güvenlik İlkeleri
  5. Veri doğrulama

175

Güvenlik Uyarısı : Bu yanıt en iyi güvenlik uygulamalarıyla uyumlu değildir. Kaçış SQL enjeksiyonunu önlemek için yetersizdir , bunun yerine hazırlanmış ifadeleri kullanın. Aşağıda özetlenen stratejiyi kullanmak kendi sorumluluğunuzdadır. (Ayrıca, mysql_real_escape_string()PHP 7'de kaldırıldı.)

Kullanım Dışı Uyarı : MySQL uzantısı şu anda kullanımdan kaldırıldı. PDO uzantısını kullanmanızı öneririz

Web uygulamamın SQL enjeksiyonuna karşı savunmasız kalmasını önlemek için üç farklı yol kullanıyorum.

  1. Kullanımı mysql_real_escape_string()içinde önceden tanımlanmış bir fonksiyonudur, PHP ve aşağıdaki karakterlere bu kod eklenti ters eğik çizgi: \x00, \n, \r, \, ', "ve \x1a. SQL enjeksiyon olasılığını en aza indirmek için giriş değerlerini parametre olarak iletin.
  2. En gelişmiş yol PDO'ları kullanmaktır.

Umarım bu sana yardımcı olmuştur.

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 () burada koruma sağlamaz. Sorgunuzun içindeki değişkenlerinizin etrafında tek tırnak ('') kullanırsanız, sizi buna karşı koruyan şey budur. İşte bunun için aşağıda bir çözüm:

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

Bu sorunun bu konuda bazı iyi cevapları var.

PDO'yu kullanmak en iyi seçenek.

Düzenle:

mysql_real_escape_string()PHP 5.5.0'dan itibaren kullanımdan kaldırılmıştır. MySQL veya PDO kullanın.

Mysql_real_escape_string () yöntemine bir alternatif

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

Misal:

$iId = $mysqli->real_escape_string("1 OR 1=1");
$mysqli->query("SELECT * FROM table WHERE id = $iId");

169

Basit bir yol, CodeIgniter veya Laravel gibi filtreleme ve aktif kayıt gibi dahili özelliklere sahip bir PHP çerçevesi kullanmaktır, böylece bu nüanslar için endişelenmenize gerek kalmaz.


7
Bence sorunun asıl amacı bunu böyle bir çerçeve kullanmadan halletmek.
Sanke

147

Uyarı: Bu yanıtta açıklanan yaklaşım yalnızca çok spesifik senaryolar için geçerlidir ve SQL enjeksiyon saldırıları yalnızca enjeksiyon yapabilmekle sınırlı olmadığından güvenli değildir X=Y.

Saldırganlar, PHP'nin $_GETdeğişkeni veya URL'nin sorgu dizesi aracılığıyla forma saldırmaya çalışıyorsa, güvenli olmadıkları takdirde onları yakalayabilirsiniz.

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php

Çünkü 1=1, 2=2, 1=2, 2=1, 1+1=2, vb ... bir saldırganın, bir SQL veritabanı ortak sorulardır. Belki de birçok hack uygulaması tarafından kullanılır.

Ancak, sitenizden güvenli bir sorguyu yeniden yazmamanız gerektiğine dikkat etmelisiniz. Yukarıdaki kod , saldırıya özgü dinamik sorgu dizesini, saldırganın IP adresini veya EVEN THE COOKIES, geçmiş, tarayıcı veya diğer hassasları depolayacak bir sayfaya yeniden yazmak veya yönlendirmek (size bağlı) için bir ipucu veriyor bu bilgileri daha sonra hesaplarını yasaklayarak veya yetkililerle bağlantı kurarak ele alabilirsiniz.


Ne var ne yok 1-1=0? :)
Rápli András

@ RápliAndrás Bir çeşit ([0-9\-]+)=([0-9]+).
5ervant

127

PHP ve MySQL için çok fazla cevap var , ancak burada SQL enjeksiyonunu ve oci8 sürücülerinin düzenli kullanımını önlemek için PHP ve Oracle için kod var :

$conn = oci_connect($username, $password, $connection_string);
$stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123');
oci_bind_by_name($stmt, ':xx', $fieldval);
oci_execute($stmt);

Lütfen oci_bind_by_name parametrelerini açıklayın.
Jahanzeb Awan

127

Idiorm gibi bir nesne-ilişkisel eşleyici kullanmak iyi bir fikirdir :

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

Sizi sadece SQL enjeksiyonlarından değil, sözdizimi hatalarından da kurtarır! Aynı anda birden çok sonuca ve birden çok bağlantıya eylemleri filtrelemek veya uygulamak için yöntem zinciri olan model koleksiyonlarını da destekler.


124

Kullanımdan Kaldırıldı Uyarı: Bu yanıtın örnek kodu (sorunun örnek kodu gibi), MySQLPHP 5.5.0'da kaldırılan ve tamamen PHP 7.0.0'da kaldırılan PHP'nin uzantısını kullanır .

Güvenlik Uyarısı : Bu yanıt en iyi güvenlik uygulamalarıyla uyumlu değildir. Kaçış SQL enjeksiyonunu önlemek için yetersizdir , bunun yerine hazırlanmış ifadeleri kullanın. Aşağıda özetlenen stratejiyi kullanmak kendi sorumluluğunuzdadır. (Ayrıca, mysql_real_escape_string()PHP 7'de kaldırıldı.)

PDO ve MYSQLi kullanmak SQL enjeksiyonlarını önlemek için iyi bir uygulamadır, ancak gerçekten MySQL işlevleri ve sorgularıyla çalışmak istiyorsanız, kullanmak daha iyi olur

mysql_real_escape_string

$unsafe_variable = mysql_real_escape_string($_POST['user_input']);

Bunu önlemek için daha fazla yetenek vardır: tanımlamak gibi - giriş bir dize, sayı, karakter veya dizi ise, bunu algılamak için çok fazla dahili işlev vardır. Ayrıca, giriş verilerini kontrol etmek için bu işlevleri kullanmak daha iyi olur.

is_string

$unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');

is_numeric

$unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');

Ve giriş verilerini kontrol etmek için bu işlevleri kullanmak çok daha iyidir mysql_real_escape_string.


10
Ayrıca, $ _POST dizi üyelerini is_string () ile kontrol etmenin hiçbir anlamı yoktur
Sizin Common Sense

21
UYARI! mysql_real_escape_string() yanılmaz değil .
eggyal

10
mysql_real_escape_stringartık kullanımdan kaldırılmıştır, bu yüzden artık geçerli bir seçenek değildir. Gelecekte PHP'den kaldırılacaktır. PHP veya MySQL kullanıcılarının önerdiklerine geçmek en iyisidir.
jww

2
Tema: Kullanıcının gönderdiği verilere güvenmeyin. Beklediğiniz her şey, kendisi yürütmekte olduğunuz SQL sorgusunun bir parçası olması gereken özel karakterlere veya boole mantığına sahip bir çöp verileridir. $ _POST değerlerini SQL parçası olarak değil, yalnızca veri olarak tutun.
Bimal Poudel

88

Bu küçük işlevi birkaç yıl önce yazdım:

function sqlvprintf($query, $args)
{
    global $DB_LINK;
    $ctr = 0;
    ensureConnection(); // Connect to database if not connected already.
    $values = array();
    foreach ($args as $value)
    {
        if (is_string($value))
        {
            $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
        }
        else if (is_null($value))
        {
            $value = 'NULL';
        }
        else if (!is_int($value) && !is_float($value))
        {
            die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
        }
        $values[] = $value;
        $ctr++;
    }
    $query = preg_replace_callback(
        '/{(\\d+)}/', 
        function($match) use ($values)
        {
            if (isset($values[$match[1]]))
            {
                return $values[$match[1]];
            }
            else
            {
                return $match[0];
            }
        },
        $query
    );
    return $query;
}

function runEscapedQuery($preparedQuery /*, ...*/)
{
    $params = array_slice(func_get_args(), 1);
    $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.   
    return $results;
}

Bu, tek satırlık bir C # -ish Dizesi'nde ifadelerin çalışmasına izin verir.

runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

Değişken türü dikkate alınarak kaçar. Tablo, sütun adlarını parametreleştirmeye çalışırsanız, her dizeyi geçersiz bir sözdizimi olan tırnak işaretleri içine aldığından başarısız olur.

GÜVENLİK GÜNCELLEME: Önceki str_replacesürüm, kullanıcı verilerine {#} jeton eklenerek enjeksiyon yapılmasına izin verdi. Bu preg_replace_callbacksürüm, değiştirme bu belirteçleri içeriyorsa sorun yaratmaz.

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.