May 2010 - Posts

När ASP.NET 2.0 kom följde en del providers med för Membership, Personalization och annat. Sedan har det stått stilla på den fronten fram tills nu. I ASP.NET 4.0 introduceras OutputCacheProvider som ger oss möjligheten att enkelt anpassa output-cachen i ASP.NET. Tack vare detta kan vi antingen skriva helt anpassade providers för cachen, eller använda oss utav till exempel AppFabric Cache.

Det jag kommer att göra här är att skapa en ny enkel provider för cache. Det kommer inte att bli någon alltför snygg och stabil lösning, utan syftet är bara att visa ett exempel på en fungerande implementation av providern.

Jag kommer börja med att skapa ett ASP.NET MVC-projekt (.NET 4.0), för att sedan skapa en klass som använder singleton pattern för att vi enkelt ska kunna dela med oss utav instansen. Jag kommer sedan att ha en till klass för själva providern.

OutputCacheProvider är en abstrakt klass och har fyra metoder som vi kommer att implementera:

  • Add
  • Get
  • Remove
  • Set

Providern i sig blir väldigt enkel:

using System;
using System.Web.Caching;
 
namespace CustomOutputCacheProvider
{
    public class CustomCacheProvider : OutputCacheProvider
    {
        CustomCache _cache = CustomCache.Instance;
 
        public override object Add(string key, object entry, DateTime expires)
        {
            return _cache.Add(key, entry, expires);
        }
 
        public override object Get(string key)
        {
            return _cache.Get(key);
        }
 
        public override void Remove(string key)
        {
            _cache.Remove(key);
        }
 
        public override void Set(string key, object entry, DateTime expires)
        {
            _cache.Set(key, entry, expires);
        }
    }
}

Det vi gör här är att vi plockar fram instansen av CustomCache, vilket blir den klassen som innehåller all logik. Vi vidarebefordrar sedan alla värden direkt till den klassen då vi vill spara cachen i en gemensam collection.

Vad gäller CustomCache-klassen så kommer jag att börja med att klistra in all kod och sedan förklara den.

using System;
using System.Collections.Generic;
 
namespace CustomOutputCacheProvider
{
    public sealed class CustomCache
    {
        private Dictionary<string, CacheItem> _items = new Dictionary<string, CacheItem>();
 
        CustomCache()
        {
        }
 
        public static CustomCache Instance
        {
            get
            {
                return GetInstance.instance;
            }
        }
 
        class GetInstance
        {
            static GetInstance()
            {
            }
 
            internal static readonly CustomCache instance = new CustomCache();
        }
 
        internal object Add(string key, object entry, DateTime expires)
        {
            if (_items.ContainsKey(key))
            {
                return _items[key].Item;
            }
            else
            {
                _items.Add(key, new CacheItem()
                {
                    Item = entry,
                    Expires = expires
                });
            }
 
            return Get(key);
        }
 
        internal object Get(string key)
        {
            if (!_items.ContainsKey(key))
                return null;
 
            CacheItem item = _items[key];
 
            if (item.Item == null || item.Expires.ToUniversalTime() < DateTime.Now.ToUniversalTime())
            {
                Remove(key);
                return null;
            }
 
            return item.Item;
        }
 
        internal void Remove(string key)
        {
            _items.Remove(key);
        }
 
        internal void Set(string key, object entry, DateTime expires)
        {
            if (_items.ContainsKey(key))
            {
                CacheItem item = _items[key];
                item.Item = entry;
                item.Expires = expires;
 
                _items[key] = item;
            }
            else
            {
                _items.Add(key, new CacheItem()
                {
                    Item = entry,
                    Expires = expires
                });
            }
        }
 
        class CacheItem
        {
            public object Item { get; set; }
            public DateTime Expires { get; set; }
        }
    }
}

Här sker all lagring av värdena. Alla värden sparas i en dictionary där värdet är av typen CacheItem och innehåller värdet och tidpunkten då cachen går ut.

Vad de olika metoderna gör är ganska självförklarande. Vad gäller Add-metoden så är det viktigt att tänka på att vi bara ska spara värdena om de inte redan finns. Om det finns värden sparade så skall dessa returneras och de nya skall kastas. I Set-metoden skall dock gamla värden ersättas med de nya.

För att vi ska kunna använda providern så måste vi ange det i web.config.

<caching>
    <outputCache defaultProvider="CustomCache">
        <providers>
            <add name="CustomCache"
                    type="CustomOutputCacheProvider.CustomCacheProvider,
                    CustomOutputCacheProvider" />
        </providers>
    </outputCache>
</caching>

Det här gör att CustomCacheProvider blir standard-provider för cachen, men det är även möjligt att i global.asax aktivera en annan provider under runtime om man så önskar.

För att vi nu ska kunna testa om det fungerar så kan vi använda oss utav våra färdiga sidor som kom med när vi skapade projektet. Gå in i HomeController och ändra raden där rubriken på sidan i Index-metoden sätts till:

ViewData["Message"] = DateTime.Now.ToLongTimeString();

Vi behöver även aktivera cache för Action-metoden:

[OutputCache(VaryByParam="*",Duration=5)]
public ActionResult Index()

Om vi nu besöker sidan och uppdaterar den så kan vi se att tiden inte uppdateras oftare än var femte sekund. För att vara säkra på att vår provider körs så kan vi sätta en breakpoint i de olika metoderna, vilket även låter oss se exakt vad som sparas.

Det här var ett väldigt enkelt exempel, och andra typer av cache som skulle kunna implementeras kan vara i filsystemet eller i en databas.

När vi skapar nya projekt i ASP.NET MVC så får vi med två Controllers, HomeController och AccountController. Vi kan även enkelt skapa egna Controllers, vilka alltid måste ha suffixet Controller, samt ligga i mappen ”Controllers”.

Men hur kan vi göra om vi inte vill ha suffixet Controller, eller om vi vill ha våra controllers i någon annan mapp eller assembly? Hur gör vi om vi vill använda dependency injection för att kunna byta Controllers mer dynamiskt? Svaret är att skapa en egen ControllerFactory. Det är här ASP.NET MVC hämtar en controller och sedan släpper den.

För att skapa en egen ControllerFactory så har vi två val, antingen implementerar vi IControllerFactory, eller så ärver vi DefaultControllerFactory vilket är standard-klassen för det i ASP.NET MVC. Vi måste sedan registrera vår ControllerFactory så att den läses in när vi surfar in på sidan.

I det här exemplet kommer jag att ha en solution i Visual Studio 2010 med två projekt, ett ASP.NET MVC-projekt, och ett klassbibliotek. I klassbiblioteket skapar jag referenser till System.Web.Mvc.dll samt System.Web.Routing.dll.

I klassbiblioteket skapar vi en ny klass som heter ”New” (observera avsaknaden av Controller-suffixet) och som ärver från Controller. Den har dessutom två ActionResult-metoder.

using System.Web.Mvc;
 
namespace ExternalControllers
{
    public class New : Controller
    {
        public ActionResult Index()
        {
            return new ContentResult() { Content = "Hämtat från New!" };
        }
 
        public ActionResult Test(string parameter)
        {
            return new ContentResult() { Content = parameter };
        }
    }
}

Vi behöver även en liknande controller i ASP.NET MVC-projektet, så vi skapar en vid namn ”OldController” där.

using System.Web.Mvc;
 
namespace CustomControllerFactoryTest.Controllers
{
    public class OldController : Controller
    {
        public ActionResult Index()
        {
            return new ContentResult() { Content = "Hämtat från OldController!" };
        }
 
        public ActionResult Test(string parameter)
        {
            return new ContentResult() { Content = parameter };
        }
    }
}

Surfar vi nu till /Old så kan vi se att vi får fram texten ”Hämtat från OldController!” då den ligger i Controllers-mappen, men surfar vi till /New så får vi ett 404-meddelanden då vi inte har någon NewController där. Hur ska vi då göra för att läsa in den och exekvera på samma sätt som OldController?

För skapar vi en klass som implementerar IControllerFactory. Vi väljer även att skapa de metoder som finns specificerade i interfacet. Vi kan sedan börja med ReleaseController(…) då den är enklast.

public void ReleaseController(IController controller)
{
    var c = controller as IDisposable;
 
    if (c == null)
        c.Dispose();
}

Det den här metoden gör är att den tar emot en IController. Vi kollar sedan om den även implementerar IDisposable, och om så är fallet så väljer vi att disposa objektet. Den här metoden anropas när en action-metod har körts på sidan och kopplingen mot controllern inte behövs längre.

Vi har även en annan metod i klassen, nämligen CreateController(…). Den här metoden gör tvärtom, den skapar upp en instans till en controller. Det vi kommer att göra nu är att kolla om användaren försöker köra controllern ”Old” eller något annat. Om någon annan controller anropas så vill vi anropa controllern New, vilken vi har i vår externa assembly.

För att veta vilken controller som har efterfrågats så kan vi kolla i RequestContext-objektet vi får som parameter. Här finns alla RouteData-värden med, vilka innehåller bl.a. namnet på Controllern. För att kunna veta vilka värden som skickas in så kan vi kolla i global.asax.

routes.MapRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

Här kan vi se att det är tre värden som skickas med vår RouteData, controller, action och id. Vi kommer alltså åt dessa värden från vår ControllerFactory, men vi kan även modifiera dessa samt lägga till nya värden programmatiskt där innan vi anropar en controller. Om vi ser på de controllers vi har skapat upp så har vi en metod i varje controller med namnet ”Test”. De tar emot en parameter vid namn ”parameter”. Då detta värde inte finns med i vår MapRoute så måste den skapas upp i vår ControllerFactory för att vi skall ha tillgång till den.

Då vi kan ändra i de RouteData-värden vi får så ska vi även se till att ändra från Index till Test som action när användaren surfar till vår sida.

För att sedan kunna visa var aktuell controller finns så kommer vi att läsa in assemblyn och skapa en ny instans av den aktuella controllern, vilken vi sedan returnerar från metoden.

När vi har fått ned dessa steg i kod, kan vår metod se ut ungefär så här:

public IController CreateController(RequestContext requestContext, string controllerName)
{
    string type = requestContext.RouteData.Values["controller"] as string;
 
    requestContext.RouteData.Values["action"] = "Test";
    requestContext.RouteData.Values["parameter"] = type;
 
    Type controllerType;
 
    if (type == "Old")
        controllerType = Type.GetType("CustomControllerFactoryTest.Controllers.OldController, CustomControllerFactoryTest");
    else
        controllerType = Type.GetType("ExternalControllers.New, ExternalControllers");
 
    var controller = Activator.CreateInstance(controllerType) as IController;
 
    return controller;
}

Om vi surfar till sidan nu så ser vi dock ingen skillnad från tidigare. Det vi har kvar att göra nu är att registrera vår ControllerFactory i global.asax.

protected void Application_Start()
{
    //…
 
    ControllerBuilder.Current.SetControllerFactory(typeof(CustomControllerFactory));
}

Genom att ange vilken typ vår ControllerFactory är kan en instans skapas av ASP.NET MVC.

Om vi surfar till sidan nu så får vi upp texten ”Home”. Anledningen till det är för att ASP.NET MVC förväntar sig HomeController. Vi har dock hoppat in mellan och skickat iväg användaren till den nya controllern kallad ”New”, vilket vi kan se om vi debuggar. Surfar vi till /Old så kan vi även se att vi kommer till OldController, vilken ligger i en annan assembly än New.

Genom att skapa en egen anpassad ControllerFactory så får vi alltså större möjlighet att påverka vilken controller som skall laddas in, än om vi hade kört med DefaultControllerFactory.

När vi skapar ett projekt i ASP.NET MVC så får vi med två standard-controllers i projektet (AccountController och HomeController). I dessa finns det sedan metoder som används för att ta emot de requests som görs. Metoderna som tar hand om dessa kallas ofta ”action-metoder” och är av typen ActionResult som standard. Men vad är egentligen ActionResult?

ActionResult-klassen har en metod vid namn ExecuteResult(ControllerContext context), vilken exekveras när vi kör den.

För att testa funktionaliteten så skapar vi en nya controller med namnet ”CustomerController”. Vi får nu med en action-metod kallad Index. Den körs som standard då det är bestämt så i route-inställningarna i global.asax.

Vår kod har nu det här utseendet:

using System.Web.Mvc;
 
namespace MvcActionResult.Controllers
{
    public class CustomerController : Controller
    {
        //
        // GET: /Customer/
 
        public ActionResult Index()
        {
            return View();
        }
 
    }
}

Surfar vi till /Customer/ så får vi det här felet på sidan:

The view 'Index' or its master was not found. The following locations were searched:
~/Views/Customer/Index.aspx
~/Views/Customer/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx

Det metoden gör i det här fallet är att den letar reda på Index.aspx eller Index.ascx i antingen den delade Shared-mappen (vilken alla controllers kommer åt), eller i Customer-mappen, vilken innehåller aspx-filer för just CustomerController. Då vi inte har skapat någon vy för vår action-metod (i det här fallet en aspx-fil) så får vi felet.

Om vi vill returnera en vanlig aspx-fil passar det utmärkt med en ActionResult då det är just det den gör. Om man kikar i dokumentationen för ActionResult så kan vi se att det dessutom finns en del olika klasser som ärver denna. Det finns även klasser som ärver dessa i sin tur.

De klasser som ärver ActionResult är:

ContentResult
ContentResult är lämplig när man vill returnera till exempel en text, XML eller annat. Man kan ange content som en string, och sedan även ange encoding och content type.

EmptyResult
Namnet säger allt. Ett EmptyResult gör ingenting och returnerar ingenting.

FileResult
Kan användas när man vill returnera en fil. Finns även klasser som ärver denna och är anpassade för t.ex. FileStream.

HttpUnauthorizedResult
Om en användare inte har rätt att se innehållet kan denna returneras.

JavaScriptResult
Då JavaScript används allt mer ökar även kravet på dynamiskt skapad sådan. Med JavaScriptResult kan returnera JavaScript direkt från sin action-metod.

JsonResult
JsonResult är en av mina favoriter, och jag kommer snart att visa hur man kan använda den. Det den gör är att den serialiserar modellen och returnerar som ett Json-objekt. En utmärkt lösning när man arbetar med Ajax.

RedirectResult
Om du vill skicka iväg användare till en annan Url bör du använda RedirectResult.

RedirectToRouteResult
Använd denna om du vill använda route-värden för att skicka en användare till rätt Url. Bör användas istället för RedirectResult om du skall skicka användaren till en sida i samma ASP.NET MVC-projekt.

ViewResultBase
Det här är egentligen ingen action-metod, men ärver ActionResult. Det är här ASP.NET MVC avgör vilken vy som skall returneras till klienten. Det är alltså en väldigt viktig del.

När vi vill använda någon av dessa så kan vi returnera dem rakt av. För att testa det så kan vi ändra i Index-metoden vi har i CustomerController till det här:

public ActionResult Index()
{
    return new ContentResult() {
        Content = "Testar lite!"
    };
}

Om vi besöker sidan nu så kan vi se att vi inte får felmeddelandet om aspx-filerna längre, utan nu får vi istället texten ”Testar lite!” utskriven på skärmen. Anledningen till det är att ContentResult bara returnerar det som vi har sagt åt det att returnera.

För att göra ett mer avancerat exempel så kan vi använda JsonResult. Som jag nämnde tidigare så fungerar det utmärkt med Ajax, så för att testa det kommer jag att använda jQuery för att hämta Json-strängen asynkront.

För att returnera något så behöver vi en modell. I det här fallet så kommer jag inte att skapa en ny typ att returnera, utan kommer istället att skapa en anonym typ för att spara ned på antalet rader kod. Det vi vill returnera är en person med dess namn och hemort.

public ActionResult Customer()
{
    var data = new {
        Name = "Mikael",
        City = "Stockholm"
    };
 
    return Json(data, JsonRequestBehavior.AllowGet);
}

Json returnerar ett JsonResult, och har ett antal överlagrade konstruktorer. I det här fallet anger jag dels modellen (data) samt ett JsonRequestBehavior då jag vill kunna anropa metoden vid GET och inte bara POST.

Surfar vi till Url:en får vi upp:

{"Name":"Mikael","City":"Stockholm"}

Nu väljer jag att skapa en vy för Index-metoden som bara returnerar View(). Här lägger jag in en knapp samt en referens till jQuery.

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Testar JsonResult</title>
    <script src="../../Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            $('input').click(function () {
                $.getJSON('/Customer/Customer', function (customer) {
                    alert(customer.Name);
                });
            });
        });
    </script>
</head>
<body>
    <div>
        <input type="button" value="Klicka för namnet!" />
    </div>
</body>
</html>

Om vi nu klickar på knappen så kommer en alert-ruta med texten ”Mikael” att dyka upp. Det är alltså väldigt enkelt att med hjälp av JsonResult returnera modellerna som ren Json utan särskilt mycket arbete.

Men om vi nu vill skapa egna typer av Action-metoder? Det är absolut möjligt att göra, och kan enkelt göras genom att man antingen ärver ActionResult, eller någon av de klasser som redan ärver ActionResult.

Säg att vi vill ha en ImageResult som returnerar bilder direkt i webbläsaren. Det vi behöver göra för att få det är att först ärva ActionResult, och sedan i ExecuteResult välja att skriva ut något direkt till klienten. Denna ImageResult kommer vi sedan att kunna använda i vilken controller vi vill, samt även i andra projekt då den blir väldigt generisk.

För enkelhetens skull har jag valt att ärva direkt i ActionResult nu, men jag skulle även kunna ärva från någon av de klasser som har med binära resultat att göra.

Den färdiga koden ser ut på det här viset:

using System;
using System.IO;
using System.Web.Mvc;
 
namespace MvcActionResult.Code
{
    public class ImageResult : ActionResult
    {
        private string _path;
 
        public ImageResult(string imagePath)
        {
            if (String.IsNullOrWhiteSpace(imagePath))
                throw new ArgumentException("Image path can not be null, empty or whitespace.");
 
            _path = imagePath;
        }
 
        public override void ExecuteResult(ControllerContext context)
        {
            byte[] imageData = File.ReadAllBytes(_path);
            context.HttpContext.Response.ContentType = "image/png";
            context.HttpContext.Response.BinaryWrite(imageData);
        }
    }
}

Jag har först en konstruktor som tar emot en fysisk sökväg till en fil. I ExecuteResult läser jag sedan av filen och returnerar hos klienten.

För att använda action-metoden så skapar jag en ny metod i controllern:

public ActionResult Image()
{
    return new ImageResult(Server.MapPath("~/Content/logo.png"));
}

Om jag nu surfar till /Customer/Image så kan jag se att bilden visas direkt i sidan. Tack vare att det är en action-metod kan jag nu använda vanliga helpers för att generera URL:en. Det är även enkelt att programmatiskt påverka vilken bild som skall visas. Just den här metoden är väldigt enkelt och klarar enbart png-bilder, men den är å andra sidan enkelt att bygga ut.

Då det är så enkelt att skapa egna implementationer av ActionResult så finns det en mängd olika varianter att tillgå på internet för att returnera t.ex. RSS, vCard-filer och annat.

Hör gärna av dig om du har funderingar, eller om du har skrivit någon egen typ av ActionResult!

Idag har jag varit i Umeå (tidigare även Sundsvall och Stockholm) för att tillsammans med Danwei Tran hålla ett föredrag om bland annat ASP.NET och webbstandarder.

Ett av problemen med video på internet som jag tog upp var det om codecs. För att video-elementet i HTML 5 skall fungera så måste webbläsartillverkarna komma överens om en och samma codec. Internet Explorer och Safari har förklarat att de ger stöd för h264, och Firefox Ogg. Det leder till stora problem då det kräver två olika versioner av varje video för att kunna visas rätt i webbläsarna.

Nu har dock en lösning på det kommit. Google har idag gått ut med att de släpper VP8-codecen – som de fick genom köpet av On2 Technologies – helt fri under BSD-licensen. I samband med det har även ett nytt projekt kallat The WebM Project dragit igång, för att lösa problemen med codecs på internet.

Ett exempel på sidor som kommer att använda VP8 är Youtube som kommer att göra om alla videos till det. Förutom det så kommer även Internet Explorer 9, Chrome, Opera, Firefox, Flash och många andra att ge stöd för det.

Det här är väldigt stora nyheter och kommer definitivt att ge video på webben en stor knuff framåt!

Gång på gång hör man om webbsajter som på olika sätt blir hackade. Som tur är så kan vi faktiskt skydda oss väldigt lätt mot de flesta attacker.

En majoritet av dessa attacker beror på:

  • XSS (Cross-Site Scripting)
  • SQL Injections
  • XSRF (Cross-Site Request Forgery)

Det jag kommer att ta upp här är den första punkten – nämligen Cross-Site Scripting.

XSS går ut på att man på något vis injicerar HTML och/eller JavaScript på en sida. Det kan låta ofarligt, men med tanke på att man med JavaScript kan utnyttja brister i webbläsare, skicka användaren till andra sidor, skriva om DOM:en och annat så kan väldigt stora problem uppstå.

För att skydda sig mot detta så finns det en del olika saker man bör tänka på. Viktigaste regeln är dock att aldrig lita på användaren. Litar man blint på det användaren postar till sidan så råkar man lätt ut för bland annat XSS-problem.

I de följande exemplen kommer jag att använda Visual Studio 2010 och .NET 4.0. Jag kommer även att använda Anti-XSS Library från Microsoft, vilket är ett bibliotek med funktionalitet som på ett smart sätt skyddar oss mot XSS. Biblioteket kan laddas ned här:

http://www.microsoft.com/downloads/details.aspx?FamilyId=051ee83c-5ccf-48ed-8463-02f56a6bfc09&displaylang=en

Stäng av inbyggda säkerhetsskyddet

För att enklare visa hur vi kan skydda upp sidorna så kommer vi att börja med att stänga av det inbyggda skyddet. Det kanske låter dumt, och det är det också. Det finns dock tillfällen när vi faktiskt ”måste” stänga av det.

I ASP.NET 1.1 kom ett nytt attribut kallat ”validateRequest”. Det kollar automatiskt querystrings och formulär efter HTML-element och kastar i sådana fall ett felmeddelande. Om man nu vill posta HTML så måste man inaktivera det på den sidan man vill posta HTML. Det går även att inaktivera det över hela webbplatsen, och det är det vi ska göra nu.

Börja med att gå till web.config. Här skall vi lägga till två olika attribut för att inaktivera skyddet:

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
    
    <!-- Kom i .NET 1.1 för att skydda mot XSS -->
    <pages validateRequest="false" />
    
    <!-- Kom i .NET 4.0. Sätter normalt requestValidate på alla requests. -->
    <httpRuntime requestValidationMode="2.0" />
  </system.web>
</configuration>

I tidigare versioner av .NET så räckte det med validateRequest, men då vi kör .NET 4.0 så behöver vi även sätta requestValidationMode till 2.0. Det här är en ny funktion som automatiskt sätter requestValidate på alla requests, och inte bara på de vars filändelse slutar med aspx. Det här är en breaking change i .NET 4.0 och kan leda till att befintliga webbsajter upphör att fungera om de uppgraderas direkt till .NET 4.0. Genom att sätta värdet till 2.0 så körs det dock i klassiska läget.

Nu har vi stängt av det inbyggda XSS-skyddet över hela sajten, vilket absolut inte rekommenderas för skarpa sajter.

Skydda din output

Det som renderas på vår sida är ofta dynamiskt och kommer från till exempel en databas. I aspx-filen så har vi sällan koll på exakt vad som kommer, utan litar blint på de och renderar allt som vanligt.

För att ha ett enkelt scenario så har vi det här formuläret på vår sida:

<div>
    <fieldset>
        <legend>Skydda din output</legend>
        <p>
            <a href="?namn=Mikael">Säkert</a>
            <a href="?namn=Mikael&lt;script&gt;alert('XSS!')&lt;/script&gt;">Osäkert</a>
        </p>
        <b>Osäkert namn:</b> <%=Request.QueryString["namn"]%>
        <br />
        <b>Säkert namn (HtmlEncode):</b> <%=Server.HtmlEncode(Request.QueryString["namn"])%>
        <br />
        <b>Säkert namn:</b> <%:Request.QueryString["namn"]%>
    </fieldset>
</div>

Det som händer här är att vi har två länkar. Den första skickar oss till ?name=Mikael och den andra till ?name=Mikael<script>alert(’XSS!’)</script>.

Vi skriver sedan ut resultatet på tre olika sätt. Om vi klickar på den säkra länken så får vi detta:

1 - SafeOutput

Om vi istället klickar på den osäkra länken så får vi detta:

2 - SafeOutput

Om vi ser på vad som har renderats nu så kan vi se detta:

<b>Osäkert namn:</b> Mikael<script>alert('XSS!')</script>
<br />
<b>Säkert namn (HtmlEncode):</b> Mikael&lt;script&gt;alert(&#39;XSS!&#39;)&lt;/script&gt;
<br />
<b>Säkert namn:</b> Mikael&lt;script&gt;alert(&#39;XSS!&#39;)&lt;/script&gt;

Vi kan se att den första raden skrev ut script-blocket rakt av, medan de andra ersatte tecknen till ofarliga sådana. I ena fallet användes <%= Server.HtmlEncode() %> och i det andra <%: %>.

Den kortare varianten introducerades i ASP.NET 4.0 och fungerar med både ASP.NET Web Forms och ASP.NET MVC. Anledningen till att den kom är av den anledningen att kodblock ofta används med ASP.NET MVC, och det här gör att man med färre tecken kan nyttja en funktion som man väldigt ofta behöver.

Det här är ett exempel på hur farligt det kan vara att ta något och skriva ut det direkt. Man bör alltid se till att filtrera det som kommer att skrivas ut på sidan.

Skydda dig från användaren

Som jag sa tidigare så kan vi aldrig lita på användaren. Oavsett hur ärlig användaren ser ut att vara så kan denne alltid skriva in ”fel” information.

Säg att vi har ett formulär som detta:

<div>
    <fieldset>
        <legend>Information från code behind</legend>
        <p>
            <asp:TextBox ID="tbText" runat="server" CssClass="text" />
            <asp:Button id="btnSubmitText" runat="server" Text="Skicka anrop" onclick="btnSubmitText_Click" />
        </p>
        <b>Resultat:</b>  <asp:Literal ID="ltResult" runat="server" />
    </fieldset>
</div>

När något postas så tar vi emot det i den här metoden i code behind:

protected void btnSubmitText_Click(object sender, EventArgs e)
{
    ltResult.Text = tbText.Text;
}

Vi tar alltså informationen direkt från användaren och skriver ut. Om vi nu blint litar på användaren så kan användaren fylla i något i stil med:

Namn!<script>alert('XSS')</script>

Det leder till att vi får:

3 - User

Det här är ett relativt ofarligt resultat, men då inget syns direkt på sidan förutom alert-rutan så skulle vi lika gärna kunna ha ett JavaScript som körs i bakgrunden utan att användaren vet om det.

Säkra upp länkar

Det här är att scenario som väldigt få skyddar sig mot. Gång på gång kan man stöta på potentiella säkerhetshål som detta.

Det händer väldigt ofta att man renderar länkar på en sida. När man gör detta så kanske man hämtar URL:er från till exempel en databas eller från annat håll. Man sätter sedan href-attributet till det hämtade värdet och tänker inte mer på det.

Om man inte ser till att kontrollera värdet så kan det leda till oönskade effekter.

En vanlig länk kan se ut på följande sätt:

<a href=”sida.aspx>Min sida</a>

Tänk nu att ”sidan.aspx” hämtas in från annat håll. Om man inte skyddar mot citationstecken här så är det möjligt att själv avsluta url:en och själv lägga till egna attribut. Det hela påminner till viss del om SQL Injections där man själv skriver om en SQL-sats genom att stoppa den vanliga.

Jag har nu det här formuläret:

<div>
    <fieldset>
        <legend>Osäker länk</legend>
        <p>
            <asp:Button id="btnLink" runat="server" Text="Skicka anrop" 
                onclick="btnLink_Click" />
        </p>
        <a id="hlLink" runat="server">Länk</a>
    </fieldset>
</div>

I Code Behind så sätts href-attributet på följande sätt:

protected void btnLink_Click(object sender, EventArgs e)
{
    string href = "SäkerSida.aspx\" onclick=\"location.href='http://www.msdn.se'; return false;";
    hlLink.HRef = href;
}

Om vi klickar på knappen nu och håller musen över länken så ser vi det här:

4 - Link

I statusfältet står det att vi kommer att skickas till SäkerSida.aspx. Men om vi nu klickar på länken så kommer vi istället till www.msdn.se, hur kan det komma sig?

Det som har renderats på sidan är:

<a href="SäkerSida.aspx" onclick="location.href='http://www.msdn.se'; return false;" id="hlLink">Länk</a>

Anledningen till att vi skickades till MSDN är för att vi har stoppat in ett onclick-event i länken, vilket inte syns från klienten. Vi kan alltså enkelt lura användaren genom att bara ange en URL som innehåller ett citationstecken.

Det bästa sättet att skydda sig mot detta är att helt enkelt se till att värdet är en giltig URL. De tecken som inte är giltiga för en URL skall ersättas till giltiga sådana. Här kommer vi att använda Anti-XSS Library.

För att använda det så räcker det med att sätta in en extra rad i Code Behind:

string href = "SäkerSida.aspx\" onclick=\"location.href='http://www.msdn.se'; return false;";
href = AntiXss.UrlEncode(href);
hlLink.HRef = href;

Det som renderas nu är:

<a href="
S%c3%a4kerSida.aspx%22%20onclick%3d%22location.href%3d%27http%3a%2f%2fwww.msdn.se%27%3b%20return%20false%3b
" id="hlLink">Länk</a>

Rendera säker HTML

Något som är väldigt vanligt och som växer explosionsartat är Content Management Systems. Med dessa så brukar det ofta följa med en Wysiwyg-editor där redaktören har möjlighet att posta in HTML som sedan visas direkt på sidan.

I och med att HTML kan injiceras så innebär det att även JavaScript kan följa med. Det bästa sättet att skydda sig mot detta är genom att inte tillåta HTML alls, men det ger även vissa begränsningar som kanske inte är önskvärda.

För att både tillåta HTML och skydda sig mot JavaScript så kan man använda en väldigt praktisk metod i Anti-XSS Library kallad GetSafeHtmlFragment().

För att testa metoden så använder vi koden nedan:

string html = "<b>Hello, world!</b> <script>alert('XSS!')</script> <i>Text...</i>";
html = AntiXss.GetSafeHtmlFragment(html);
ltHtml.Text = html;

Det som skrivs ut på skärmen är:

<b>Hello, world!</b> <i>Text...</i>

Det som hände var att Script-elementet försvann, vilket gjorde vår kod säker mot XSS. Det är en enkel metod för att täcka igen ett stort potentiellt säkerhetshål.

More Posts