Neden sadece parametrelenmemiş sorgular bir hata döndürmüyor?


22

SQL enjeksiyonu çok ciddi bir güvenlik sorunudur, çünkü büyük ölçüde yanlış anlamak çok kolaydır: Kullanıcı girişi içeren bir sorgu oluşturmanın bariz ve sezgisel yolu sizi savunmasız bırakır ve bunu azaltmanın Doğru Yolu parametreleştirilmiş olarak bilmenizi gerektirir önce sorgular ve SQL enjeksiyonu.

Bana göre bunu düzeltmenin açık yolu bariz (ama yanlış) seçeneğini kapatmak olacaktır: veritabanı motorunu düzeltin, böylece parametrelerin yerine WHERE yan tümcesinde kodlanmış değerleri kullanan herhangi bir sorgu alınca güzel, açıklayıcı bir sonuç döndürür parametreleri yerine kullanmanızı bildiren hata mesajı. Bunun açık bir şekilde bir seçenek dışı bırakılması gerekir; böylece yönetimsel araçlardan gelen geçici sorgular gibi şeyler hala kolayca çalışır, ancak varsayılan olarak etkinleştirilmesi gerekir.

Bunu yapmak, SQL enjeksiyonunu neredeyse bir gece boyunca soğuyacaktı ama bildiğim kadarıyla hiçbir RDBMS bunu yapmıyor. Olmamasının iyi bir nedeni var mı?


22
bad_ideas_sql = 'SELECT title FROM idea WHERE idea.status == "bad" AND idea.user == :mwheeler'tek bir sorguda hem kodlanmış hem de parametreleştirilmiş değerlere sahip olurdu - onu yakalamaya çalışın! Bu tür karışık sorgular için geçerli kullanım durumları olduğunu düşünüyorum.
amon

6
Bugünden gelen kayıtları seçmeye ne dersinizSELECT * FROM jokes WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY score DESC;
Jaydee

10
@MasonWheeler üzgünüm, “buna izin vermeye çalış” demek istedim. Mükemmel bir şekilde parametrelendirildiğini ve SQL enjeksiyonundan etkilenmediğini unutmayın. Ancak, veritabanı sürücüsü hazır bilginin "bad"gerçekten hazır olup olmadığını veya dizge birleştirme işleminden kaynaklandığını söyleyemez . Gördüğüm iki çözüm, SQL'den ve dize gömülü diğer DSL'lerden kurtulmak (lütfen evet) veya dize birleştirmenin parametreli hale getirilmiş sorguları kullanmaktan daha sinir bozucu olduğu dilleri teşvik etmek (umm, hayır).
amon

4
ve RDBMS bunun yapılıp yapılmayacağını nasıl tespit eder? Bir gecede etkileşimli bir SQL istemi kullanarak RDBMS'ye erişmeyi imkansız kılar ... Artık herhangi bir aracı kullanarak DDL veya DML komutlarını giremezsiniz.
15’te

8
Bir anlamda bunu yapabilirsiniz: SQL sorgularını çalışma zamanında oluşturmayın, bunun yerine ORM veya SQL sorguları oluşturmanıza gerek kalmasını önleyen başka bir soyutlama katmanı kullanın. ORM ihtiyacınız olan özelliklere sahip değil mi? Daha sonra SQL, SQL yazmak isteyenlere yönelik bir dildir, bu yüzden genel olarak SQL yazmalarına izin verir. Temel sorun, dinamik olarak kod üretmenin göründüğünden daha zor olmasıdır, ancak insanlar yine de yapmak ister ve izin vermeyen ürünlerden memnun kalmazlar.
Steve Jessop

Yanıtlar:


45

Bir edebi kullanmanın doğru yaklaşım olduğu çok fazla durum vardır.

Performans açısından, sorgularınızda değişmezleri istediğiniz zamanlar vardır. Düşünsene, performanstan endişe duymaya yetecek kadar büyüdüğünde, sistemdeki hataların% 70'inin "kapalı",% 20'sinin "açık",% 5'inin "aktif" ve 5 olacağını % başka bir durumda olacak. Makul olarak tüm etkin hataları döndüren sorguyu almak isteyebilirim

SELECT *
  FROM bug
 WHERE status = 'active'

statusbağlama değişkeni olarak geçmek yerine . İçin iletilen değere bağlı olarak farklı bir sorgu planı statusistiyorum - kapalı hataları döndürmek için bir tablo taraması yapmak vestatusaktif kredileri iade etmek için sütun. Şimdi, farklı veritabanları ve farklı sürümler, farklı değişkenlere sahip (az çok başarılı) aynı sorgunun, bind değişkeninin değerine bağlı olarak farklı bir sorgu planı kullanmasına izin veriyor. Ancak bu, bir sorgunun yeniden ayrıştırılmasının zahmete uğramaması veya mevcut bir planın yeni bir bağlama değişkeni değeri için tekrar kullanılıp kullanılmayacağı kararını dengelemek için yönetilmesi gereken makul bir karmaşıklık ortaya koyma eğilimindedir. Bir geliştirici için bu karmaşıklıkla başa çıkmak mantıklı gelebilir. Veya verilerimin optimize ediciden daha çok neye benzeyeceği hakkında daha fazla bilgiye sahip olduğumda farklı bir yolu zorlamak mantıklı olabilir.

Bir kod karmaşıklığı açısından bakıldığında, SQL deyimlerinde değişmezleri olması mükemmel bir anlam ifade eder. Örneğin, zip_code5 karakterlik bir posta koduna ve bazen ek 4 basamağa sahip bir sütununuz varsa , bunun gibi bir şey yapmak mükemmel olur.

SELECT substr( zip_code, 1, 5 ) zip,
       substr( zip_code, 7, 4 ) plus_four

Sayısal değerler için 4 ayrı parametreden geçmek yerine. Bunlar değişecek şeyler değildir, bu yüzden değişkenleri bağlamalarını sağlamak, sadece kodun okunmasını zorlaştırmak ve birisinin parametreleri yanlış sırayla bağlama ve bir hata ile sonuçlanma potansiyelini yaratma işlevini görür.


12

SQL enjeksiyonu, bir sorgu güvenilir olmayan ve doğrulanmamış bir kaynaktan gelen metni bir sorgunun diğer bölümleriyle birleştirerek bir sorgu oluşturduğunda meydana gelir. Böyle bir şey en çok dize değişmezleri ile gerçekleşse de, gerçekleşmesi için tek yol bu olmazdı. Sayısal değerler için bir sorgu, kullanıcının girdiği bir dizeyi ( yalnızca rakam içermesi gerekiyordu ) alabilir ve normalde string değişmezleriyle ilişkilendirilmiş alıntı işaretleri olmadan bir sorgu oluşturmak için diğer malzemelerle birleştirilebilir; İstemci tarafı doğrulamasına aşırı derecede güvenen kod, alan adlarına benzer bir HTML sorgu dizesinden gelebilir. Bir SQL sorgusu dizisine bakmanın nasıl bir araya getirildiğini görmesinin bir yolu yoktur.

Önemli olan, bir SQL ifadesinin string değişmezleri içerip içermediği değil, bir dizgenin güvenilir olmayan kaynaklardan gelen karakter dizilerini içerip içermediği ve bunun için doğrulamanın sorguları oluşturan kütüphanede en iyi şekilde ele alınmasıdır. Genel olarak C # 'da bir dizgenin değişmezine izin verecek, ancak başka bir dize ifadesine izin vermeyecek kod yazmasına imkan yoktur, ancak bir sorguların sorgulama sınıfı yerine sorgu oluşturma sınıfı kullanılarak oluşturulmasını gerektiren bir kodlama-uygulama kuralına sahip olabilir. dize bitiştirme işleminin ve hazırlayıcı olmayan bir dizgiyi sorgu oluşturucuya ileten herkes böyle bir eylemi haklı göstermelidir.


1
"Bu bir değişmez mi?" Yaklaşımı olarak, dizenin interned olup olmadığını kontrol edebilirsiniz.
CodesInChaos 11:15

1
@CodesInChaos: Doğru ve böyle bir test, çalışma zamanında bir dize oluşturmak için bir nedeni olan herhangi birinin, çalışma zamanı tarafından oluşturulan bir dize yerleştirmek ve kullanmak yerine, değişmez bir dize kabul eden bir yöntem kullanması koşuluyla, bu amaç için yeterince doğru olabilir. bu (değişmez-string-string yöntemine farklı bir ad vermek, kod gözden geçirenlerin tüm kullanımlarını denetlemelerini kolaylaştırır).
supercat

Bunu C # ile yapmanın bir yolu olmadığı halde, bazı diğer dillerin bunu mümkün kılan olanaklara sahip olduğunu unutmayın (örn. Perl'in sicim dizili dize modülü).
Jules

Daha kısaca, bu bir müşteri problemidir, sunucu problemi değildir.
Blrfl

7
SELECT count(ID)
FROM posts
WHERE deleted = false

Bunların sonuçlarını forumunuzun altbilgisine koymak istiyorsanız, sadece her seferinde yanlış demek için kukla bir parametre eklemeniz gerekir. Ya da saf web programcısı bu uyarının nasıl devre dışı bırakılacağına bakar ve devam eder.

Şimdi, enums'lar için bir istisna ekleyeceğinizi söyleyebilirsiniz ancak bu sadece deliği tekrar açar (daha küçük) İnsanlardan bahsetmemek, öncelikle varcharsonlar için kullanılmaması için eğitimli olmalıdır .

Enjeksiyonun asıl sorunu programatik olarak sorgu dizesini oluşturmaktır. Bunun çözümü, saklı bir prosedür mekanizmasıdır ve kullanımını veya izin verilen sorguların beyaz listesini zorlamaktır.


2
"Tamamen unutmak - ya da ilk etapta bilmemek - parametreli sorguları kullanmak" için çözümünüz "herkesi hatırlatmak - ve ilk etapta bilmek - saklı işlemleri kullanmak" ise, o zaman Sorunun tüm noktasını özlüyorum.
Mason Wheeler

5
İşimde saklı yordamlar aracılığıyla SQL enjeksiyonu gördüm. Her şey için saklı yordamlar zorunlu KÖTÜ olduğu ortaya çıktı. Her zaman gerçek dinamik sorgular olan% 0,5'tir (bir tablonun bir araya gelmesine rağmen, tümcenin tümcesini parametreleştiremezsiniz).
Joshua,

Bu cevaptaki örnekte , değişmezden kaçınan deleted = falseile değiştirebilirsiniz NOT deleted. Ancak mesele genel olarak geçerlidir.
psm,

5

TL; DR : Sadece cümlelerde olmayanları değil, tüm harfleri kısıtlamanız gerekir WHERE. Yapmamaları nedeniyle, veritabanının diğer sistemlerden ayrı kalmasını sağlar.

İlk olarak, öncülünüz hatalı. Yalnızca WHEREcümleleri kısıtlamak istiyorsunuz , ancak kullanıcı girişinin gidebileceği tek yer bu değil. Örneğin,

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Bu, SQL enjeksiyonuna eşit derecede savunmasızdır:

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) FROM item; DROP TABLE user_info; SELECT CASE(WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Öyleyse, WHEREmaddedeki harfleri kısıtlayamazsın . Tüm değişmezleri kısıtlamanız gerekir .

Şimdi "Neden değişmezlere hiç izin verilsin?" Sorusundan ayrıldık. Şunu aklından çıkarma: ilişkisel veri tabanları başka bir dile bir zaman büyük bir yüzdesi yazılmış bir uygulamanın altına kullanılır iken, hiçbir orada gereklilik veritabanını kullanmak için uygulama kodunu kullanmak gerektiğini söyledi. Ve burada bir cevabımız var: kod yazmak için değişmezlere ihtiyacınız var. Diğer tek alternatif tüm kodun veritabanından bağımsız olarak bir dilde yazılmasını gerektirmektir. Böylece, onlara sahip olmak size doğrudan veritabanına "kod" (SQL) yazma yeteneği verir. Bu değerli bir ayrıştırma ve hazır bilgi olmadan imkansız olurdu. (En sevdiğiniz dilde, bazen hazır olmadan da yazmaya çalışın. Bunun ne kadar zor olacağını hayal edebileceğinizden eminim.)

Yaygın bir örnek olarak, değişmezler genellikle değer listesi / arama tabloları popülasyonunda kullanılır:

CREATE TABLE user_roles (role_id INTEGER, role_name VARCHAR(50));
INSERT INTO user_roles (1, 'normal');
INSERT INTO user_roles (2, 'admin');
INSERT INTO user_roles (3, 'banned');

Onlar olmadan, sadece bu tabloyu doldurmak için başka bir programlama dilinde kod yazmanız gerekir . Bunu doğrudan SQL'de yapabilme yeteneği değerlidir .

Daha sonra bir sorumuz kaldı: neden programlama dili istemcisi kütüphaneleri bunu yapmıyor? Ve burada çok basit bir cevabımız var: veritabanının desteklenen her bir sürümü için tüm veritabanı ayrıştırıcısını yeniden uygularlardı . Niye ya? Çünkü her kelimeyi bulduğunu garanti etmenin başka yolu yok. Normal ifadeler yeterli değil. Örneğin: Bu PostgreSQL'de 4 ayrı hazır bilgi içerir:

SELECT $lit1$I'm a literal$lit1$||$lit2$I'm another literal $$ with nested string delimiters$$ $lit2$||'I''m ANOTHER literal'||$$I'm the last literal$$;

Bunu yapmaya çalışmak bir bakım kabusu olur, özellikle de geçerli sözdizimi çoğu kez veritabanlarının büyük sürümleri arasında değiştiğinden.

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.