11 Haziran 2017

Güvenli Yazılım Geliştirmede Cross Site Scripting (XSS) Ve AntiXSS Kütüphanesi


Bu yazıda Cross Site Scripting yani daha çok kullandığımız ismiyle XSS üzerine konuşacağız.

Eski bir açıklıktır ve son Owasp Top 10'de de her zamanki gibi yerini almıştır. Kaynak kod analizlerinde veya sızma testlerinde de oldukça adını duyduğumuz bir açıklıktır.

XSS, bir saldırganın kaynak kod içerisine kendi zararlı kodlarını enjekte edebilmesini neden olan bir zafiyettir. Bu zafiyeti istismar eden bir saldırganın geniş yelpazede bir zarar vermesi mümkün olabilmektedir.

Üç çeşit XSS türü vardır. Bunlar arasındaki fark girdinin kullanıcıya ulaşma şekliyle alakalıdır, yani istismar etmenin zorluğuyla, etkisi ile değil:
  1. Stored XSS çeşidinde; zararlı kod veri tabanına depolanmakta, ilgili sayfa her çağrıldığı zaman bu kod çalışmaktadır. 
  2. Reflected XSS çeşidinde; zararlı kod sunucuya gitmekte, sunucudan doğru şekilde sanitize edilmeden geri dönmektedir. Örneğin URL'de bulunan bir parametredeki zafiyeti istismar etmek isteyen bir saldırgan, hazırlayacağı zararlı kod barındıran URL'i seçtiği kullanıcıya göndererek bu kullanıcıda ilgili kodun çalışmasını sağlayabilir. 
  3. DOM Based XSS çeşidinde ise zararlı paylaod sunucuya hiç gitmemekte, doğrudan kullanıcının tarayıcısındaki DOM ortamında çalışmaktadır. 



Bu konuda owasp.org'dan daha detaylı bilgi alınabilir. Ek olarak web uygulama güvenliği testleri ile uğraşan uzmanların da yazdığı oldukça fazla yazı var. Benim bu yazıda değineceğim konu ise XSS'e karşı alınabilecek örnek bir önlemdir.

Asp.Net web forms için yapacağımız örnek bir sayfadan aldığımız girdiyi başka bir sayfada yazdıracağımız çok basit bir örnek olacak. Tasarım, kullanıcıdan bir girdi almak için bir textbox ve bir submit butonu içerecek. Bir tane de Person isimli çok basit class olacak:

    public static class Person
    {
        public static string Name;
    }

Butona basıldığında da şöyle bir kod çalışacak.

    protected void btnSubmit_Click(object sender, EventArgs e)
    {
        Person.Name = txtName.Text;
        Response.Redirect("PageTwo.aspx");
    }

Yapılmak istenen Person'ın Name property'sini diğer sayfada yazdırmak olacağından diğer sayfa page load event'ine de şu kodu yazalım:

    protected void Page_Load(object sender, EventArgs e)
    {
        Response.Write(Person.Name);
    }

Buna ek olarak web.config dosyasında şu ayarı yapalım (Bu konuya aşağıda değiniyor olacağım):

  <system.web>
    <pages validateRequest="false"/>

  </system.web>


Senaryoya göre girdi gireceğimiz yere çalışacak bir script yazıyoruz, Submit butonuna basıyoruz ve bu girdiyi diğer sayfaya taşıyıp o sayfada render ediyoruz ve tam o sırada karşımıza bir reflected XSS çıkıyor:

Sayfa açılır:
Girdi alanına basit bir XSS vektörü yerleştirilir:

Ve işte bir reflected XSS karşımızda:



Böylelikle basit bir reflected XSS örneği yazmış olduk. Burada sorun bir girdi alanından alınan verinin çalıştırılabilir bir script olması ve bizim de bu script'in çalışmasına herhangi bir engel koymamış olmamızdır.


Peki bu durumda biz yazılımcılar ne yapmalı?

Öncelikle web.config'de yaptığımız ayarı açıklayayım. <pages validateRequest="true"/> olsaydı Asp.Net girilen verinin doğrulamasını bizim için yapacaktı ve karşımıza bu girdinin zararlı olduğuna dair şu hatayı çıkaracaktı, yani saldırgan başarılı olamayacaktı:



Fakat bazen projelerimizde true değerini false yapmak zorunda kalabiliyoruz ve bu da tüm sayfalarda validation'ı kapatılmasına ve zararlı bir script yazıldığında kontrol edilmemesine neden olmaktadır. Kapatmak eğer gerekli ise tüm proje yerine web form için her sayfaya ayrı ayrı yani sayfa bazlı da yapılabilir:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="PageOne.aspx.cs" Inherits="AntiXSSSampleWithXSS.PageOne" ValidateRequest="false" %>

Aynı ayar gerekli ise Asp.Net MVC için controller'a [ValidateInput(false)] şeklinde yapılabilir, field'lar için de [AllowHtmlattribute'u yazılabilir.

XSS için alınabilecek önlem validation'dur. Bunu yapmak için kendi whitelist veya blacklist'imizi oluşturabilir ve bu listelere göre input encoding ya da output encoding yapılabilir. Hem input encoding hem de output encoding yapılmamalıdır zira bu seçim istenmeyen durumlar ortaya çıkaracaktır. Whitelist veya blacklist oluşturmak da her zaman çok güvenilir bir yöntem olmayabilir. Çünkü kendi oluşturduğumuz mimaride encoding sırasında veya öncesinde veya sonrasında öngörülemeyen ya da işlere engel olabilecek durumlarla karşılaşılabilir.

Microsoft AntiXSS kütüphanesi işte tam bu noktada işimizi kolaylaştırıyor. Önceleri Nuget Package Manager'dan indirip projemize dahil edebiliyorken artık yeni açtığımız projelerde hali hazırda kullanabileceğimiz şekilde geliyor. Kullanacağımız yerde sadece using System.Web.Security.AntiXss;'i eklemek yeterli. Nuget'ten eklemek gerekiyorsa da aşağıdaki gibi aratıp ilk sırada AntiXSS by Microsoft isimli olanı projeye dahil edilebilir:



Örneğimizdeki kod blogunu output encoding yöntemi kullanarak yani şu şekilde değiştirerek kullanılabilir.:

Response.Write(AntiXssEncoder.HtmlEncode(Person.Name, false));

Sayfa render edildiğinde ise saldırgan tarafından input alanından yazılan script, bir script gibi çalıştırılmadan kullanıcıya yansıyacaktır:


Encoding işlemi html tarafında bir output encoding şeklinde yapılacaksa örneğin şu şekilde yapılabilir:

<td><%# Server.HtmlEncode(Eval("NAME").ToString()) %></td>



XSS'si bir başka yazımda MVC ile kurcalamayı düşünüyorum. Bu yazımda XSS'i kısaca anlattıktan sonra klasik Asp.Net örneği üzerinden alabileceğimiz bir savunma yönteminden bahsetmiş oldum. Faydalı olması dileğiyle.



Devamını oku ...

7 Mayıs 2017

Güvenli Yazılım Geliştirmede Dosya Yükleme


Bu yazım ilk olarak BGA Security Blog'da yayınlanmıştır.  






İşleyiş olarak son kullanıcıya oldukça yararlı ve günümüz dünyasında yaygınlığı olan uygulamalarda bile güvenlik açıklıkları çıkmaktadır. Bu durum yazılımcıların fonksiyonaliteyi arttırayım derken güvenliğe yeterince önem verememesinden kaynaklanabilmektedir. Bu açıklıklardan en fazla dikkat edilmesi gereken konulardan birisi olarak güvensiz upload’ı inceleyeceğiz.

File upload alanları istemci tarafından sunucu tarafına dosya gönderme olanağı sağlayan yapılardır. Dikkat edileceği üzere burası bir saldırgan için sunucuya açılan bir kapıdır. Bu nedenle yazılımcıların buradaki senaryoları iyi kodlaması gerekmektedir ki yüklenilen dosya üzerindeki yetersiz kontroller nedeniyle uygulamanın koştuğu sistem için güvenlik problemlerine yol açılmasın. Bu nedenlerdendir ki bir yazılımcı asla dışarıdan gelen kaynağa güvenmemelidir. Heleki bu kaynağın sunucu tarafında yorumlanıp çalıştırılabilecek bir dosya olabileceği düşünülürse.


Bu yazıda alınabilecek önlemler, best practice’ler ve tek başına yeterli olmayan kontroller ele alınacaktır.



Authentication ve Authorization



Bir dosya yükleme alanı varsa öncelikle burayı bu uygulamaya giriş yapan ve dosya yüklemeye izni olan kullanıcılar kullanmalı. Aksi taktirde bir saldırganın uygulamaya girmeden (Authentication) ve de işlemi yapması için bir yetkiye ihtiyaç duymadan (Authorization) kolayca bu dosya yükleme alanında denemeler yapmasına ve hatta belki de başarılı olmasına neden olunabilir.


Önlem olarak projenin kendi yapısına uygun olacak şekilde bir kurgu oluşturulmalıdır. Dosya yükleme işlemini gerçekleştiren kullanıcı login durumunda mı, dosya yüklemeye veya yüklemek istediği dosyanın türünde bir dosyayı yüklemeye yetkili mi gibi kontroller yapan metodlar yazıp en azından herkesin bu önemli alana erişme imkanı ortadan kaldırılmalıdır.



Yüklenen Dosyanın Maximum Boyutu



Yüklenen dosyalarda bir boyut sınırı olmazsa saldırgan sisteme oldukça büyük bir dosya yükleyip yer kaynaklarını tüketebilir ve sunucuyu hizmetdışı bırakabilir. Bunu engellemenin yolu yüklenmeye çalışılan dosyanın boyutunu kontrol etmektir. .Net’te yüklenen dosyanın boyutunu byte olarak döndüren bir metod vardır:



fileUploadControl.PostedFile.ContentLength


Bu kod ile yüklenen dosyanın boyut kontrolünün yapılması gerekmektedir.


  
if(fileUploadControl.PostedFile.ContentLength > 0
    && fileUploadControl.PostedFile.ContentLength < 1048576)
{;}


Burada ancak 0 Byte’tan büyük, 1 MegaByte’tan küçük boyuttaki bir dosya ise if koşulunun içini gerçekleştir denmiş oluyor. İstenilen boyutun dışında bir dosya yüklenmeye çalışırsa da işlemi gerçekleştirmeyecektir.



Uzantıların Kontrolü (BlackListing & WhiteListing)



Uygulamaya hangi uzantıya/uzantılara sahip olan dosyaların yüklenebilir olduğuna göre uzantı kontrolü yapılabilir. Bunun için blacklist veya whitelist gibi yöntemler uygulanabilir. Bir whitelist oluşturarak yüklenmesine izin verilen dosya uzantıları belirlenebilir ve bunların dışındaki dosyaların yüklenmesine izin verilmeyebilir. Ya da bir blacklist oluşturarak asla yüklenmemesi gereken dosya uzantıları kontrol edilebilir ve yüklenmesi engellenebilir.


Ancak zararlı bir içerik barındıran bir dosya için bu yöntem yeterli olmayacaktır çünkü saldırgan shell.png.aspx şeklinde bir dosya adı kullanarak bu yöntemi bypass edebilir. Örneğin png dosyası yüklemeye izin veren bir file upload alanında saldırgan içinde zararlı bir kod bulunan shell.png.aspx diye bir dosyayı sisteme yükleyebilir ve bunu çalıştırabilir.


Önlem olarak dosya adındaki ilk noktadan sonraki değil de en son noktadan sonraki uzantı kontrol edilmelidir. Bu dosyanın gerçek uzantısı aslında .aspx’tir.


ASP.NET’te dosyanın uzantısını bulan metod:

string extension = System.IO.Path.GetExtension(FileName);


Bu kod dosyanın uzantısı olarak .aspx’i döndürecektir. Fakat kullanılan programlama diline göre bu işi yapan bir metod olmayabilir. O zaman da şu mantık ile dosya uzantısı bulunabilir:


string extension = fileUploadControl.FileName.Substring(fileUploadControl.FileName.LastIndexOf('.') + 1);



Yüklenen Dosyada İsim Değişikliği



Yüklenecek dosyaya yeni bir isim ve kabul edilen uzantı verilebilir. Bu yöntem XSS ve path traversal gibi dosya ismi yolu ile yapılabilecek saldırılara karşı alınabilecek bir önlem olarak kabul edilir.


Dosya Content’ini Kontrol Etme



Her dosya için kendine özgü bir header bilgisi olur. Her dosya uzantısı için de ilk 4 byte sabittir. Bu ilk 4 byte’ı kontrol ederek de dosyaya güvenilebilir. Aşağıdaki örnek kodlar bu kontrolü yapmaktadır:


if (fileUploadControl.HasFile
            && fileUploadControl.PostedFile.ContentLength > 0
            && fileUploadControl.PostedFile.ContentLength < 1048576)
        {
            if (IsImage())
            {
                try
                {
                    string fileName = Path.GetFileName(fileUploadControl.PostedFile.FileName);
                    string directoryPath = Server.MapPath("~/UploadedFiles");
                    string uploadPath = Path.Combine(directoryPath, fileName);
                    if (!Directory.Exists(directoryPath))
                    {
                        Directory.CreateDirectory(directoryPath);
                    }
                   fileUploadControl.SaveAs(uploadPath);
                    lblStatus.Text = "Success!";
                }
                catch (Exception ex)
                {
                    lblStatus.Text = "Failure! " + ex.Message;
                   }
            }
            else
            {
                lblStatus.Text += "<br />This is not an image file!";
            }
        }


public bool IsImage()
       {
        string[] validFileTypes = { "JPG", "JPEG", "PNG", "TIF", "TIFF", "GIF", "BMP", "ICO" };
        string ext = fileUploadControl.FileName.Substring(fileUploadControl.FileName.LastIndexOf('.') + 1).ToUpper(); //or string ext = Path.GetExtension(this.fileUploadControl.PostedFile.FileName).TrimStart('.').ToUpper();
        bool isValidFileExtention = false;
        for (int i = 0; i < validFileTypes.Length; i++)
        {
            if (ext == validFileTypes[i])
            {
                isValidFileExtention = true;
                break;
            }
        }
        if (!isValidFileExtention)
        {
            lblStatus.ForeColor = System.Drawing.Color.Red;
            lblStatus.Text = string.Format("Invalid file. Please upload a file with extension {0}", string.Join(",", validFileTypes));
        }
        if (isValidFileExtention)
            isValidFileExtention = CheckTrueImageType();
        return isValidFileExtention;
       }
       public bool CheckTrueImageType()
       {
        // DICTIONARY OF ALL IMAGE FILE HEADER
        Dictionary<string, byte[][]> imageHeader = new Dictionary<string, byte[][]>();
        imageHeader.Add("JPG", new byte[][] { new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
                                                 new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 },
                                                 new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
                                                 new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
                                                 new byte[] { 0xFF, 0xD8, 0xFF, 0xE8 },
                                                 new byte[] { 0xFF, 0xD8, 0xFF, 0xDB } });
        imageHeader.Add("JPEG", new byte[][] { new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
                                                  new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 },
                                                  new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
                                                  new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
                                                  new byte[] { 0xFF, 0xD8, 0xFF, 0xE8 },
                                                  new byte[] { 0xFF, 0xD8, 0xFF, 0xDB }  });
        imageHeader.Add("PNG", new byte[][] { new byte[] { 0x89, 0x50, 0x4E, 0x47 } });
        imageHeader.Add("TIF", new byte[][] { new byte[] { 0x49, 0x49, 0x2A, 0x00 },
                                             new byte[] { 0x49, 0x20, 0x49 },
                                                 new byte[] { 0x4D, 0x4D, 0x00, 0x2A },
                                                 new byte[] { 0x4D, 0x4D, 0x00, 0x2B } });
        imageHeader.Add("TIFF", new byte[][] { new byte[] { 0x49, 0x49, 0x2A, 0x00 },
                                                  new byte[] { 0x49, 0x20, 0x49 },
                                                  new byte[] { 0x4D, 0x4D, 0x00, 0x2A },
                                                  new byte[] { 0x4D, 0x4D, 0x00, 0x2B } });
        imageHeader.Add("GIF", new byte[][] { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 },
                                                 new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 } });
        imageHeader.Add("BMP", new byte[][] { new byte[] { 0x42, 0x4D } });
        imageHeader.Add("ICO", new byte[][] { new byte[] { 0x00, 0x00, 0x01, 0x00 } });
        bool isTrueImage = false;
        if (fileUploadControl.HasFile)
        {
            // GET FILE EXTENSION
            string fileExt = Path.GetExtension(this.fileUploadControl.PostedFile.FileName).TrimStart('.').ToUpper();
            // CUSTOM VALIDATION GOES HERE BASED ON FILE EXTENSION IF ANY
            byte[][] tmp = imageHeader[fileExt];
            foreach (byte[] validHeader in tmp)
            {
                byte[] header = new byte[validHeader.Length];
                   // GET HEADER INFORMATION OF UPLOADED FILE
                //fileUploadControl.FileContent.Seek(0, SeekOrigin.Begin);
                   fileUploadControl.FileContent.Read(header, 0, header.Length);
                if (CompareArray(validHeader, header))
                {
                    // VALID HEADER INFORMATION
                    isTrueImage = true;
                    break;
                }
            }
        }
        if (!isTrueImage)
        {
            lblStatus.ForeColor = System.Drawing.Color.Red;
            lblStatus.Text += "<br />Invalid file header! ";
        }
        return isTrueImage;
       }
       private bool CompareArray(byte[] a1, byte[] a2)
       {
        if (a1.Length != a2.Length)
            return false;
        for (int i = 0; i < a1.Length; i++)
        {
            if (a1[i] != a2[i])
                return false;
        }
        return true;
    }


Client-Side Validation



ASP.NET yazılımcılara daha dosyayı yükleme aşamasında kullanabilecekleri bir takım doğrulama yöntemleri sunmaktadır. Validation’lar… Validation’lar, yazılımcılara dosya yüklenip yüklenmediğini veya yüklenen dosyanın belirtilen regex’e (whitelist) uygun bir uzantıda olup olmadığını kontrol etmeye ve eğer istenmeyen bir durum varsa bu konuda uygun dönüşler yapması ve işlemi gerçekleştirmemesi konusunda yardımcı olur. ASP.NET’te validation’lar genel olarak sadece dosya yükleme için değil, diğer birçok doğrulama türü için kullanılabilmektedir.


Dosya yükleme esnasında client-side validation kullanımı daha çok .Net’te yaygındır ve sebebi de belirttiğimiz gibi .Net’in yazılımcıların işini kolaylaştıran bu imkanı basitçe sunmuş olmasından kaynaklanmaktadır.


ASP.NET’te örnek bir validation kullanımı:


        <asp:FileUpload ID="fileUploadControl" runat="server" />
        <asp:RequiredFieldValidator ID="rfvFileUpload" runat="server"
            ControlToValidate="fileUploadControl" ErrorMessage="File required." Display="Dynamic" ForeColor="Red">
        </asp:RequiredFieldValidator>
        <asp:RegularExpressionValidator ID="revFileUpload" ValidationExpression="([a-zA-Z0-9\s_\\\-:])+(.jpg|.jpeg|.png|)$"
            ControlToValidate="fileUploadControl" runat="server" ForeColor="Red" ErrorMessage="Please select a valid Word or PDF file."
            Display="Dynamic" />
        <asp:Button ID="btnUpload" runat="server" OnClick="btnUpload_Click" Text="Save" />

ASP.NET, Jquery, HTML5, javascript veya diğer programlama dilleri ile yazılan client-side yöntemler saldırgan tarafından Burp veya Fiddler gibi araçlar ile kolayca bypass edilebileceği için yazılımcının kullanacağı bu yöntem güvensiz upload’lara karşı kesin alınmış bir önlem olarak kabul edilemeyecektir.



IIS Request Filtering



IIS (Internet Information Services) 7.0 ile gelen bir istek filtreleme mekanizması olan Request Filtering ile istenilen dizinde belirlenen türlerdeki dosyalar için gelen isteklere izin vermeyi ya da isteği reddetmeyi sağlamaktadır. Bu özelliği kullanmanın yolu ise şu şekilde:


IIS’te istek filtreleme yapılacak alana tıklanır:


Şekil 1 - IIS Görünüm


Request Filtering yazan yere tıklandığında da aşağıdaki kısım açılacaktır:


Şekil 2 - File Name Extensions Alanından Çalıştırılmasının Engellendiği Dosya Uzantılarının Listesi


Boş alana sağ tıklanınca açılacak küçük pencereden Deny File Name Extension seçeneği seçilir:


Şekil 3 - File Name Extensions Alanından Çalıştırılması Engellenecek Dosya Uzantısının Eklenmesi


Açılacak küçük pencereye istenilen uzantı yazılıp OK’a basılınca listeye eklendiği görülecektir:


Şekil 4 - File Name Extensions Alanından Çalıştırılması Engellenmek İstenen Dosya Uzantılarının Belirlenmesi



Bu yöntemi kullanarak da projedeki belli klasörlerde veya tamamında belirlenen türdeki dosyalar için gelen istekler kabul edilmeyebilir ve atılması olası herhangi bir shell ya da zararlı içerikli bir dosyanın çağrılıp çalıştırılması engellenecektir.



Sonuç



Buraya kadar anlatılanlardan da görüldüğü gibi bir saldırgan dosya yükleme alanlarında birçok bypass yöntemi kullanabilmektedir. Yazılımcıdan beklenen bu anlatılan hususlara dikkat ederek önlemler almak ve bu önlemlerin çalışır olduğundan emin olmaktır.
Bu yazı .Net file upload control’ü üzerinden yazılmıştır. Ancak alınacak önlemler diğer diller ile benzer başlıkları taşımaktadır.








Devamını oku ...