Skydda dig mot XSS

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.

6 Comments

Comments have been disabled for this content.