ASP.NET Core'da Siteler Arası Betik Çalıştırmayı (XSS) Engelleme

Yazan Rick Anderson

Siteler Arası Betik (XSS), bir siber saldırının istemci tarafı betiklerini (genellikle JavaScript) web sayfalarına yerleştirmesini sağlayan bir güvenlik açığıdır. Diğer kullanıcılar etkilenen sayfaları yüklediğinde siber saldırının betikleri çalıştırılır. Siber saldırı daha sonra tanımlama bilgilerini ve oturum belirteçlerini çalabilir, DOM düzenlemesi aracılığıyla web sayfasının içeriğini değiştirebilir veya tarayıcıyı başka bir sayfaya yönlendirebilir. XSS güvenlik açıkları genellikle bir uygulama, kullanıcı girdisini doğrulamadan, kodlamadan veya kaçışlamadan bir sayfada çıktı olarak sunduğunda ortaya çıkar.

Bu makale öncelikle görünümlere sahip ASP.NET Core MVC, Razor Sayfaları ve XSS'ye karşı savunmasız olabilecek HTML döndüren diğer uygulamalar için geçerlidir. HTML, XML veya JSON biçiminde veri döndüren Web API'leri, kullanıcı girişini düzgün temizlemedikleri takdirde istemci uygulamalarında XSS saldırılarını tetikleyebilir. Bu davranış, istemci uygulamasının API'ye ne kadar güven olduğuna bağlıdır. API kullanıcı tarafından oluşturulan içeriği kabul eder ve html yanıtında döndürürse, veriler saldırılara açıktır. Bir siber saldırı aracı, yanıt kullanıcının tarayıcısında işlendiğinde yürütülen içeriğe kötü amaçlı betikler ekleyebilir.

XSS saldırılarını önlemek için web API'leri giriş doğrulama ve çıkış kodlaması uygulamalıdır. Giriş doğrulaması, kullanıcı girişinin beklenen ölçütleri karşıladığından ve kötü amaçlı kod içermediğinden emin olur. Çıkış kodlaması, API tarafından döndürülen verilerin, kullanıcının tarayıcısı tarafından kod olarak yürütülememesi için uygun şekilde güvenli hâle getirilmesini sağlar. Daha fazla bilgi için bkz. GitHub dotnet/aspnetcore.docs sorunu #28789.

Uygulamanızı XSS'ye karşı koruma

Temel olarak XSS, uygulamanızı kandırıp oluşturulan sayfanıza bir <script> etiketi eklemesini sağlayarak veya bir öğeye On* olayı ekleterek çalışır.

Geliştiriciler, uygulamaya XSS'nin eklenmesini önlemek için aşağıdaki önleme tekniklerini uygulamalıdır:

  • Bu bölümde listelenen diğer teknikleri izlemediğiniz sürece, html girişinize güvenilmeyen verileri hiçbir zaman yerleştirmeyin.

    Güvenilmeyen veriler, siber saldırılar tarafından denetlenebilen verilerdir. Örnek olarak HTML form girişleri, sorgu dizeleri, HTTP üst bilgileri ve hatta veritabanından alınan veriler verilebilir. Bir siber saldırı sorumlusu uygulamanızı ihlal etmese bile veritabanınızı ihlal edebilir.

  • Güvenilmeyen verileri bir HTML öğesine yerleştirmeden önce verilerin HTML kodlanmış olduğundan emin olun.

    HTML kodlaması, sol açılı ayraç veya küçük (<) gibi karakterleri alır ve bunları (&lt;) gibi güvenli bir biçimde değiştirir.

  • Güvenilmeyen verileri bir HTML özniteliğine yerleştirmeden önce verilerin HTML özniteliği kodlanmış olduğundan emin olun.

    Bu özel HTML kodlama biçimi çift tırnak ("), tek tırnak ('), ampersand (&) ve küçüktür işareti (<) karakterlerini işler. Güvenilmeyen girişlerle ilgilenirken, genel HTML içeriği için HTML kodlamasını ve HTML öznitelikleri için HTML öznitelik kodlamasını kullanın.

  • JavaScript'e güvenilmeyen verileri yerleştirmeden önce, verileri çalışma zamanında içeriğini aldığınız bir HTML öğesine yerleştirin.

    Bu tekniği izleyemiyorsanız verilerin JavaScript kodlanmış olduğundan emin olun. JavaScript kodlaması, JavaScript için tehlikeli karakterleri onaltılık eşdeğer bir değere dönüştürür. Örneğin, JavaScript kodlaması küçüktür (<) karakterini onaltılık değer \u003Colarak değiştirir.

  • Güvenilmeyen verileri bir URL sorgu dizesine yerleştirmeden önce verilerin URL ile kodlandığından emin olun.

ile HTML kodlamayı keşfetme Razor

MVC'de Razor kullanılan altyapı, bu davranışı önlemek için çalışmadığınız sürece değişkenlerden alınan tüm çıkışı otomatik olarak kodlar. at sembol @ yönergesini her kullandığınızda HTML özniteliği kodlama kurallarını kullanır. HTML öznitelik kodlaması HTML kodlamasının üst kümesi olduğundan, HTML kodlaması mı yoksa HTML özniteliği kodlaması mı kullanacağınızı düşünmeniz gerekmez. Güvenilmeyen girişi doğrudan JavaScript'e eklemeye çalışırken değil, yalnızca html bağlamında at simgesini @ kullandığınızdan emin olmanız gerekir. Razor Etiket Yardımcıları , etiket parametrelerinde kullandığınız girişi de kodlar.

Aşağıdaki Razor görünümü göz önünde bulundurun:

@{
    var untrustedInput = "<\"123\">";
}

@untrustedInput

Bu görünüm, untrustedInput değişkeninin içeriğini görüntüler. Değişken, XSS saldırılarında kullanılan bazı karakterleri içerir: küçüktür (<), çift tırnak işareti (") ve dik açılı ayraç veya büyüktür (>). Kaynağın incelenmesi, işlenen çıkışın şu şekilde kodlanmış olduğunu gösterir:

&lt;&quot;123&quot;&gt;

Uyarı

ASP.NET Core MVC, çıkışta otomatik olarak kodlanmış olmayan bir HtmlString sınıfı sağlar. Bu sınıf hiçbir zaman güvenilmeyen girişle birlikte kullanılmamalıdır çünkü bir XSS güvenlik açığını ortaya çıkarır.

JavaScript kodlamasını keşfedin Razor

Bazı durumlarda, görünümünüzde işlemek için JavaScript'e bir değer eklemek isteyebilirsiniz. Bu görevi gerçekleştirmenin iki yolu vardır. Değer eklemenin en güvenli yolu, değeri bir etiketin veri özniteliğine yerleştirmek ve JavaScript'inize almaktır. Örneğin:

@{
    var untrustedInput = "<script>alert(1)</script>";
}

<div id="injectedData"
     data-untrustedinput="@untrustedInput" />

<div id="scriptedWrite" />
<div id="scriptedWrite-html5" />

<script>
    var injectedData = document.getElementById("injectedData");

    // All clients
    var clientSideUntrustedInputOldStyle =
        injectedData.getAttribute("data-untrustedinput");

    // HTML 5 clients only
    var clientSideUntrustedInputHtml5 =
        injectedData.dataset.untrustedinput;

    // Put the injected, untrusted data into the scriptedWrite div tag.
    // Do NOT use document.write() on dynamically generated data as it can lead to XSS.

    document.getElementById("scriptedWrite").innerText += clientSideUntrustedInputOldStyle;

    // Or, you can use createElement() to dynamically create document elements.
    // This instance uses textContent to ensure the data is properly encoded.
    var x = document.createElement("div");
    x.textContent = clientSideUntrustedInputHtml5;
    document.body.appendChild(x);

    // You can also use createTextNode on an element to ensure data is properly encoded.
    var y = document.createElement("div");
    y.appendChild(document.createTextNode(clientSideUntrustedInputHtml5));
    document.body.appendChild(y);

</script>

Yukarıdaki işaretleme aşağıdaki HTML'yi oluşturur:

<div id="injectedData"
     data-untrustedinput="&lt;script&gt;alert(1)&lt;/script&gt;" />

<div id="scriptedWrite" />
<div id="scriptedWrite-html5" />

<script>
    var injectedData = document.getElementById("injectedData");

    // All clients
    var clientSideUntrustedInputOldStyle =
        injectedData.getAttribute("data-untrustedinput");

    // HTML 5 clients only
    var clientSideUntrustedInputHtml5 =
        injectedData.dataset.untrustedinput;

    // Put the injected, untrusted data into the scriptedWrite div tag.
    // Do NOT use document.write() on dynamically generated data as it can lead to XSS.

    document.getElementById("scriptedWrite").innerText += clientSideUntrustedInputOldStyle;

    // Or, you can use createElement() to dynamically create document elements.
    // This instance uses textContent to ensure the data is properly encoded.
    var x = document.createElement("div");
    x.textContent = clientSideUntrustedInputHtml5;
    document.body.appendChild(x);

    // You can also use createTextNode on an element to ensure data is properly encoded.
    var y = document.createElement("div");
    y.appendChild(document.createTextNode(clientSideUntrustedInputHtml5));
    document.body.appendChild(y);

</script>

Yukarıdaki kod aşağıdaki çıkışı oluşturur:

<script>alert(1)</script>
<script>alert(1)</script>
<script>alert(1)</script>

Uyarı

DOM öğeleri oluşturmak için JavaScript'te güvenilmeyen girdileri birleştirmeyin veya dinamik olarak oluşturulan içerikte document.write() kullanmayın.

Bunun yerine, kodun DOM tabanlı XSS'ye açık olmasını önlemek için aşağıdaki yaklaşımlardan birini kullanın:

  • createElement() öğesini çağırın ve özellik değerlerini, node.textContent= veya node.InnerText= gibi uygun yöntemler ya da özellikler kullanarak atayın.
  • yöntemini çağırın document.CreateTextNode() ve uygun DOM konumuna ekleyin.
  • yöntemini çağırın element.SetAttribute() .
  • element[attribute]= atamasını kullanın.

Koddaki kodlayıcılara erişme

Kodunuzda HTML, JavaScript ve URL kodlayıcılarını iki şekilde kullanabilirsiniz:

  • Bunları bağımlılık enjeksiyonu yoluyla ekleyin.
  • Ad alanında bulunan varsayılan kodlayıcıları System.Text.Encodings.Web kullanın.

Varsayılan kodlayıcıları kullandığınızda, karakter aralıklarına uygulanan özelleştirmeler (böylece güvenli olarak kabul edilir) etkili olmaz. Varsayılan kodlayıcılar mümkün olan en güvenli kodlama kurallarını kullanır.

Bağımlılık ekleme yoluyla yapılandırılabilir kodlayıcıları kullanmak için oluşturucularınızın uygun şekilde bir HtmlEncoder, JavaScriptEncoderve UrlEncoder parametresi alması gerekir.

Örneğin:

public class HomeController : Controller
{
    HtmlEncoder _htmlEncoder;
    JavaScriptEncoder _javaScriptEncoder;
    UrlEncoder _urlEncoder;

    public HomeController(HtmlEncoder htmlEncoder,
                          JavaScriptEncoder javascriptEncoder,
                          UrlEncoder urlEncoder)
    {
        _htmlEncoder = htmlEncoder;
        _javaScriptEncoder = javascriptEncoder;
        _urlEncoder = urlEncoder;
    }
}

URL parametrelerini kodlama

Değer olarak güvenilmeyen giriş içeren bir URL sorgu dizesi oluşturmak istiyorsanız, değerini kodlamak için parametresini kullanın UrlEncoder :

var example = "\"Quoted Value with spaces and &\"";
var encodedValue = _urlEncoder.Encode(example);

Kodlamadan sonra encodedValue değişkeni %22Quoted%20Value%20with%20spaces%20and%20%26%22 dizesini içerir. Boşluklar, tırnak işaretleri, noktalama işaretleri ve diğer güvenli olmayan karakterler, onaltılık değerlerine göre yüzde olarak kodlanır. Örneğin, bir boşluk karakteri %20 öğesine dönüştürülür.

Uyarı

URL yolunun parçası olarak güvenilmeyen girişi kullanmayın. Güvenilmeyen girişi her zaman sorgu dizesi değeri olarak geçirin.

Kodlayıcıları özelleştirme

Kodlayıcılar varsayılan olarak Temel Latin Unicode aralığıyla sınırlı bir güvenli liste kullanır. Belirtilen aralığın dışındaki tüm karakterler, karakter kodu eşdeğerleri olarak kodlanır. Bu davranış, dizelerinizi çıktıya dönüştürmek için kodlayıcıları kullandıklarından, Razor Etiket Yardımcıları ve HTML Yardımcıları tarafından oluşturulan çıktıyı da etkiler.

Bu davranışın amacı bilinmeyen veya gelecekteki tarayıcı hatalarına karşı koruma sağlamaktır. Önceki tarayıcı hataları, İngilizce dışındaki karakterlerin işlenmesine dayalı ayrıştırmayı sekteye uğrattı. Web siteniz Çince, Kiril veya diğerleri gibi Latin olmayan karakterleri yoğun bir şekilde kullanıyorsa, bu davranış büyük olasılıkla yapılandırmanız için uygun değildir.

Kodlayıcı güvenli listelerini, başlatma sırasında uygulamaya uygun Unicode aralıkları içerecek şekilde özelleştirebilirsiniz. özelleştirmeleri Program.cs dosyasında yapın.

Örneğin, varsayılan yapılandırmayı aşağıdaki HTML'ye benzer bir Razor HTML Yardımcısı ile kullanabilirsiniz:

<p>This link text is in Chinese: @Html.ActionLink("汉语/漢語", "Index")</p>

Yukarıdaki işaretleme Çince metin kodlanmış olarak işlenir:

<p>This link text is in Chinese: <a href="/">&#x6C49;&#x8BED;/&#x6F22;&#x8A9E;</a></p>

Kodlayıcı tarafından güvenli olarak ele alınan karakter aralığını genişletmeye Program.cs dosyasına aşağıdaki satırı ekleyin:

builder.Services.AddSingleton<HtmlEncoder>(
     HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
                                               UnicodeRanges.CjkUnifiedIdeographs }));

Kodlayıcı güvenli listelerini, ConfigureServices()içinde, başlatma sırasında uygulamanıza uygun Unicode aralıkları içerecek şekilde özelleştirebilirsiniz.

Örneğin, varsayılan yapılandırmayı kullanarak bunun gibi bir Razor HtmlHelper kullanabilirsiniz;

<p>This link text is in Chinese: @Html.ActionLink("汉语/漢語", "Index")</p>

Web sayfasının kaynağını görüntülediğinizde, çince metin kodlanmış şekilde aşağıdaki gibi işlendiğini görürsünüz;

<p>This link text is in Chinese: <a href="/">&#x6C49;&#x8BED;/&#x6F22;&#x8A9E;</a></p>

Kodlayıcı tarafından güvenli olarak ele alınan karakterleri genişletmek için aşağıdaki satırı startup.cs içindeki ConfigureServices() yöntemine eklersiniz;

services.AddSingleton<HtmlEncoder>(
     HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
                                               UnicodeRanges.CjkUnifiedIdeographs }));

Bu örnek, güvenli listeyi Unicode Aralığı CJK Birleşik Ideograflarını içerecek şekilde genişletmektedir. Aşağıdaki çıktı, daha geniş güvenli karakter aralığı için işlenmiş görünümü gösterir:

<p>This link text is in Chinese: <a href="/">汉语/漢語</a></p>

Güvenli liste aralıkları dil olarak değil Unicode kod grafikleri olarak belirtilir. Unicode standardı, karakterlerinizi içeren grafiği bulmak için kullanabileceğiniz kod grafiklerinin bir listesine sahiptir. Her kodlayıcı (HTML, JavaScript, URL) ayrı olarak yapılandırılmalıdır.

Not

Güvenli listenin özelleştirilmesi yalnızca bağımlılık enjeksiyonu aracılığıyla sağlanan kodlayıcıları etkiler. aracılığıyla doğrudan bir kodlayıcıya System.Text.Encodings.Web.*Encoder.Defaulterişiyorsa, yalnızca varsayılan güvenli liste (Temel Latin) kullanılır.

Kodlamanın ne zaman ve nerede yapılacağına karar verme

Genel olarak kabul edilen uygulama, kodlamanın çıkış noktasında gerçekleşmesi ve kodlanmış değerlerin hiçbir zaman veritabanında depolanmaması gerektiğidir.

Çıktı noktasında kodlama, veri kullanımını değiştirmenize olanak tanır. Örneğin, HTML'den sorgu dizesi değerine geçin. Bu yaklaşım, arama yapmadan önce değerleri kodlamak zorunda kalmadan verilerinizi kolayca aramanızı sağlar. Ayrıca kodlayıcılarda yapılan tüm değişikliklerden veya hata düzeltmelerinden yararlanmanızı sağlar.

Doğrulamayı XSS önleme tekniği olarak kullanma

Doğrulama, XSS saldırılarını sınırlamak için kullanışlı bir araç olabilir. Örneğin, yalnızca 0-9 karakterlerini içeren sayısal bir dize XSS saldırısını tetiklemez.

Kullanıcı girişinde HTML kabul edildiğinde doğrulama daha karmaşık olur. HTML girişini ayrıştırmak zor olabilir ve bazen imkansız olabilir. Gömülü HTML’yi ayıklayan bir ayrıştırıcıyla birlikte kullanıldığında Markdown, zengin içerikli girdi kabul etmek için daha güvenli bir seçenektir.

Yalnızca doğrulamaya asla güvenmeyin. Hangi doğrulama veya temizleme gerçekleştirilirse gerçekleştirilsin, her zaman güvenilmeyen girişi çıktıdan önce kodlar.