PDO hazırlanmış ifadeler SQL enjeksiyonunu önlemek için yeterli mi?


661

Diyelim ki böyle bir kod var:

$dbh = new PDO("blahblah");

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

PDO belgeleri şunları söylüyor:

Hazırlanan ifadelerin parametrelerinin alıntılanması gerekmez; sürücü sizin için halleder.

SQL enjeksiyonlarından kaçınmak için tek yapmam gereken bu mu? Gerçekten bu kadar kolay mı?

Bir fark yaratırsa MySQL olduğunu varsayabilirsiniz. Ayrıca, sadece SQL enjeksiyonuna karşı hazırlanmış ifadelerin kullanımını merak ediyorum. Bu bağlamda, XSS veya diğer olası güvenlik açıklarını umursamıyorum.


5
daha iyi yaklaşım 7. numara yanıt stackoverflow.com/questions/134099/…
NullPoiиteя

Yanıtlar:


807

Kısa cevap HAYIR , PDO hazırlar sizi tüm olası SQL-Enjeksiyon saldırılarına karşı savunmayacaktır. Belirli belirsiz kenar durumlar için.

Bu cevabı PDO hakkında konuşmak için uyarlıyorum ...

Uzun cevap o kadar kolay değil. Burada gösterilen bir saldırıya dayanıyor .

Saldırı

Öyleyse, saldırıyı göstererek başlayalım ...

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

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

  1. Karakter Kümesi Seçme

    $pdo->query('SET NAMES gbk');

    İşe bu saldırı için, sunucunun hem kodlamak için bağlantıda bekliyor o kodlamayı ihtiyaç 'ASCII ie olarak 0x27 ve kimin nihai bayt bir ASCII bazı karakter var \yani 0x5c. Sonradan anlaşıldı ki, varsayılan olarak MySQL 5.6 desteklenen 5 tür kodlamalar vardır: big5, cp932, gb2312, gbkve sjis. Burada seçeceğiz gbk.

    Şimdi, SET NAMESburada kullanımını not etmek çok önemlidir . Bu, SUNUCU ÜZERİNDEKİ karakter setini ayarlar . Bunu yapmanın başka bir yolu var, ama yakında oraya ulaşacağız.

  2. Yük

    Bu enjeksiyon için kullanacağımız yük, bayt dizisi ile başlar 0xbf27. Bu gbkgeçersiz bir çokbaytlı karakterdir; içinde latin1, bu dize ¿'. İçinde olduğunu unutmayın latin1 ve gbk , 0x27kendi hazır bilgi üzerine 'karakteri.

    Bu yükü seçtik, çünkü eğer çağırırsak, addslashes()ASCII'yi \yani karakterden 0x5cönce 'eklerdik. Bu yüzden iki karakter dizisi 0xbf5c27olan bir rüzgârla karşılaşırız gbk: 0xbf5carkasından 0x27. Ya da başka bir deyişle, geçerli bir karakter ve ardından çıkış karakteri yok '. Ama kullanmıyoruz addslashes(). Bir sonraki adıma geçelim ...

  3. $ Stmt->) (yürütmek

    Burada gerçekleştirmek için önemli olan yok varsayılan olarak o PDO olduğu DEĞİL gerçek hazırlanmış deyimleri yapmak. Onlara öykünür (MySQL için). Bu nedenle, PDO dahili olarak sorgu dizesini oluşturur ve mysql_real_escape_string()her bağlı dize değerinde (MySQL C API işlevi) çağırır .

    C API çağrısı , bağlantı karakter kümesini bildiğinden mysql_real_escape_string()farklıdır addslashes(). Böylece sunucunun beklediği karakter kümesi için kaçmayı düzgün bir şekilde gerçekleştirebilir. Ancak, bu noktaya kadar, müşteri hala latin1bağlantı için kullandığımızı düşünüyor , çünkü bunu asla aksini söylemedik. Biz söyledin sunucu Kullandığımız gbkancak istemci hala sanıyor latin1.

    Bu nedenle mysql_real_escape_string()ters eğik çizgi ekleme çağrısı ve '"kaçan" içeriğimizde serbest asılı bir karakterimiz var ! Biz bakmak için olsaydı Aslında, $variçinde gbkkarakter kümesi, biz görürdük:

    OR 'VEYA 1 = 1 / *

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

  4. Sorgu

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

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

Tebrikler, PDO Hazırlanan Deyimler kullanarak bir programa başarıyla saldırdınız ...

Basit Düzeltme

Şimdi, taklit hazırlanmış ifadeleri devre dışı bırakarak bunu önleyebileceğinizi belirtmek gerekir:

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

Bu genellikle doğru hazırlanmış bir bildirimle sonuçlanır (yani veriler sorgudan ayrı bir pakette gönderilir). Ancak, PDO sessizce olacağını unutmayın çare o edilmektedir edebilirsiniz olanlar bu: ifadeleri taklit etmek MySQL doğal olarak hazırlamak olamayacağını listelenen kılavuzda ama) uygun sunucu sürümünü seçmek için dikkatli olun.

Doğru Düzeltme

Buradaki sorun, mysql_set_charset()bunun yerine C API'lerini çağırmadık SET NAMES. Eğer yapsaydık, 2006'dan beri bir MySQL sürümü kullanmamız koşuluyla iyi olur.

Daha eski bir MySQL salınımını, ardından kullanıyorsanız hata içinde mysql_real_escape_string()böyle bizim yükü içinde olanlar gibi geçersiz baytlı karakterler amaçları kaçmak için tek bayt olarak tedavi edildi anlamına geliyordu istemci doğru bağlantı kodlama haberdar olmuştu bile bu yüzden bu saldırı olur ve hala başarılı. Hata MySQL giderilmiştir 4.1.20 , 5.0.22 ve 5.1.11 .

Ama en kötüsü olduğunu PDOC API maruz vermedi mysql_set_charset()böylece önceki sürümlerinde bu 5.3.6 kadar olamaz , mümkün olan her komut için bu saldırıyı önlemek! Şimdi bunun yerine kullanılması gereken bir DSN parametresi olarak ortaya çıkar ... SET NAMES

Kurtarıcı Lütuf

Başlangıçta söylediğimiz gibi, bu saldırının çalışması için veritabanı bağlantısı savunmasız bir karakter seti kullanılarak kodlanmalıdır. utf8mb4olduğunu savunmasız değil destekleyebilir henüz ve her MySQL 5.5.3 beri yerine-ama sadece olmuştur kullanılabilir kullanmayı seçebilir böylece: Unicode karakter. Bir alternatif utf8de savunmasız olmayan ve Unicode Temel Çok Dilli Düzlem'in tamamını destekleyebiliyor .

Alternatif olarak, NO_BACKSLASH_ESCAPES(diğer şeylerin yanı sıra) çalışmasını değiştiren SQL modunu etkinleştirebilirsiniz mysql_real_escape_string(). Bu mod etkinleştirildiğinde, 0x27değiştirilecek 0x2727ziyade 0x5c27kaçan süreci dolayısıyla ve olamaz , daha önce var olmayan savunmasız kodlamalarla geçerli karakterleri oluşturmak (yani 0xbf27hala 0xbf27vb.) - Sunucu yüzden hala geçersiz olarak dize reddedecektir . Ancak, @ eggyal'in bu SQL modundan (PDO ile olmasa da) kaynaklanabilecek farklı bir güvenlik açığı konusundaki cevabına bakınız .

Güvenli Örnekler

Aşağıdaki örnekler güvenlidir:

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

Sunucu bekliyor çünkü utf8...

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

Çünkü karakter setini istemci ve sunucu eşleşecek şekilde doğru şekilde ayarladık.

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

Çünkü taklit hazırlanmış ifadeleri kapattık.

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Çünkü karakter setini doğru ayarladık.

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

Çünkü MySQLi her zaman doğru hazırlanmış ifadeler yapar.

Paketleme

Eğer sen:

  • MySQL'in Modern Sürümlerini (geç 5.1, tümü 5.5, 5.6 vb.) VE PDO'nun DSN karakter parametresini (PHP ≥ 5.3.6'da) kullanın

VEYA

  • Bağlantı kodlaması için savunmasız bir karakter seti kullanmayın (yalnızca utf8/ latin1/ ascii/ etc kullanıyorsunuz)

VEYA

  • NO_BACKSLASH_ESCAPESSQL modunu etkinleştir

% 100 güvendesin.

Aksi takdirde, PDO Hazırlık Bildirimleri kullanıyor olsanız bile savunmasızsınız ...

ek

Yavaş yavaş PHP gelecekteki bir sürümü için hazırlar taklit değil varsayılan değiştirmek için bir yama üzerinde çalışıyorum. Benim karşılaştığım sorun, bunu yaptığımda bir sürü test kırılması. Bir sorun öykünmüş hazırlanmaların sadece yürütme sırasında sözdizimi hatalarını atmasıdır, ancak gerçek hazırlıklar hazırlanma sırasında hatalar atar. Böylece bu sorunlara neden olabilir (ve testlerin canlanmasının nedeninin bir parçasıdır).


47
Bu bulduğum en iyi cevap .. daha fazla referans için bir bağlantı sağlayabilir misiniz?
StaticVariable

1
@nicogawenda: bu farklı bir hataydı. 5.0.22'den önce mysql_real_escape_string, bağlantının düzgün şekilde BIG5 / GBK olarak ayarlandığı durumları düzgün işlemezdi. Yani aslında mysql_set_charset()mysql <5.0.22 çağırmak bile bu hataya karşı savunmasız olurdu! Yani hayır, bu yazı 5.0.22 için hala geçerlidir (çünkü mysql_real_escape_string sadece gelen çağrılara charset uzaktır mysql_set_charset(), bu da bu
yazının

1
@progfa İster kullanıcı verileriyle herhangi bir şey yapmadan önce sunucudaki girişinizi her zaman doğrulamanız gerekir.
Tek

2
Lütfen NO_BACKSLASH_ESCAPESyeni güvenlik açıkları da getirebileceğini unutmayın : stackoverflow.com/a/23277864/1014813
lepix

2
@slevin "OR 1 = 1" istediğiniz her şey için bir yer tutucudur. Evet, adında bir değer arıyor, ancak "VEYA 1 = 1" bölümünün "UNION SELECT * kullanıcılardan" olduğunu hayal edin. Şimdi sorguyu kontrol edersiniz ve bu nedenle kötüye kullanabilirsiniz ...
ircmaxell

515

Hazırlanan ifadeler / parametreli sorgular genellikle söz konusu ifadeye 1. dereceden enjeksiyon yapılmasını önlemek için yeterlidir * . Kontrolsüz dinamik sql'ı uygulamanızın başka herhangi bir yerinde kullanıyorsanız, yine de 2. dereceden enjeksiyona karşı savunmasızsınız .

2. dereceden enjeksiyon, verilerin bir sorguya dahil edilmeden önce veritabanından bir kez çevrildiği ve çıkarılmasının çok daha zor olduğu anlamına gelir. AFAIK, neredeyse hiçbir zaman gerçek olarak tasarlanmış 2. derece saldırıları görmezsiniz, çünkü saldırganların girişlerini sosyal mühendisliği yapmak daha kolaydır, ancak bazen ekstra iyi huylu 'karakterler veya benzeri nedeniyle 2. sıra hatalar ortaya çıkar .

Bir değerin daha sonra bir sorguda değişmez değer olarak kullanılan bir veritabanında depolanmasına neden olabileceğinizde, 2. dereceden enjeksiyon saldırısı gerçekleştirebilirsiniz. Örnek olarak, bir web sitesinde hesap oluştururken aşağıdaki bilgileri yeni kullanıcı adınız olarak girdiğinizi varsayalım (bu soru için MySQL DB varsayarak):

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

Kullanıcı adında başka bir kısıtlama yoksa, hazırlanmış bir ifade yine de yukarıdaki gömülü sorgunun ekleme sırasında yürütülmediğinden emin olur ve değeri veritabanında doğru bir şekilde saklar. Ancak, uygulamanın daha sonra kullanıcı adınızı veritabanından aldığını ve bu değere yeni bir sorgu eklemek için dize birleştirme kullandığını düşünün. Başkasının şifresini görebilirsiniz. Kullanıcılar tablosundaki ilk birkaç isim yönetici olma eğilimi gösterdiğinden, çiftliği de vermiş olabilirsiniz. (Ayrıca not: bu, şifreleri düz metin olarak saklamamanın bir nedenidir!)

Ardından, hazırlanan ifadelerin tek bir sorgu için yeterli olduğunu görüyoruz, ancak kendileri güvenli bir şekilde kullanan bir veritabanına tüm erişimi zorlayacak bir mekanizmaya sahip olmadıkları için, tüm uygulama boyunca sql enjeksiyon saldırılarına karşı koruma için yeterli değildirler. kodu. Ancak, iyi uygulama tasarımının bir parçası olarak kullanılan - - limitleri dinamik sql böyle kod inceleme veya statik analizi veya bir ORM kullanımıyla, veri katmanı veya servis katmanı olarak uygulamaları içerebilir hazırlanan ifadeleri vardır SQL Enjeksiyon çözümü için birincil aracı sorun.Veri erişiminiz programınızın geri kalanından ayrılacak şekilde iyi uygulama tasarım ilkelerine uyarsanız, her sorgunun parametrelendirmeyi doğru şekilde kullandığını zorlamak veya denetlemek kolaylaşır. Bu durumda, sql enjeksiyonu (hem birinci hem de ikinci derece) tamamen önlenir.


* MySql / PHP'nin geniş karakterler söz konusu olduğunda parametreleri ele alma konusunda sadece aptal olduğu ortaya çıkıyor ve buradaki en yüksek oy alan cevapta enjeksiyonun parametreli bir şekilde kaymasına izin verebilecek hala nadir bir durum var sorgu.


6
İlginç. 1. sipariş vs 2. sipariş farkında değildi. 2. derecenin nasıl çalıştığı hakkında biraz daha ayrıntı verebilir misiniz?
Mark Biek

193
TÜM sorgularınız parametrelendirilirse, 2. sıra enjeksiyonlara karşı da korunursunuz. 1. sıra enjeksiyon kullanıcı verilerinin güvenilir olmadığını unutuyor. 2. dereceden enjeksiyon, veritabanı verilerinin güvenilmez olduğunu unutmaktadır (çünkü kullanıcıdan orijinal olarak gelmiştir).
cjm

6
Teşekkürler cjm. Ayrıca bu makaleyi 2. sipariş enjeksiyonlarını açıklamakta yararlı buldum: codeproject.com/KB/database/SqlInjectionAttacks.aspx
Mark

49
Ah evet. Peki ya üçüncü dereceden enjeksiyon . Bunların farkında olmak zorunda.
troelskn

81
@troelskn bu geliştirici güvenilmez veri kaynağı nerede olmalıdır
MikeMurko

45

Hayır, her zaman değil.

Kullanıcı girişinin sorgunun içine yerleştirilmesine izin verip vermediğinize bağlıdır. Örneğin:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];

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

SQL girişlerine karşı savunmasızdır ve bu örnekte hazırlanan ifadelerin kullanılması çalışmaz, çünkü kullanıcı girişi veri olarak tanımlayıcı olarak kullanılır. Burada doğru cevap aşağıdaki gibi bir tür filtreleme / doğrulama kullanmak olacaktır:

$dbh = new PDO("blahblah");

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

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

Not: PDL'yi DDL (Veri Tanımlama Dili) dışında kalan verileri bağlamak için kullanamazsınız, yani bu çalışmaz:

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

Çünkü yukarıda iştir gelmez sebebi DESCve ASColmayan veriler . PDO yalnızca veri için kaçabilir . İkincisi, 'onun içine tırnak bile koyamazsınız . Kullanıcı tarafından seçilen sıralamaya izin vermenin tek yolu, manuel olarak filtrelemek ve ya DESCda olup olmadığını kontrol etmektir ASC.


11
Burada bir şey eksik mi ama sql bir dize gibi tedavi önlemek için hazırlanan ifadelerin bütün mesele değil mi? $ Dbh-> prepar ('SELECT * FROM: tableToU kullaniciadi =: kullaniciadi'); problemin üstesinden gelmek mi?
Rob Forrest

4
@RobForrest evet eksik :). Bağladığınız veriler yalnızca DDL (Veri Tanımlama Dili) için çalışır. Bu alıntılara ve düzgün kaçmaya ihtiyacınız var. Sorgunun diğer bölümleri için tırnak işareti koymak, yüksek olasılıkla keser. Örneğin, SELECT * FROM 'table'olması gerektiği gibi SELECT * FROM `table`veya herhangi bir ters çubuk olmadan yanlış olabilir . Sonra kullanıcıdan ORDER BY DESCnereden DESCgeliyor gibi bazı şeyler kaçamaz. Dolayısıyla, pratik senaryolar oldukça sınırsızdır.
Kule

8
Acaba 6 kişinin hazırlanan bir açıklamanın yanlış kullanılmasını öneren bir yorumu nasıl değerlendirebileceğini merak ediyorum. Bir kez bile deneselerdi, bir tablo adı yerine adlandırılmış parametrenin kullanılmasının işe yaramayacağını hemen keşfederlerdi.
Félix Gagnon-Grenier

Öğrenmek istiyorsanız PDO hakkında harika bir eğitim. a2znotes.blogspot.in/2014/09/introduction-to-pdo.html
RN Kushwaha

11
Kullanılacak tabloyu seçmek için hiçbir zaman bir sorgu dizesi / POST gövdesi kullanmamalısınız. Modelleriniz yoksa switch, tablo adını türetmek için en azından a kullanın .
ZiggyTheHamster

29

Evet, yeter. Enjeksiyon tipi saldırıların çalışma şekli, bir şekilde, bir kodun olduğu gibi veri olması gereken bir şeyi değerlendirmek için bir tercüman (veritabanı) almaktır. Bu, yalnızca aynı ortamda kod ve verileri karıştırırsanız mümkündür (örneğin, bir sorguyu dize olarak oluşturduğunuzda).

Parametrelenmiş sorgular, kodu ve verileri ayrı ayrı göndererek çalışır, bu nedenle içinde bir delik bulmak asla mümkün olmaz .

Yine de, diğer enjeksiyon tipi saldırılara karşı savunmasız olabilirsiniz. Örneğin, verileri bir HTML sayfasında kullanırsanız, XSS türü saldırılara maruz kalabilirsiniz.


10
İse "Hiçbir zaman" yolu overstating yanıltıcı olma noktasına. Hazırlanan ifadeleri yanlış kullanıyorsanız, bunları hiç kullanmamaktan daha iyi değildir. (Tabii ki, içine kullanıcı girişi enjekte edilmiş olan bir "hazırlanmış ifade" amacı yendi ... ama aslında bunu gördüm. Ve hazırlanan ifadeler parametre olarak tanımlayıcıları (tablo adları vb) işleyemez.) Ekle buna göre, bazı PDO sürücüleri hazırlanmış ifadeleri taklit ederler ve yanlış yapmaları için yer vardır (örneğin, SQL'i yarı zamanlı olarak ayrıştırarak). Kısa versiyon: asla bu kadar kolay olduğunu varsaymayın.
cHao

29

Hayır, bu yeterli değildir (bazı özel durumlarda)! Varsayılan olarak PDO, MySQL'i veritabanı sürücüsü olarak kullanırken öykünülmüş hazırlanmış ifadeler kullanır. MySQL ve PDO kullanırken öykünmeli hazırlanmış ifadeleri her zaman devre dışı bırakmalısınız:

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

Her zaman yapılması gereken başka bir şey, veritabanının doğru kodlamasını ayarlar:

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

Ayrıca şu ilgili soruya bakın: PHP'de SQL enjeksiyonunu nasıl önleyebilirim?

Ayrıca, yalnızca verileri görüntülerken kendinizi izlemeniz gereken şeylerin veritabanı tarafı ile ilgili olduğunu unutmayın. Örneğin htmlspecialchars()tekrar doğru kodlama ve alıntılama stiliyle kullanmak.


14

Şahsen ben kullanıcı girişine asla güvenemeyeceğiniz için önce veriler üzerinde her zaman bir tür sanitasyon çalıştırırdım, ancak yer tutucuları / parametre bağlamayı kullanırken girilen veriler sunucuya ayrı ayrı sql deyimine gönderilir ve sonra birbirine bağlanır. Buradaki anahtar, sağlanan verileri belirli bir türe ve belirli bir kullanıma bağlaması ve SQL ifadesinin mantığını değiştirme fırsatını ortadan kaldırmasıdır.


1

Html veya js kontrollerini kullanarak sql enjeksiyon ön ucunu önleyecekseniz, ön uç kontrollerinin "atlanabilir" olduğunu düşünmeniz gerekir.

Ön uç geliştirme aracıyla js'yi devre dışı bırakabilir veya bir deseni düzenleyebilirsiniz (günümüzde firefox veya chrome ile yerleşiktir).

Bu nedenle, SQL enjeksiyonunu önlemek için, denetleyicinizin içindeki giriş tarihi arka ucunu sterilize etmek doğru olacaktır.

Size GET ve INPUT değerlerini sterilize etmek için filter_input () yerel PHP işlevini kullanmanızı öneririm.

Güvenlikle devam etmek istiyorsanız, mantıklı veritabanı sorguları için, veri biçimini doğrulamak için normal ifadeyi kullanmanızı öneririm. preg_match () bu durumda size yardımcı olacaktır! Ama kendine iyi bak! Regex motoru çok hafif değil. Sadece gerektiğinde kullanın, aksi takdirde uygulama performanslarınız düşecektir.

Güvenliğin bir maliyeti vardır, ancak performansınızı boşa harcamayın!

Kolay örnek:

GET'ten alınan bir değerin bir sayı olup olmadığını iki kez kontrol etmek istiyorsanız, (! preg_match ('/ [0-9] {1,2} /')) {...}

if (isset($value) && intval($value)) <99) {...}

Yani, son cevap: "Hayır! PDO Hazırlanan İfadeler her türlü sql enjeksiyonunu engellemez"; Beklenmedik değerleri engellemez, sadece beklenmedik birleştirme


5
SQL enjeksiyonunu, cevabınızı tamamen alakasız hale getiren başka bir şeyle karıştırıyorsunuz
Common Sense
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.