PHP'de mysql_ * işlevlerini neden kullanmamalıyım?

Oct 12 2012

Neden mysql_*işlevlerin kullanılmaması gerektiğinin teknik nedenleri nelerdir ? (örneğin mysql_query(), mysql_connect()veya mysql_real_escape_string())?

Sitemde çalışıyor olsalar bile neden başka bir şey kullanmalıyım?

Sitemde çalışmazlarsa, neden böyle hatalar alıyorum

Uyarı: mysql_connect (): Böyle bir dosya veya dizin yok

Yanıtlar

2118 Quentin Oct 12 2012 at 20:23

MySQL uzantısı:

  • Aktif geliştirme altında değil
  • Is resmen kaldırılmış PHP 5.5 itibariyle (Haziran 2013 yayınlandı).
  • PHP 7.0'dan itibaren tamamen kaldırılmıştır (Aralık 2015'te yayınlandı)
    • Bu, 31 Aralık 2018 itibariyle PHP'nin desteklenen herhangi bir sürümünde bulunmadığı anlamına gelir . PHP'nin onu destekleyen bir sürümünü kullanıyorsanız, güvenlik sorunları giderilmeyen bir sürüm kullanıyorsunuzdur.
  • OO arayüzü yok
  • Desteklemiyor:
    • Engellemeyen, eşzamansız sorgular
    • Hazırlanmış ifadeler veya parametreli sorgular
    • Saklanan prosedürler
    • Çoklu İfadeler
    • İşlemler
    • "Yeni" parola kimlik doğrulama yöntemi (MySQL 5.6'da varsayılan olarak açıktır; 5.7'de gereklidir)
    • MySQL 5.1 veya sonraki sürümlerdeki yeni işlevlerden herhangi biri

Kullanımdan kaldırıldığı için, kodunuzun gelecekte daha az kanıtlanmış olmasını sağlar.

Hazırlanan ifadeler için destek eksikliği, harici verilerden kaçmak ve alıntı yapmak için ayrı bir işlev çağrısıyla manuel olarak kaçmaktan daha net, daha az hataya açık bir yöntem sağladığından özellikle önemlidir.

SQL uzantılarının karşılaştırmasına bakın .

1303 NullPoiиteя Jan 01 2013 at 18:52

PHP, MySQL'e bağlanmak için üç farklı API sunar. Bunlar mysql(PHP 7'den itibaren kaldırılmıştır) mysqlive PDOuzantılardır.

mysql_*Fonksiyonları çok popüler olarak kullanılan, ancak bunların kullanımı artık teşvik edilmez. Dokümantasyon ekibi veritabanı güvenlik durumunu tartışıyor ve kullanıcıları yaygın olarak kullanılan ext / mysql uzantısından uzaklaşmaları için eğitmek bunun bir parçası ( php.internals'ı kontrol edin: ext / mysql'yi kullanımdan kaldırma ).

Ve sonraki PHP geliştirici ekibi, E_DEPRECATEDkullanıcılar MySQL'e bağlandıklarında mysql_connect(), mysql_pconnect()bunlar aracılığıyla veya yerleşik olarak örtük bağlantı işlevi aracılığıyla hatalar oluşturma kararı aldı ext/mysql.

ext/mysqloldu resmen PHP 5.5 tarihinden itibaren kullanımdan kaldırılmıştır ve olmuştur PHP 7'nin olarak uzaklaştırıldı .

Kırmızı Kutuyu görüyor musun?

Herhangi bir mysql_*işlev kılavuzu sayfasına gittiğinizde, artık kullanılmaması gerektiğini açıklayan kırmızı bir kutu görürsünüz.

Neden


Uzaklaşmak ext/mysqlsadece güvenlikle ilgili değil, aynı zamanda MySQL veritabanının tüm özelliklerine erişim sağlamakla da ilgilidir.

ext/mysqliçin inşa edilmiştir MySQL 3.23 ve ancak o zaman çoğunlukla korumak için biraz daha zor kodu yapar bu eski sürümü ile uyumluluk tutarken beri çok az eklemeler var. İnclude tarafından desteklenmeyen eksik özellikler ext/mysql: ( PHP kılavuzundan ).

  • Saklanan prosedürler (birden fazla sonuç kümesini işleyemez)
  • Hazırlanan ifadeler
  • Şifreleme (SSL)
  • Sıkıştırma
  • Tam Karakter Seti desteği

mysql_*Fonksiyonu kullanmama nedeni :

  • Aktif geliştirme altında değil
  • PHP 7 itibariyle kaldırıldı
  • OO arayüzü yok
  • Engellemeyen, eşzamansız sorguları desteklemez
  • Hazırlanmış ifadeleri veya parametreli sorguları desteklemez
  • Depolanan prosedürleri desteklemiyor
  • Birden çok ifadeyi desteklemiyor
  • İşlemleri desteklemiyor
  • MySQL 5.1'deki tüm işlevleri desteklemiyor

Yukarıdaki nokta, Quentin'in cevabından alıntılanmıştır

Hazırlanan ifadeler için destek eksikliği, harici verilerden kaçmak ve alıntı yapmak için ayrı bir işlev çağrısıyla manuel olarak kaçmaktan daha net, daha az hataya açık bir yöntem sağladıkları için özellikle önemlidir.

SQL uzantılarının karşılaştırmasına bakın .


Kullanımdan kaldırma uyarılarını engelleme

Kod dönüştürülen edilirken MySQLi/ PDO, E_DEPRECATEDayarlayarak hatalar bastırılabilir error_reportingiçinde php.ini dışlamak içinE_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

Bunun aynı zamanda diğer kullanımdan kaldırma uyarılarını da gizleyeceğini unutmayın , ancak bunlar MySQL dışındaki şeyler için olabilir. ( PHP kılavuzundan )

Makale Hangi Meli sen Kullanımı: MySQLi vs PDO? tarafından Dejan Marjanovic seçtiğiniz için yardımcı olacaktır.

Ve daha iyi bir yol PDO, ve şimdi basit bir PDOeğitim yazıyorum .


Basit ve kısa bir PDO öğreticisi


S. Aklımdaki ilk soru şuydu: "PDO" nedir?

A. " PDO - PHP Veri Nesneleri - birden çok veritabanına tek tip erişim yöntemi sağlayan bir veritabanı erişim katmanıdır."


MySQL'e bağlanma

İle mysql_*biz eski şekilde söyleyebiliriz fonksiyonu ya da (yukarıda PHP'de 5.5 ve kullanımdan kaldırıldı)

$link = mysql_connect('localhost', 'user', 'pass'); mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);

İle PDO: Tek yapmanız gereken yeni bir PDOnesne oluşturmaktır . Yapıcı, veritabanı kaynağının PDOyapıcısını belirtmek için parametreleri kabul eder, çoğunlukla DSN(veri kaynağı adı) ve isteğe bağlı olarak username, dört parametre alır password.

Burada bence hariç her şeye aşinasınız DSN; bu yeni PDO. A DSN, temelde PDOhangi sürücünün kullanılacağını ve bağlantı ayrıntılarını söyleyen bir seçenekler dizisidir . Daha fazla referans için PDO MySQL DSN'yi kontrol edin .

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');

Not: kullanabilirsiniz charset=UTF-8, ancak bazen bir hataya neden olur, bu nedenle kullanmak daha iyidir utf8.

Herhangi bir bağlantı hatası varsa, daha fazla PDOExceptionişlemek için yakalanabilecek bir nesne atacaktır Exception.

İyi okuma : Bağlantılar ve Bağlantı yönetimi ¶

Ayrıca dördüncü parametreye bir dizi olarak birkaç sürücü seçeneği de aktarabilirsiniz. PDOİstisna moduna geçen parametreyi geçmenizi tavsiye ederim . Bazı PDOsürücüler yerel hazırlanmış ifadeleri desteklemediğinden PDO, hazırlamanın öykünmesini gerçekleştirir. Ayrıca bu öykünmeyi manuel olarak etkinleştirmenize de izin verir. Yerel sunucu tarafında hazırlanmış ifadeleri kullanmak için, bunu açıkça ayarlamalısınız false.

Diğeri, MySQLsürücüde varsayılan olarak etkinleştirilen hazır öykünmeyi kapatmaktır , ancak PDOgüvenli bir şekilde kullanmak için öykünme hazırla kapatılmalıdır .

Daha sonra öykünme hazırlamanın neden kapatılması gerektiğini açıklayacağım. Sebep bulmak için lütfen bu gönderiyi kontrol edin .

Yalnızca MySQLtavsiye etmediğim eski bir sürümü kullanıyorsanız kullanılabilir .

Aşağıda bunu nasıl yapabileceğinize dair bir örnek verilmiştir:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password',
              array(PDO::ATTR_EMULATE_PREPARES => false,
              PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

PDO oluşturulduktan sonra nitelikler belirleyebilir miyiz?

Evet , PDO oluşturulduktan sonra şu setAttributeyöntemle de bazı nitelikler belirleyebiliriz :

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Hata yönetimi


Hata işleme de çok daha kolaydır PDOdaha mysql_*.

Kullanırken yaygın bir uygulama mysql_*şudur:

//Connected to MySQL
$result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));

OR die()Bu, hatayı halletmek için iyi bir yol değil çünkü içindeki şeyi halledemiyoruz die. Sadece komut dosyasını aniden sonlandıracak ve ardından hatayı genellikle son kullanıcılarınıza göstermek istemediğiniz ekrana yansıtacak ve kanlı bilgisayar korsanlarının şemanızı keşfetmesine izin verecektir. Alternatif olarak, hataları işlemek için mysql_*işlevlerin dönüş değerleri sıklıkla mysql_error () ile birlikte kullanılabilir .

PDOdaha iyi bir çözüm sunar: istisnalar. Bizimle ilgisi şey PDObir sarılmış olmalıdır try- catchbloğun. PDOHata modu özelliğini ayarlayarak üç hata modundan birine zorlayabiliriz . Üç hata işleme modu aşağıdadır.

  • PDO::ERRMODE_SILENT. Bu sadece hata kodlarını ayarlamaktır ve hemen hemen mysql_*her sonucu kontrol etmeniz ve ardından $db->errorInfo();hata ayrıntılarını almak için bakmanız gereken yerle aynı şekilde davranır .
  • PDO::ERRMODE_WARNINGYükselt E_WARNING. (Çalışma zamanı uyarıları (önemli olmayan hatalar). Komut dosyasının yürütülmesi durdurulmaz.)
  • PDO::ERRMODE_EXCEPTION: İstisnaları atın. PDO tarafından ortaya çıkan bir hatayı temsil eder. PDOExceptionKendi kodunuzdan bir atmamalısınız . PHP'deki istisnalar hakkında daha fazla bilgi için İstisnalara bakın . or die(mysql_error());Yakalanmadığı zamanki gibi davranır . Ancak aksine or die(), PDOExceptionbunu seçerseniz, zarif bir şekilde yakalanabilir ve kullanılabilir.

İyi okuma :

  • Hatalar ve hata işleme ¶
  • PDOException sınıfı ¶
  • İstisnalar ¶

Sevmek:

$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); $stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

Ve bunu sarar try- catchaşağıya şöyle:

try {
    //Connect as appropriate as above
    $db->query('hi'); //Invalid query!
} 
catch (PDOException $ex) { echo "An Error occured!"; //User friendly message/message you want to show to user some_logging_function($ex->getMessage());
}

Sen ile ele almak gerekmez try- catchhemen şimdi. İstediğiniz zaman yakalayabilirsiniz, ancak kullanmanızı şiddetle tavsiye ederim try- catch. Ayrıca, PDOşeyleri çağıran işlevin dışında yakalamak daha mantıklı olabilir :

function data_fun($db) { $stmt = $db->query("SELECT * FROM table"); return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

//Then later
try {
    data_fun($db); } catch(PDOException $ex) {
    //Here you can handle error and show message/perform action you want.
}

Ayrıca, halledebilirsin or die()veya beğenebiliriz diyebiliriz mysql_*, ama gerçekten çok çeşitli olacak. Üretimdeki tehlikeli hata mesajlarını çevirerek display_errors offve sadece hata günlüğünüzü okuyarak gizleyebilirsiniz .

Şimdi, yukarıdaki her şeyi okuduktan sonra, muhtemelen düşünüyorsun: halt ne olduğunu ben sadece basit eğilerek başlamak istediğinizde SELECT, INSERT, UPDATEveya DELETEifadeleri? Merak etmeyin, işte başlıyoruz:


Verilerin Seçilmesi

Yani ne yapıyorsun mysql_*:

<?php
$result = mysql_query('SELECT * from table') or die(mysql_error()); $num_rows = mysql_num_rows($result); while($row = mysql_fetch_assoc($result)) { echo $row['field1'];
}

Şimdi içinde PDO, bunu şu şekilde yapabilirsiniz:

<?php
$stmt = $db->query('SELECT * FROM table');

while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo $row['field1'];
}

Veya

<?php
$stmt = $db->query('SELECT * FROM table'); $results = $stmt->fetchAll(PDO::FETCH_ASSOC); //Use $results

Not : Aşağıdaki ( query()) yöntemi kullanıyorsanız , bu yöntem bir PDOStatementnesne döndürür . Yani sonucu almak istiyorsanız yukarıdaki gibi kullanın.

<?php
foreach($db->query('SELECT * FROM table') as $row) {
    echo $row['field1'];
}

PDO Verisinde, ->fetch()ifade tutamacınızın bir yöntemi aracılığıyla elde edilir . Getirmeyi aramadan önce, en iyi yaklaşım PDO'ya verinin nasıl alınmasını istediğinizi söylemektir. Aşağıdaki bölümde bunu açıklıyorum.

Getirme Modları

Yukarıdaki ve kodundaki kullanımına dikkat PDO::FETCH_ASSOCedin . Bu , satırların alan adlarının anahtar olduğu bir ilişkilendirilebilir dizi olarak döndürülmesini söyler . Tek tek açıklayacağım birçok başka getirme modu da var.fetch()fetchAll()PDO

Öncelikle, getirme modunun nasıl seçileceğini açıklarım:

 $stmt->fetch(PDO::FETCH_ASSOC)

Yukarıda kullanıyorum fetch(). Ayrıca kullanabilirsin:

  • PDOStatement::fetchAll() - Tüm sonuç kümesi satırlarını içeren bir dizi döndürür
  • PDOStatement::fetchColumn() - Bir sonuç kümesinin sonraki satırından tek bir sütun verir
  • PDOStatement::fetchObject() - Sonraki satırı çeker ve bir nesne olarak döndürür.
  • PDOStatement::setFetchMode() - Bu ifade için varsayılan getirme modunu ayarlayın

Şimdi getirme moduna geliyorum:

  • PDO::FETCH_ASSOC: sonuç kümenizde döndürüldüğü gibi sütun adına göre dizinlenmiş bir dizi döndürür
  • PDO::FETCH_BOTH (varsayılan): sonuç kümenizde döndürüldüğü gibi hem sütun adı hem de 0 dizinli sütun numarası tarafından dizinlenmiş bir dizi döndürür

Daha da fazla seçenek var! Bunların hepsini PDOStatementGetirme belgelerinde okuyun . .

Satır sayısını elde etmek :

mysql_num_rowsDöndürülen satırların sayısını elde etmek için kullanmak yerine , aşağıdaki gibi bir alabilir PDOStatementve yapabilirsiniz rowCount():

<?php
$stmt = $db->query('SELECT * FROM table');
$row_count = $stmt->rowCount();
echo $row_count.' rows selected';

Son Eklenen Kimliği Alma

<?php
$result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')"); $insertId = $db->lastInsertId();

İfadeleri Ekleme ve Güncelleme veya Silme

mysql_*İşlevsel olarak yaptığımız şey:

<?php
$results = mysql_query("UPDATE table SET field='value'") or die(mysql_error());
echo mysql_affected_rows($result);

Ve pdo'da, aynı şey şu şekilde yapılabilir:

<?php
$affected_rows = $db->exec("UPDATE table SET field='value'"); echo $affected_rows;

Yukarıdaki sorguda PDO::execbir SQL ifadesi yürütün ve etkilenen satırların sayısını döndürür.

Ekleme ve silme işlemleri daha sonra ele alınacaktır.

Yukarıdaki yöntem yalnızca sorguda değişken kullanmadığınızda kullanışlıdır. Ancak bir sorguda bir değişken kullanmanız gerektiğinde, asla yukarıdaki gibi denemeyin ve orada hazırlanmış ifade veya parametreli ifade için öyle.


Hazırlanmış Beyanlar

S. Hazır ifade nedir ve neden onlara ihtiyacım var?
A. Hazırlanmış bir ifade, sunucuya yalnızca verileri göndererek birden çok kez yürütülebilen önceden derlenmiş bir SQL ifadesidir.

Hazırlanmış bir ifadeyi kullanmanın tipik iş akışı aşağıdaki gibidir ( Wikipedia üç 3 noktasından alıntı ):

  1. Hazırla : İfade şablonu uygulama tarafından oluşturulur ve veritabanı yönetim sistemine (DBMS) gönderilir. Parametreler, yer tutucular veya bağ değişkenleri olarak adlandırılan belirli değerler belirtilmemiş olarak bırakılmıştır ( ?aşağıda etiketlenmiştir ):

    INSERT INTO PRODUCT (name, price) VALUES (?, ?)

  2. DBMS, ifade şablonunda sorgu optimizasyonunu ayrıştırır, derler ve gerçekleştirir ve sonucu çalıştırmadan depolar.

  3. Yürütme : Daha sonra, uygulama parametreler için değerler sağlar (veya bağlar) ve DBMS deyimi yürütür (muhtemelen bir sonuç döndürür). Uygulama, ifadeyi farklı değerlerle istediği kadar çalıştırabilir. Bu örnekte, ilk parametre ve 1.00ikinci parametre için "Ekmek" sağlayabilir .

SQL'inize yer tutucular ekleyerek hazırlanmış bir deyimi kullanabilirsiniz. Temelde yer tutucular içermeyen üç tane vardır (bunu yukarıdaki değişkenle denemeyin), biri adsız yer tutucularla ve biri de adlandırılmış yer tutucularla.

S. Peki şimdi, adlandırılmış yer tutucular nelerdir ve bunları nasıl kullanırım?
A. Adlandırılmış yer tutucular. Soru işaretleri yerine, iki nokta üst üste ile başlayan açıklayıcı adlar kullanın. İsim yer tutucuda konum / değer sırasını önemsemiyoruz:

 $stmt->bindParam(':bla', $bla);

bindParam(parameter,variable,data_type,length,driver_options)

Ayrıca bir yürütme dizisi kullanarak da bağlayabilirsiniz:

<?php
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->execute(array(':name' => $name, ':id' => $id)); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

OOPArkadaşlar için bir başka güzel özellik de, adlandırılmış yer tutucuların, özelliklerin adlandırılmış alanlarla eşleştiği varsayılarak nesneleri doğrudan veritabanınıza ekleme yeteneğine sahip olmasıdır. Örneğin:

class person {
    public $name;
    public $add; function __construct($a,$b) { $this->name = $a; $this->add = $b; } } $demo = new person('john','29 bla district');
$stmt = $db->prepare("INSERT INTO table (name, add) value (:name, :add)");
$stmt->execute((array)$demo);

S. Peki şimdi, adsız yer tutucular nedir ve bunları nasıl kullanırım?
A. Bir örnek verelim:

<?php
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->bindValue(1, $name, PDO::PARAM_STR);
$stmt->bindValue(2, $add, PDO::PARAM_STR);
$stmt->execute();

ve

$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)"); $stmt->execute(array('john', '29 bla district'));

Yukarıda, ?bir isim yer tutucusundaki gibi bir isim yerine bunları görebilirsiniz . Şimdi ilk örnekte, değişkenleri çeşitli yer tutuculara ( $stmt->bindValue(1, $name, PDO::PARAM_STR);) atıyoruz . Ardından, bu yer tutuculara değerler atar ve ifadeyi çalıştırırız. İkinci örnekte, birinci dizi öğesi birinciye ?ve ikincisi ikinciye gider ?.

NOT : Adsız yer tutucularda , PDOStatement::execute()yönteme aktardığımız dizideki öğelerin doğru sırasına dikkat etmeliyiz .


SELECT, INSERT, UPDATE, DELETESorgular hazırlanan

  1. SELECT:

    $stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
    $stmt->execute(array(':name' => $name, ':id' => $id)); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
    
  2. INSERT:

    $stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)"); $stmt->execute(array(':field1' => $field1, ':field2' => $field2));
    $affected_rows = $stmt->rowCount();
    
  3. DELETE:

    $stmt = $db->prepare("DELETE FROM table WHERE id=:id");
    $stmt->bindValue(':id', $id, PDO::PARAM_STR);
    $stmt->execute(); $affected_rows = $stmt->rowCount();
    
  4. UPDATE:

    $stmt = $db->prepare("UPDATE table SET name=? WHERE id=?"); $stmt->execute(array($name, $id));
    $affected_rows = $stmt->rowCount();
    

NOT:

Ancak PDOve / veya MySQLitamamen güvenli değildir. Cevabı kontrol edin PDO tarafından hazırlanmış ifadeler SQL enjeksiyonunu önlemek için yeterli mi? ircmaxell tarafından . Ayrıca cevabının bir kısmını da aktarıyorum:

$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(chr(0xbf) . chr(0x27) . " OR 1=1 /*"));
305 Madara'sGhost Oct 12 2012 at 20:28

Öncelikle herkese verdiğimiz standart yorumla başlayalım:

Lütfen mysql_*yeni kodda işlev kullanmayın . Artık korunmuyorlar ve resmi olarak kullanımdan kaldırılıyorlar . Bkz kırmızı kutuyu ? Bunun yerine hazırlanmış ifadeler hakkında bilgi edininve PDO veya MySQLi kullanın - bu makale hangisi olduğuna karar vermenize yardımcı olacaktır. PDO'yu seçerseniz, işte size iyi bir eğitim .

Şunu cümle cümle üzerinden inceleyelim ve açıklayalım:

  • Artık korunmuyorlar ve resmi olarak kullanımdan kaldırılıyorlar

    Bu, PHP topluluğunun bu çok eski işlevler için desteği kademeli olarak bıraktığı anlamına gelir. PHP'nin gelecekteki (son) bir sürümünde bulunmayabilirler! Bu işlevlerin kullanımına devam edilmesi, kodunuzu (şimdiye kadar) uzak gelecekte bozabilir.

    YENİ! - ext / mysql artık resmi olarak PHP 5.5'ten itibaren kullanımdan kaldırıldı!

    Daha yeni! ext / mysql PHP 7'de kaldırılmıştır .

  • Bunun yerine, hazırlanmış ifadeleri öğrenmelisiniz

    mysql_*uzantısı, SQL Enjeksiyonuna karşı çok etkili bir karşı önlem olan (diğer şeylerin yanı sıra) hazırlanmış ifadeleri desteklemez . MySQL bağımlı uygulamalardaki çok ciddi bir güvenlik açığını gidererek saldırganların betiğinize erişmesine ve veritabanınızda olası herhangi bir sorguyu gerçekleştirmesine olanak sağladı .

    Daha fazla bilgi için bkz . PHP'de SQL enjeksiyonunu nasıl önleyebilirim?

  • Kırmızı Kutuyu görüyor musun?

    Herhangi bir mysqlişlev kılavuz sayfasına gittiğinizde, artık kullanılmaması gerektiğini açıklayan kırmızı bir kutu görürsünüz.

  • PDO veya MySQLi kullanın

    Daha iyi, daha sağlam ve iyi oluşturulmuş alternatifler, veritabanı etkileşimine tam bir OOP yaklaşımı sunan PDO - PHP Veritabanı Nesnesi ve MySQL'e özgü bir iyileştirme olan MySQLi vardır.

221 mario Dec 25 2013 at 06:30

Kullanım kolaylığı

Analitik ve sentetik nedenlerden daha önce bahsedilmişti. Yeni gelenler için, tarihli mysql_ işlevlerini kullanmayı bırakmak için daha önemli bir teşvik vardır.

Çağdaş veritabanı API'lerinin kullanımı daha kolaydır .

Kodu basitleştirebilen çoğunlukla bağlı parametrelerdir . Ve mükemmel öğreticilerle (yukarıda görüldüğü gibi) PDO'ya geçiş aşırı derecede zor değildir.

Daha büyük bir kod tabanını aynı anda yeniden yazmak zaman alır. Bu ara alternatif için Raison d'être:

Eşdeğer pdo_ * yerine işlevleri mysql_ *

< Pdo_mysql.php > kullanarak çok az çabayla eski mysql_ işlevlerinden geçiş yapabilirsiniz . pdo_Muadillerinin yerini alan işlev sarmalayıcılar ekler mysql_.

  1. Basitçe , veritabanıyla etkileşime girmesi gereken her çağrı betiğinde.include_once("pdo_mysql.php");

  2. mysql_İşlev önekini her yerde kaldırın ve yerine koyun pdo_.

    • mysql_connect() olur pdo_connect()
    • mysql_query() olur pdo_query()
    • mysql_num_rows() olur pdo_num_rows()
    • mysql_insert_id() olur pdo_insert_id()
    • mysql_fetch_array() olur pdo_fetch_array()
    • mysql_fetch_assoc() olur pdo_fetch_assoc()
    • mysql_real_escape_string() olur pdo_real_escape_string()
    • ve bunun gibi...

  3. Kodunuz aynı şekilde çalışacak ve yine de çoğunlukla aynı görünecektir:

    include_once("pdo_mysql.php"); 
    
    pdo_connect("localhost", "usrABC", "pw1234567");
    pdo_select_db("test");
    
    $result = pdo_query("SELECT title, html FROM pages"); while ($row = pdo_fetch_assoc($result)) { print "$row[title] - $row[html]";
    }
    

Et voilà.
Kodunuz PDO kullanıyor .
Şimdi onu gerçekten kullanma zamanı .

Bağlı parametrelerin kullanımı kolay olabilir

Sadece daha az hantal bir API'ye ihtiyacınız var.

pdo_query()bağlı parametreler için çok kolay destek ekler. Eski kodu dönüştürmek basittir:

Değişkenlerinizi SQL dizesinin dışına taşıyın.

  • Bunları virgülle ayrılmış işlev parametreleri olarak ekleyin pdo_query().
  • Soru işaretlerini ?, değişkenlerin daha önce olduğu yer tutucular olarak yerleştirin.
  • Daha 'önce dize değerlerini / değişkenlerini içeren tek tırnak işaretlerinden kurtulun .

Daha uzun kod için avantaj daha belirgin hale gelir.

Genellikle dize değişkenleri yalnızca SQL'e eklenmez, aradaki kaçan çağrılarla birleştirilir.

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title='" . pdo_real_escape_string($title) . "' OR id='".
   pdo_real_escape_string($title) . "' AND user <> '" . pdo_real_escape_string($root) . "' ORDER BY date")

Yer ?tutucular uygulandığında bununla uğraşmanıza gerek kalmaz:

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title=? OR id=? AND user<>? ORDER BY date", $title, $id, $root)

Pdo_ * 'nin ya da .
Sadece bir değişken kaçış yok ve aynı sorguda bağlamak.

  • Yer tutucu özelliği, arkasındaki gerçek PDO tarafından sağlanır.
  • Böylece :nameddaha sonra yer tutucu listelerine de izin verildi .

Daha da önemlisi, $ _REQUEST [] değişkenlerini herhangi bir sorgunun arkasına güvenle geçirebilirsiniz. Gönderilen <form>alanlar veritabanı yapısıyla tam olarak eşleştiğinde daha da kısadır:

pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);

Çok fazla basitlik. Ama neden kurtulmak mysql_ve kaçmak isteyebileceğinize dair daha fazla yeniden yazma tavsiyesine ve teknik nedenlere geri dönelim .

Herhangi bir eski okul sanitize()işlevini düzeltin veya kaldırın

Tüm mysql_çağrıları pdo_querybağlı parametrelere dönüştürdükten sonra , tüm gereksiz pdo_real_escape_stringçağrıları kaldırın .

Özellikle, herhangi bir sanitizeveya cleanveya filterThisveya veya veya veya veya veya veya veya bir şekilde clean_datatarihli öğreticiler tarafından ilan edilen işlevleri bir şekilde veya diğerinde düzeltmelisiniz :

function sanitize($str) {
   return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}

Buradaki en göze çarpan hata, belge eksikliğidir. Daha da önemlisi, filtreleme sırası tam olarak yanlış sıradaydı.

  • Doğru düzen olurdu: deprecatedly stripslashesiçteki araması olarak, daha sonra trim, sonradan strip_tags, htmlentitiessadece son çıkış bağlamı ve için _escape_stringonun uygulama olarak doğrudan intersparsing SQL preceed olmalıdır.

  • Ancak ilk adım olarak aramadan kurtulun_real_escape_string .

  • sanitize()Veritabanınız ve uygulama akışınız HTML bağlamında güvenli dizeler bekliyorsa , işlevinizin geri kalanını şimdilik saklamanız gerekebilir . Bundan sonra yalnızca HTML kaçışını uyguladığına dair bir yorum ekleyin.

  • Dize / değer işleme, PDO'ya ve parametreleştirilmiş ifadelerine delege edilir.

  • stripslashes()Sanitize işlevinizde bahsedilmişse , bu daha yüksek düzeyde bir gözetimi gösterebilir.

    • Bu, genellikle kullanımdan kaldırılan hasarın geri alınması (çift kaçma) için vardı magic_quotes. Bununla birlikte, en iyisi merkezi olarak sabitlenir , dizgiye göre dizgiye göre değil.

    • Kullanıcı alanı tersine çevirme yaklaşımlarından birini kullanın . Sonra kaldırmak stripslashes()içinde sanitizeişlevi.

    Magic_quotes hakkında tarihi not. Bu özellik haklı olarak kullanımdan kaldırılmıştır. Bununla birlikte, genellikle hatalı bir şekilde başarısız güvenlik özelliği olarak tasvir edilir . Ancak sihirli sözler, tenis topları beslenme kaynağı olarak başarısız olduğu için başarısız bir güvenlik özelliğidir. Bu onların amacı değildi.

    PHP2 / FI'deki orijinal uygulama, onu açıkça " tırnak işaretleri otomatik olarak atlanacak ve form verilerini doğrudan msql sorgularına aktarmayı kolaylaştıracak " şeklinde tanıttı . Yalnızca ASCII'yi desteklediği için mSQL ile kullanmak yanlışlıkla güvenliydi .
    Daha sonra PHP3 / Zend, MySQL için magic_quotes'ı yeniden tanıttı ve yanlış belgelendirdi. Ancak başlangıçta bu, güvenlik amaçlı değil , sadece kullanışlı bir özellikti .

Hazırlanan ifadeler nasıl farklılık gösterir?

Dize değişkenlerini SQL sorgularına karıştırdığınızda, takip etmeniz daha karmaşık hale gelmez. Ayrıca MySQL'in kodu ve verileri tekrar ayırması için gereksiz bir çaba.

SQL enjeksiyonları basitçe verilerin kod bağlamına akmasıdır . Bir veritabanı sunucusu daha sonra PHP'nin değişkenleri sorgu cümleleri arasına orijinal olarak nereye yapıştırdığını tespit edemez.

Bağlı parametrelerle, PHP kodunuzdaki SQL kodunu ve SQL bağlam değerlerini ayırırsınız. Ancak perde arkasında yeniden karıştırılmaz (PDO :: EMULATE_PREPARES hariç). Veritabanınız, değişmeyen SQL komutlarını ve 1: 1 değişken değerlerini alır.

Bu cevap, bırakmanın okunabilirlik avantajlarını önemsemeniz gerektiğini vurgular mysql_. Bu görünür ve teknik veri / kod ayrımı nedeniyle bazen bir performans avantajı (sadece farklı değerlere sahip tekrarlanan INSERT'ler) vardır.

Parametre bağlamanın hala tüm SQL enjeksiyonlarına karşı sihirli bir tek duraklı çözüm olmadığına dikkat edin . Veriler / değerler için en yaygın kullanımı yönetir. Ancak sütun adı / tablo tanımlayıcılarını beyaz listeye ekleyemez, dinamik yan tümce oluşturma konusunda yardımcı olamaz veya yalnızca düz dizi değer listeleri.

Hibrit PDO kullanımı

Bu pdo_*sarmalayıcı işlevleri, kodlama dostu bir stop-gap API'si oluşturur. (Kendine MYSQLIözgü işlev imza kayması olmasaydı, neredeyse ne olabilirdi). Ayrıca çoğu zaman gerçek PDO'yu ortaya çıkarırlar.
Yeniden yazmanın yeni pdo_ işlev adlarını kullanmakla bitmesi gerekmez. Her pdo_query () 'yi birer birer düz bir $ pdo-> ready () -> execute () çağrısına geçirebilirsiniz.

Yine de basitleştirmeye başlamak en iyisidir. Örneğin, genel sonuç getirme:

$result = pdo_query("SELECT * FROM tbl"); while ($row = pdo_fetch_assoc($result)) {

Sadece bir foreach yinelemesiyle değiştirilebilir:

foreach ($result as $row) {

Ya da daha iyisi, doğrudan ve eksiksiz bir dizi alımı:

$result->fetchAll();

Çoğu durumda, PDO veya mysql_'nin genellikle başarısız sorgulardan sonra sağladığından daha yararlı uyarılar alırsınız.

Diğer seçenekler

Bu, umarım bazı pratik nedenleri ve düşmeye değer bir yolu görselleştirdi mysql_.

Sadece pdo'ya geçmek pek yeterli değil. pdo_query()aynı zamanda sadece bir arayüzdür.

Parametre bağlamayı da sunmadığınız veya daha güzel API'den başka bir şey kullanamadığınız sürece, bu anlamsız bir anahtardır. Umarım yeni gelenlerin cesaretini kırmayacak kadar basit tasvir edilmiştir. (Eğitim genellikle yasaklamadan daha iyi çalışır.)

İşe yarayacak en basit şey kategorisine uygun olsa da, yine de çok deneysel bir koddur. Bunu sadece hafta sonu yazdım. Bununla birlikte, çok sayıda alternatif var. Sadece PHP veritabanı soyutlaması için google ve biraz göz atın. Bu tür görevler için her zaman çok sayıda mükemmel kitaplık olmuştur ve olacaktır.

Veritabanı etkileşiminizi daha da basitleştirmek istiyorsanız, Paris / Idiorm gibi haritacılar denemeye değer. Artık hiç kimsenin JavaScript'te yumuşak DOM'u kullanmaması gibi, günümüzde ham bir veritabanı arayüzüne bakıcılık yapmanız gerekmiyor.

150 Alnitak Oct 12 2012 at 20:22

mysql_fonksiyonlar:

  1. güncel değil - artık bakım yapılmıyor
  2. başka bir veritabanı arka ucuna kolayca geçmenize izin vermeyin
  3. hazırlanmış beyanları desteklemeyin, bu nedenle
  4. programcıları sorgular oluşturmak için birleştirmeyi kullanmaya teşvik ederek SQL enjeksiyon güvenlik açıklarına yol açar
109 YourCommonSense Jan 02 2013 at 00:42

Teknik nedenlerden bahsetmişken, yalnızca birkaç tane var, son derece spesifik ve nadiren kullanılıyor. Büyük olasılıkla onları hayatınızda asla kullanmayacaksınız.
Belki çok cahilim ama onları kullanma fırsatım olmadı.

  • engellemeyen, eşzamansız sorgular
  • birden çok sonuç kümesini döndüren saklı yordamlar
  • Şifreleme (SSL)
  • Sıkıştırma

İhtiyacınız varsa - bunlar şüphesiz mysql uzantısından daha şık ve modern görünümlü bir şeye geçmek için teknik nedenlerdir.

Bununla birlikte, deneyiminizi biraz daha zorlaştırabilecek teknik olmayan bazı sorunlar da vardır.

  • bu işlevlerin modern PHP sürümleriyle daha fazla kullanılması, kullanımdan kaldırılan düzey uyarıları artıracaktır. Basitçe kapatılabilirler.
  • uzak bir gelecekte, muhtemelen varsayılan PHP yapısından kaldırılabilirler. Mydsql ext PECL'e taşınacağından ve her barındırıcı, siteleri on yıllardır çalışan istemcilerini kaybetmek istemedikleri için PHP'yi onunla derlemekten mutlu olacaklar.
  • Stackoverflow topluluğundan güçlü bir direnç. Bu dürüst işlevlerden her zaman söz ettiğinizde, bunların katı bir tabu altında olduğu söyleniyor.
  • Ortalama bir PHP kullanıcısı olarak, büyük olasılıkla bu işlevleri kullanma fikriniz hataya açık ve yanlıştır. Sırf size yanlış yolu öğreten tüm bu sayısız öğretici ve kılavuz yüzünden. İşlevlerin kendileri değil - vurgulamalıyım - kullanılma biçimleri.

Bu ikinci sorun bir sorundur.
Ama bence önerilen çözüm de daha iyi değil.
Bana öyle geliyor ki, tüm bu PHP kullanıcıları SQL sorgularını bir kerede düzgün bir şekilde nasıl ele alacaklarını öğrenecekler. Büyük olasılıkla , yaklaşımı aynı bırakarak, mekanik olarak mysql_ * 'yi mysqli_ * olarak değiştirirlerdi . Özellikle mysqli hazırlanmış ifadelerin kullanımını inanılmaz acı verici ve zahmetli hale getirdiğinden. Yerel olarak hazırlanmış ifadelerin SQL enjeksiyonlarından korunmak için yeterli olmadığından ve ne mysqli ne de PDO'nun bir çözüm sunduğundan
bahsetmiyorum bile .

Bu yüzden, bu dürüst uzantıyla savaşmak yerine, yanlış uygulamalarla savaşmayı ve insanları doğru yollarla eğitmeyi tercih ederim.

Ayrıca, bazı yanlış veya önemli olmayan nedenler vardır.

  • Depolanan Prosedürleri desteklemiyor (çağlardır kullanıyorduk mysql_query("CALL my_proc");)
  • İşlemleri desteklemez (yukarıdakiyle aynı)
  • Çoklu İfadeleri desteklemiyor (onlara kimin ihtiyacı var?)
  • Aktif geliştirme altında değil (peki ne? Sizi herhangi bir pratik şekilde etkiliyor mu?)
  • Bir OO arayüzü yok (bir tane oluşturmak birkaç saat meselesi)
  • Hazırlanmış İfadeleri veya Parametreli Sorguları desteklemez

Sonuncusu ilginç bir nokta. Mysql ext yerel olarak hazırlanmış ifadeleri desteklemese de, güvenlik için gerekli değildir. Manuel olarak işlenen yer tutucular kullanarak hazırlanmış ifadeleri kolayca taklit edebiliriz (tıpkı PDO'nun yaptığı gibi):

function paraQuery()
{
    $args = func_get_args(); $query = array_shift($args); $query = str_replace("%s","'%s'",$query); foreach ($args as $key => $val)
    {
        $args[$key] = mysql_real_escape_string($val); } $query  = vsprintf($query, $args);
    $result = mysql_query($query);
    if (!$result) { throw new Exception(mysql_error()." [$query]");
    }
    return $result; } $query  = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d";
$result = paraQuery($query, $a, "%$b%", $limit);

voila , her şey parametreleştirilmiş ve güvenlidir.

Ama tamam, kılavuzdaki kırmızı kutuyu beğenmezseniz, bir seçim problemi ortaya çıkar: mysqli veya PDO?

Cevap şu şekilde olacaktır:

  • Bir veritabanı soyutlama katmanı kullanmanın ve bir API oluşturmak için bir API aramanın gerekliliğini anlıyorsanız , mysqli gerçekten de mysql'e özgü birçok özelliği desteklediği için çok iyi bir seçimdir.
  • PHP çalışanlarının büyük çoğunluğu gibi, ham API çağrılarını doğrudan uygulama kodunda kullanıyorsanız (bu aslında yanlış bir uygulamadır) - PDO tek seçenektir , çünkü bu uzantı yalnızca API değil, yarı DAL gibi görünmektedir, hala eksiktir ancak birçok önemli özellik sunar, bunlardan ikisi PDO'yu mysqli'den kritik bir şekilde ayırır:

    • mysqli'den farklı olarak, PDO yer tutucuları değere göre bağlayabilir , bu da dinamik olarak oluşturulmuş sorguları oldukça karışık kod ekranları olmadan mümkün kılar.
    • mysqli'den farklı olarak, PDO her zaman basit bir olağan dizide sorgu sonucunu döndürebilirken, mysqli bunu yalnızca MySQL kurulumlarında yapabilir.

Dolayısıyla, ortalama bir PHP kullanıcısıysanız ve yerel olarak hazırlanmış ifadeleri kullanırken kendinize bir sürü baş ağrısından kurtulmak istiyorsanız, PDO - yine - tek seçenektir.
Bununla birlikte, PDO da sihirli bir değnek değildir ve zorlukları vardır.
Bu yüzden, PDO etiket wiki'sindeki tüm yaygın tuzaklar ve karmaşık durumlar için çözümler yazdım

Bununla birlikte, uzantılardan bahseden herkes Mysqli ve PDO hakkındaki 2 önemli gerçeği her zaman kaçırır:

  1. Hazırlanan açıklama sihirli bir değnek değil . Hazırlanmış ifadeler kullanılarak bağlanamayan dinamik tanımlayıcılar vardır. Bilinmeyen sayıda parametre içeren dinamik sorgular vardır ve bu da sorgu oluşturmayı zor bir görev haline getirir.

  2. Uygulama kodunda ne mysqli_ * ne de PDO işlevleri görünmemelidir. Bunlar ile uygulama kodu arasında, içerideki tüm kirli ciltleme, döngüleme, hata işleme vb. İşleri yapacak, uygulama kodunu KURU ve temiz hale getirecek
    bir soyutlama katmanı olmalıdır . Özellikle dinamik sorgu oluşturma gibi karmaşık durumlar için.

Yani sadece PDO veya mysqli'ye geçmek yeterli değil. Kodlarında ham API işlevlerini çağırmak yerine ORM veya sorgu oluşturucu veya herhangi bir veritabanı soyutlama sınıfı kullanmak gerekir.
Aksine - uygulama kodunuz ile mysql API arasında bir soyutlama katmanınız varsa - hangi motorun kullanıldığının aslında önemi yoktur. Kullanımdan kaldırılıncaya kadar mysql ext'i kullanabilir ve ardından tüm uygulama kodu bozulmadan soyutlama sınıfınızı başka bir motora kolayca yeniden yazabilirsiniz .

İşte böyle bir soyutlama sınıfının nasıl olması gerektiğini göstermek için safemysql sınıfıma dayalı bazı örnekler :

$city_ids = array(1,2,3);
$cities = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);

Bu tek satırı PDO ile ihtiyacınız olacak kod miktarı ile karşılaştırın .
Daha sonra ihtiyacınız olacak çılgın miktarda kod ile hazırlanmış ham Mysqli ifadeleriyle karşılaştırın. Hata işleme, profil oluşturma, sorgu günlüğünün zaten yerleşik ve çalışıyor olduğunu unutmayın.

$insert = array('name' => 'John', 'surname' => "O'Hara");
$db->query("INSERT INTO users SET ?u", $insert);

Her bir alan adının altı ila on kez tekrarlandığı alışılmış PDO ekleriyle karşılaştırın - tüm bu adlandırılmış yer tutucularda, bağlamalarda ve sorgu tanımlarında.

Başka bir örnek:

$data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);

PDO'nun bu kadar pratik bir durumu ele alması için bir örnek bulmanız çok zordur.
Ve çok uzun ve büyük olasılıkla güvensiz olacak.

Öyleyse, bir kez daha - endişeniz sadece ham sürücü değil, soyutlama sınıfı olmalıdır, sadece başlangıç ​​kılavuzundaki saçma örnekler için değil, aynı zamanda gerçek hayattaki sorunları çözmek için de yararlıdır.

98 Trott Oct 12 2012 at 20:23

Pek çok neden vardır, ancak belki de en önemlisi, bu işlevlerin, hazırlanmış ifadeleri desteklemedikleri için güvenli olmayan programlama uygulamalarını teşvik etmesidir. Hazırlanmış ifadeler, SQL enjeksiyon saldırılarını önlemeye yardımcı olur.

Fonksiyonları kullanırken mysql_*, kullanıcı tarafından sağlanan parametreleri çalıştırmayı unutmamalısınız mysql_real_escape_string(). Tek bir yerde unutursanız veya girdinin yalnızca bir kısmından kaçarsanız, veritabanınız saldırıya maruz kalabilir.

Hazırlanmış ifadelerin kullanılması PDOveya mysqlibu tür programlama hatalarının yapılmasının daha zor olmasını sağlar.

77 enhzflep Oct 12 2012 at 20:24

Çünkü (diğer nedenlerin yanı sıra) giriş verilerinin sterilize edilmesini sağlamak çok daha zordur. PDO veya mysqli'de olduğu gibi, parametreleştirilmiş sorgular kullanırsanız, riski tamamen önleyebilirsiniz.

Örnek olarak, birisi "enhzflep); drop table users"kullanıcı adı olarak kullanabilir . Eski işlevler, sorgu başına birden çok ifadenin yürütülmesine izin verir, bu nedenle bu iğrenç hata gibi bir şey tüm tabloyu silebilir.

Biri mysqli'nin PDO'sunu kullanacak olsaydı, kullanıcı adı olur "enhzflep); drop table users".

Bobby-tables.com adresine bakın .

66 Fluffeh Sep 18 2013 at 19:28

Bu cevap, kötü yazılmış PHP kullanıcı doğrulama kodunu atlamanın ne kadar önemsiz olduğunu, bu saldırıların nasıl (ve neyi kullanarak) çalıştığını ve eski MySQL işlevlerinin nasıl güvenli hazırlanmış bir ifadeyle nasıl değiştirileceğini - ve temelde neden StackOverflow kullanıcılarının olduğunu göstermek için yazılmıştır. (muhtemelen çok fazla temsilciyle) kodlarını geliştirmek için sorular soran yeni kullanıcılara havlıyor.

Öncelikle, lütfen bu test mysql veritabanını oluşturmaktan çekinmeyin (mayın hazırlığını aradım):

mysql> create table users(
    -> id int(2) primary key auto_increment,
    -> userid tinytext,
    -> pass tinytext);
Query OK, 0 rows affected (0.05 sec)

mysql> insert into users values(null, 'Fluffeh', 'mypass');
Query OK, 1 row affected (0.04 sec)

mysql> create user 'prepared'@'localhost' identified by 'example';
Query OK, 0 rows affected (0.01 sec)

mysql> grant all privileges on prep.* to 'prepared'@'localhost' with grant option;
Query OK, 0 rows affected (0.00 sec)

Bunu yaptıktan sonra PHP kodumuza geçebiliriz.

Aşağıdaki komut dosyasının bir web sitesindeki bir yönetici için doğrulama işlemi olduğunu varsayalım (basitleştirilmiştir, ancak test için kopyalayıp kullanırsanız çalışır):

<?php 

    if(!empty($_POST['user'])) { $user=$_POST['user']; } else { $user='bob';
    }
    if(!empty($_POST['pass'])) { $pass=$_POST['pass']; } else { $pass='bob';
    }

    $database='prep'; $link=mysql_connect('localhost', 'prepared', 'example');
    mysql_select_db($database) or die( "Unable to select database"); $sql="select id, userid, pass from users where userid='$user' and pass='$pass'";
    //echo $sql."<br><br>"; $result=mysql_query($sql); $isAdmin=false;
    while ($row = mysql_fetch_assoc($result)) {
        echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>"; $isAdmin=true;
        // We have correctly matched the Username and Password
        // Lets give this person full access
    }
    if($isAdmin) { echo "The check passed. We have a verified admin!<br>"; } else { echo "You could not be verified. Please try again...<br>"; } mysql_close($link);

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

İlk bakışta yeterince yasal görünüyor.

Kullanıcının bir kullanıcı adı ve şifre girmesi gerekiyor, değil mi?

Harika, aşağıdakileri girmeyin:

user: bob
pass: somePass

ve gönderin.

Çıktı aşağıdaki gibidir:

You could not be verified. Please try again...

Süper! Beklendiği gibi çalışıyoruz, şimdi gerçek kullanıcı adını ve şifreyi deneyelim:

user: Fluffeh
pass: mypass

İnanılmaz! Her yönden beşli, kod bir yöneticiyi doğru bir şekilde doğruladı. Mükemmel!

Aslında değil. Kullanıcı zeki, küçük bir insan diyelim. Diyelim ki kişi benim.

Aşağıdakileri girin:

user: bob
pass: n' or 1=1 or 'm=m

Ve çıktı:

The check passed. We have a verified admin!

Tebrikler, sadece süper korumalı yöneticilerinize yanlış bir kullanıcı adı ve yanlış şifre girerek girmeme izin verdiniz. Cidden, bana inanmıyorsanız, vermiş olduğum kodla veritabanını oluşturun ve bu PHP kodunu çalıştırın - bu bir bakışta GERÇEKTEN kullanıcı adını ve parolayı oldukça güzel bir şekilde doğruluyor gibi görünüyor.

Öyleyse, cevap olarak, NEDEN BAĞIRIYORSUNUZ.

Öyleyse, neyin yanlış gittiğine ve neden süper yönetici olan yarasa mağaranıza neden girdiğime bir bakalım. Bir tahminde bulundum ve girdilerinize dikkat etmediğinizi varsaydım ve bunları doğrudan veritabanına aktardım. Girdiyi, aslında çalıştırdığınız sorguyu DEĞİŞTİRECEK şekilde oluşturdum. Öyleyse, ne olması gerekiyordu ve neye dönüştü?

select id, userid, pass from users where userid='$user' and pass='$pass'

Sorgu bu, ancak değişkenleri kullandığımız gerçek girdilerle değiştirdiğimizde aşağıdakileri elde ederiz:

select id, userid, pass from users where userid='bob' and pass='n' or 1=1 or 'm=m'

"Parolamı", önce parolanın etrafındaki tek alıntıyı kapatacak ve ardından tamamen yeni bir karşılaştırma sunacak şekilde nasıl oluşturduğumu görün? Sonra sadece güvenlik için, tek bir alıntı başlangıçta sahip olduğumuz kodda beklendiği gibi kapanacak şekilde başka bir "dizge" ekledim.

Ancak, bu şimdi size bağıran insanlarla ilgili değil, bu size kodunuzu nasıl daha güvenli hale getireceğinizi göstermekle ilgili.

Tamam, peki ne ters gitti ve bunu nasıl düzeltebiliriz?

Bu klasik bir SQL enjeksiyon saldırısıdır. Bu konuda en basitlerinden biri. Saldırı vektörleri ölçeğinde, bu, bir tanka saldıran ve kazanan bir küçük çocuktur.

Öyleyse, kutsal yönetici bölümünüzü nasıl koruyacağız ve onu güzel ve güvenli hale getireceğiz? Yapılacak ilk şey, gerçekten eski ve artık kullanılmayan mysql_*işlevleri kullanmayı bırakmak olacaktır . Biliyorum, çevrimiçi bulduğunuz bir öğreticiyi takip ettiniz ve işe yarıyor, ancak eski, modası geçmiş ve birkaç dakika içinde, ter dökmeden geçtim.

Artık mysqli_ veya PDO kullanma konusunda daha iyi seçeneklere sahipsiniz . Ben şahsen PDO'nun büyük bir hayranıyım, bu yüzden bu cevabın geri kalanında PDO kullanacağım. Profesyoneller ve eksiler var, ama şahsen, profesyonelin aleyhte olanlardan çok daha ağır bastığını görüyorum. Birden çok veritabanı motorunda taşınabilir - ister MySQL veya Oracle kullanıyor olun, ister kanlı herhangi bir şey - sadece bağlantı dizesini değiştirerek, kullanmak istediğimiz tüm süslü özelliklere sahip ve güzel ve temiz. Temizliği severim.

Şimdi, bu koda bir daha bakalım, bu sefer bir PDO nesnesi kullanılarak yazılmıştır:

<?php 

    if(!empty($_POST['user'])) { $user=$_POST['user']; } else { $user='bob';
    }
    if(!empty($_POST['pass'])) { $pass=$_POST['pass']; } else { $pass='bob';
    }
    $isAdmin=false; $database='prep';
    $pdo=new PDO ('mysql:host=localhost;dbname=prep', 'prepared', 'example'); $sql="select id, userid, pass from users where userid=:user and pass=:password";
    $myPDO = $pdo->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); if($myPDO->execute(array(':user' => $user, ':password' => $pass)))
    {
        while($row=$myPDO->fetch(PDO::FETCH_ASSOC))
        {
            echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>"; $isAdmin=true;
            // We have correctly matched the Username and Password
            // Lets give this person full access
        }
    }

    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

En büyük farklar, daha fazla mysql_*işlevin olmamasıdır . Bunların hepsi bir PDO nesnesi aracılığıyla yapılır, ikinci olarak, hazırlanmış bir ifade kullanılarak yapılır. Şimdi, sorduğunuz önceden hazırlanmış bir ifade nedir? Veritabanına bir sorgu çalıştırmadan önce, hangi sorguyu çalıştıracağımızı söylemenin bir yoludur. Bu durumda veritabanına "Merhaba, kullanıcı kimliğinin bir değişken ve geçişin de bir değişken olduğu tablodan id, userid ve pass isteyen bir select deyimi çalıştıracağım."

Daha sonra execute ifadesinde, veritabanına şimdi beklediği tüm değişkenleri içeren bir dizi geçiririz.

Sonuçlar harika. Bu kullanıcı adı ve şifre kombinasyonlarını daha önce tekrar deneyelim:

user: bob
pass: somePass

Kullanıcı doğrulanmadı. Harika.

Peki ya:

user: Fluffeh
pass: mypass

Oh, biraz heyecanlandım, işe yaradı: Çek geçti. Doğrulanmış bir yöneticimiz var!

Şimdi, akıllı bir adamın küçük doğrulama sistemimizi geçmeye çalıştığı verileri deneyelim:

user: bob
pass: n' or 1=1 or 'm=m

Bu sefer aşağıdakileri alıyoruz:

You could not be verified. Please try again...

Bu yüzden soru gönderirken size bağırıyorsunuz - çünkü insanlar kodunuzun denemeden bile atlanabileceğini görebiliyor. Lütfen, kodunuzu geliştirmek, daha güvenli hale getirmek ve güncel işlevleri kullanmak için bu soruyu ve cevabı kullanın.

Son olarak, bu MÜKEMMEL kod olduğu anlamına gelmez. İyileştirmek için yapabileceğiniz daha pek çok şey var, örneğin karma şifreler kullanın, hassas bilgileri veri tabanında sakladığınızda, bunları düz metin olarak saklamadığınızdan, birden çok doğrulama düzeyine sahip olmadığınızdan emin olun - ama gerçekten, eğer sadece eski enjeksiyona yatkın kodunuzu buna değiştirirseniz, iyi kod yazma yolunda İYİ olacaksınız - ve bu kadar ileri gitmiş olmanız ve hala okuyor olmanız bana sadece bu türü uygulamayacağınıza dair bir umut duygusu veriyor web sitelerinizi ve uygulamalarınızı yazarken kod yazmak, ancak dışarı çıkıp az önce bahsettiğim diğer şeyleri ve daha fazlasını araştırabilirsiniz. Yapabileceğiniz en iyi kodu yazın, zar zor işleyen en temel kodu değil.

34 Alexander Sep 02 2015 at 14:20

MySQL uzantısı, üçünün en eskisidir ve geliştiricilerin MySQL ile iletişim kurmak için kullandıkları orijinal yoldur. Bu uzantı, hem PHP hem de MySQL'in daha yeni sürümlerinde yapılan iyileştirmeler nedeniyle artık diğer iki alternatifin lehine kullanımdan kaldırılıyor .

  • MySQLi , MySQL veritabanları ile çalışmak için 'geliştirilmiş' uzantıdır. MySQL sunucusunun daha yeni sürümlerinde bulunan özelliklerden yararlanır, geliştiriciye hem işlev yönelimli hem de nesne yönelimli bir arabirim sunar ve birkaç başka güzel şey yapar.

  • PDO , daha önce MySQL, PostgreSQL, SQLite, MSSQL gibi başlıca veritabanı erişim uzantılarına yayılmış olan işlevselliğin çoğunu birleştiren bir API sunar. Arayüz, programcının veritabanı bağlantıları, sorgular ve sonuç kümeleri ve düşük seviyeli sürücüler, veritabanı sunucusuyla iletişimi ve kaynak işlemeyi gerçekleştirir. PDO'da pek çok tartışma ve çalışma var ve bu, modern, profesyonel kodlu veritabanlarıyla çalışmanın uygun yöntemi olarak kabul ediliyor.

22 AniMenon Sep 07 2016 at 22:06

Yukarıdaki cevapları gerçekten uzun buluyorum, bu yüzden özetlemek gerekirse:

Mysqli uzantısının bir dizi avantajı vardır, mysql uzantısına göre temel geliştirmeler şunlardır:

  • Nesne yönelimli arayüz
  • Hazırlanmış İfadeler için Destek
  • Birden Çok İfade Desteği
  • İşlem Desteği
  • Gelişmiş hata ayıklama yetenekleri
  • Gömülü sunucu desteği

Kaynak: MySQLi'ye genel bakış


Yukarıdaki cevaplarda açıklandığı gibi, mysql'e alternatifler mysqli ve PDO'dur (PHP Data Objects).

  • API, sunucu tarafında Hazırlanmış İfadeleri destekler: MYSQLi ve PDO tarafından desteklenir
  • API, istemci tarafında Hazırlanmış İfadeleri destekler: Yalnızca PDO tarafından desteklenir
  • API, Depolanan Prosedürleri destekler: Hem MySQLi hem de PDO
  • API, Çoklu İfadeleri ve tüm MySQL 4.1+ işlevlerini destekler - MySQLi tarafından ve çoğunlukla PDO tarafından desteklenir

Hem MySQLi hem de PDO PHP 5.0'da, MySQL ise PHP 3.0'dan önce tanıtıldı. Dikkat edilmesi gereken bir nokta, MySQL'in PHP5.x'e dahil edilmesine rağmen sonraki sürümlerde kullanımdan kaldırılmış olmasıdır.

7 PavelTzonkov Jun 09 2017 at 13:24

Hemen hemen tüm mysql_*fonksiyonları mysqli veya PDO kullanarak tanımlamak mümkündür . Bunları eski PHP uygulamanızın üzerine ekleyin ve PHP7 üzerinde çalışacaktır. Benim çözümüm burada .

<?php

define('MYSQL_LINK', 'dbl');
$GLOBALS[MYSQL_LINK] = null; function mysql_link($link=null) {
    return ($link === null) ? $GLOBALS[MYSQL_LINK] : $link; } function mysql_connect($host, $user, $pass) {
    $GLOBALS[MYSQL_LINK] = mysqli_connect($host, $user, $pass);
    return $GLOBALS[MYSQL_LINK]; } function mysql_pconnect($host, $user, $pass) {
    return mysql_connect($host, $user, $pass); } function mysql_select_db($db, $link=null) { $link = mysql_link($link); return mysqli_select_db($link, $db); } function mysql_close($link=null) {
    $link = mysql_link($link);
    return mysqli_close($link); } function mysql_error($link=null) {
    $link = mysql_link($link);
    return mysqli_error($link); } function mysql_errno($link=null) {
    $link = mysql_link($link);
    return mysqli_errno($link); } function mysql_ping($link=null) {
    $link = mysql_link($link);
    return mysqli_ping($link); } function mysql_stat($link=null) {
    $link = mysql_link($link);
    return mysqli_stat($link); } function mysql_affected_rows($link=null) {
    $link = mysql_link($link);
    return mysqli_affected_rows($link); } function mysql_client_encoding($link=null) {
    $link = mysql_link($link);
    return mysqli_character_set_name($link); } function mysql_thread_id($link=null) {
    $link = mysql_link($link);
    return mysqli_thread_id($link); } function mysql_escape_string($string) {
    return mysql_real_escape_string($string); } function mysql_real_escape_string($string, $link=null) { $link = mysql_link($link); return mysqli_real_escape_string($link, $string); } function mysql_query($sql, $link=null) { $link = mysql_link($link); return mysqli_query($link, $sql); } function mysql_unbuffered_query($sql, $link=null) { $link = mysql_link($link); return mysqli_query($link, $sql, MYSQLI_USE_RESULT); } function mysql_set_charset($charset, $link=null){ $link = mysql_link($link); return mysqli_set_charset($link, $charset); } function mysql_get_host_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_host_info($link); } function mysql_get_proto_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_proto_info($link); } function mysql_get_server_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_server_info($link); } function mysql_info($link=null) {
    $link = mysql_link($link);
    return mysqli_info($link); } function mysql_get_client_info() { $link = mysql_link();
    return mysqli_get_client_info($link); } function mysql_create_db($db, $link=null) { $link = mysql_link($link); $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "CREATE DATABASE `$db`");
}

function mysql_drop_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db)); return mysqli_query($link, "DROP DATABASE `$db`"); } function mysql_list_dbs($link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, "SHOW DATABASES"); } function mysql_list_fields($db, $table, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db)); $table = str_replace('`', '', mysqli_real_escape_string($link, $table));
    return mysqli_query($link, "SHOW COLUMNS FROM `$db`.`$table`"); } function mysql_list_tables($db, $link=null) { $link = mysql_link($link); $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "SHOW TABLES FROM `$db`");
}

function mysql_db_query($db, $sql, $link=null) { $link = mysql_link($link); mysqli_select_db($link, $db); return mysqli_query($link, $sql); } function mysql_fetch_row($qlink) {
    return mysqli_fetch_row($qlink); } function mysql_fetch_assoc($qlink) {
    return mysqli_fetch_assoc($qlink); } function mysql_fetch_array($qlink, $result=MYSQLI_BOTH) { return mysqli_fetch_array($qlink, $result); } function mysql_fetch_lengths($qlink) {
    return mysqli_fetch_lengths($qlink); } function mysql_insert_id($qlink) {
    return mysqli_insert_id($qlink); } function mysql_num_rows($qlink) {
    return mysqli_num_rows($qlink); } function mysql_num_fields($qlink) {
    return mysqli_num_fields($qlink); } function mysql_data_seek($qlink, $row) { return mysqli_data_seek($qlink, $row); } function mysql_field_seek($qlink, $offset) { return mysqli_field_seek($qlink, $offset); } function mysql_fetch_object($qlink, $class="stdClass", array $params=null) {
    return ($params === null) ? mysqli_fetch_object($qlink, $class) : mysqli_fetch_object($qlink, $class, $params);
}

function mysql_db_name($qlink, $row, $field='Database') { mysqli_data_seek($qlink, $row); $db = mysqli_fetch_assoc($qlink); return $db[$field]; } function mysql_fetch_field($qlink, $offset=null) { if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    return mysqli_fetch_field($qlink); } function mysql_result($qlink, $offset, $field=0) {
    if ($offset !== null) mysqli_field_seek($qlink, $offset); $row = mysqli_fetch_array($qlink); return (!is_array($row) || !isset($row[$field]))
        ? false
        : $row[$field];
}

function mysql_field_len($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset); return is_object($field) ? $field->length : false; } function mysql_field_name($qlink, $offset) { $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field)) return false; return empty($field->orgname) ? $field->name : $field->orgname;
}

function mysql_field_table($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset); if (!is_object($field))
        return false;
    return empty($field->orgtable) ? $field->table : $field->orgtable; } function mysql_field_type($qlink, $offset) { $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->type : false;
}

function mysql_free_result($qlink) { try { mysqli_free_result($qlink);
    } catch (Exception $e) {
        return false;
    }
    return true;
}