Security
Het is waarschijnlijk niet nodig om uit te leggen dat het belangrijk is om aandacht te besteden aan beveiliging in applicaties. Aan de andere kant blijkt dat vrijwel iedere ontwikkelaar weet dat het zou moeten, maar dat slechts een aantal daadwerkelijk beveiligingsmaatregelen toepassen in de implementatie. De precieze reden laat zich moeilijk raden. Mogelijk is de stap van theorie naar praktijk een lastige om te nemen. Tijd om eens praktische informatie te krijgen om direct aan de slag te kunnen met het verbeteren van de beveiliging van je web applicaties.
Je zult in dit artikel leren hoe je een aantal van de security best practices voor ASP.NET web applicaties kunt toepassen. In het bijzonder kijken we naar het valideren van input en het encoden van output.
“All input is evil”
In dit artikel richten we ons met name op de aspecten van beveiliging van web-applicaties die samenhangen met data. Immers, zonder intern of extern dataverkeer is het onwaarschijnlijk dat een web-applicatie gecompromiteerd raakt. Vrijwel alle beveiligings-incidenten hangen samen met datastromen:
· Ongeauthorizeerde toegang tot data van een applicatie/systeem
· (Ongewenste) input naar de applicatie
· (Gemanipuleerde) output van de applicatie
· Geen toegang tot data van een applicatie
De meeste aanvallen op applicaties gebruiken lekken in de configuratie of implementatie die te maken hebben met de datastromen. Het is dus zaak om maatregelen te nemen die voorkomen dat dergelijke lekken bestaan en uitgebuit kunnen worden.
Een van de principes van beveiliging is “All input is evil”, oftewel: alle input naar een applicatie is bij voorbaat niet te vertrouwen. Indien input vereist is, dan is het nodig om de input te inspecteren en te valideren. De input kan van gebruikers komen of van andere systemen, zoals webservices of databases. Per bron zul je moeten evalueren of die data wel of niet te vertrouwen is. Idealiter inspecteer je de datastromen al in de design fase van je project. Het opstellen van een threat model is daarbij een geschikt instrument. Een tool als SDL Threat Modeling Tool helpt bij het opstellen van een dataflow diagram om inzichtelijk te maken hoe data stroomt tussen entiteiten (bijvoorbeeld gebruikers), databronnen en processen en waar de grenzen van wel en niet vertrouwde delen van het systeem liggen (trust-boundaries).
Input validatie is het middel om te toetsen of binnenkomende data aan de verwachtigingen van de applica voldoet. Bewust dan wel onbewust foutieve invoer zal door ontbrekende of gebrekkige input validatie makkelijk kunnen leiden tot beveiligings-issues. Veel aanvallen op web-applicaties verlopen succesvol door het omzeilen van input validatie, als die al aanwezig is. De meeste technieken uit de OWASP Top 10 maken gebruik van het aanbieden van corrupte, gemanipuleerde data aan een web applicatie. Denk daarbij aan Cross-site Scripting (XSS), SQL Injection en varianten die op de eerste zijn gebaseerd, zoals UI redressing en browser-zombies.
De security input validatie vertoont veel raakvlakken met de standaard validatie van invoervelden in een applicatie. Beiden controleren of de invoer overeenkomt met de juiste datatypes (string, boolean, integer, enz.) en of de lengtes en ranges correct zijn. Op een aantal facetten gaat input validatie voor security verder: het beschouwt ook de feitelijke inhoud van data, wat vooral geldt voor string waarden. De inhoud van de data mag geen potentieel gevaarlijke elementen bevatten. Denk daarbij aan script bij HTML of stukken SQL statement.
Als blijkt dat input mogelijk gevaarlijke inhoud heeft, dan kunnen er grofweg twee acties ondernomen worden:
1. Verwerpen van de data
De invoer is niet juist en wordt niet geaccepteerd. De gebruiker krijgt de kans te herstellen als dat mogelijk is. Een andere optie is het tonen van een errorpagina.
2. Filteren van de data
Als de data niet verworpen kan of mag worden, dan kun je de data ook filteren en schoonmaken door de foutieve, onveilige elementen te verwijderen. Dit process heet “sanitization” van de data.
Het is nog te overwegen om deze momenten te loggen, zodat er inzicht gegeven kan worden in het optreden van deze situaties, met de details van de verkeerde invoer.
Meerlaagse verdediging
Een tweede principe van beveiliging is de “multi-layered defense”, oftewel een meerlaagse verdediging. Hiermee wordt bedoeld dat het hebben van meerdere, elkaar aanvullende beveiligingsmaatregelen een goed uitgangspunt is. Want ook al is de input door de ene applicatie nog zo goed gevalideerd en gefiltered, dan hoeft dat nog niet voor data vanuit een ander systeem te gelden. Hier kun je ook input validatie toepassen, maar ooit zul je op een zo veilig mogelijke manier output willen produceren.
Daarom moet je ook de security best practice van output encoding toepassen, ook al heb je geconstateerd dat je alleen gevalideerde input accepteert en vertrouwde data gebruikt. Met output encoding ondervang je hopelijk ook nieuwe aanvalstechnieken, die nog niet bekend zijn tijdens het bouwen van de applicatie. Daarin heb je geen garanties, maar het volgen van de huidige best practices is je verstandigste zet. Dus: zowel input validatie als output encoding.
Standaard voorzieningen in ASP.NET voor input validatie en output encoding
De ASP.NET runtime heeft al een aantal voorzieningen die zowel input validatie als output encoding mogelijk maken. Je kunt deze zonder meer inzetten tijdens het ontwikkelen. Omdat deze functionaliteit in de runtime geboden wordt, kan ieder type ASP.NET applicatie hier gebruik van maken: van Web Forms tot MVC en Web Pages. Later zullen we kijken naar een aantal aanvullende mogelijkheden om verder te gaan waar de voorzieningen van ASP.NET ophouden.
Data validation controls en attributen
De set van Web Forms server controls in ASP.NET bevat validatie controls. De validatie wordt altijd server-side en optioneel client-side uitgevoerd. Een aantal van deze validatie controls kun je inzetten voor security input validatie:
· CompareValidator
Kan een aantal primitieve datatypes controleren: Currency, Date, Double, Integer en String
· RangeValidator
Voert net als CompareValidator datatypes controle uit, maar controleert ook of deze in een geldige range vallen
· RegularExpressionValidator
Gebruikt een op te geven reguliere expressie om de inhoud van string data te valideren
· CustomValidator
De client- en server-side validatielogica kun je zelf implementeren
ASP.NET MVC applicaties kunnen (ook) gebruik maken van model-validatie en de diverse attributen uit de System.ComponentModel.DataAnnotations assembly. Deze attributen kunnen validatie uitvoeren op datatypes, lengtes, ranges, of met behulp van reguliere expressies of met custom validatielogica. De functionaliteit lijkt daarin erg op de validatie server controls, maar is niet afhankelijk van het gebruik van Web Forms.
Request validation
De ASP.NET runtime controlleert standaard variabelen met (gebruiker)input in een HttpRequest instantie, zodra je ze gebruikt. Wanneer je bijvoorbeeld Request.Form of Request.QueryString of kortweg Request["..."] benadert, dan wordt gekeken of er mogelijk gevaarlijke elementen in de data staan. Er wordt onder andere gekeken naar open tags in de input, vormen van URLs en JavaScript.
Effectief betekent dit dat ingestuurde formulier waarden, querystring en cookie variabelen, waarden uit een certificaat en de “server variables” gescreend worden door ASP.NET. Request validatie staat standaard aan en werkt zonder dat je daar iets voor hoeft te doen.
De request validatie is uit te schakelen via de web.config of @Page directive. In de web.config schakel je de validatie voor alle pagina’s uit (of aan). Het attribuut EnableRequestValidation in het @Page directive bepaalt of voor die pagina validatie aanstaat of niet, ongeacht wat in de web.config wordt genoemd. Is er geen EnableRequestValidation attribute, dan wordt overgenomen wat in de web.config is genoemd.
<system.web>
<compilation debug="true" targetFramework="4.0" />
<pages validateRequest="true"
enableEventValidation="true"
viewStateEncryptionMode="Always"
enableViewStateMac="true" >
<controls> ... </controls>
</pages>
<httpRuntime enableHeaderChecking="true" />
</system.web>
Web.config sectie met de belangrijkste security instellingen voor input validatie
<%@ Page Language="C#" ValidateRequest="true" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
@Page directive met ValidateRequest attribuut voor uitschakelen van request validatie op pagina niveau
Het is nodig om request validatie uit te schakelen als je bijvoorbeeld van plan bent om opgemaakte HTML toe te staan als input. Content-management systemen, forums en blog sites laten je veelal WYSIWYG teksten opmaken met behulp van HTML markup. In dergelijke gevallen is het vereist dat de Request validatie voor die pagina uitgeschakeld wordt. Dit is een noodzakelijk kwaad, want security voorzieningen uitzetten is meestal geen goed idee. In dit geval heeft het een tweetal nadelen: je zult zelf de validatie van de betreffende input moeten uitvoeren èn je zult dat ook voor alle request input en controls moeten doen. Het uitschakelen van de request validatie doet dat voor alle controles op een pagina, van de genoemde request properties tot control properties. Je zult gebruik moeten maken van de andere vormen van validatie in ASP.NET. Daarbuiten houdt de ondersteuning vanuit ASP.NET op en zul je zelf maatregelen moeten treffen.
Output encoding
Encoding is bij de meeste web developers redelijk bekend door HTML. De HTML standaard kent verschillende vormen van encoding voor HTML elementen en attributen, URLs in HTML en URLs in formulieren. HTML heeft een aantal encodings voor de karakters die al in de standaard gebruikt worden (<, >, “ en &): < voor <, > " en &
Bij output encoding maak je bewust de keuze dat de data die je teruggeeft (als HTML of anderszins) een encoding moet hebben. Je voorkomt daarmee dat de input die je direct of indirect als output gaat teruggeven op enige manier onderdeel gaat uitmaken van de executie van de data. Bij HTML output zou input als HTML niet voor mogen komen. Namelijk, aIs dat wel mogelijk is, dan is daar een mogelijk Cross-site Scripting lek. Hetzelfde geldt voor SQL statements. Wanneer de input uit een applicatie gebruikt wordt als onderdeel van een SQL statement, bijvoorbeeld een zoekopdracht, dan mag de input nooit een actief deel van het uit te voeren statement worden. Zou dat wel zijn, dan kunnen op die plek mogelijk SQL injection aanvallen uitgevoerd worden.
Voor het geval van de eerder genoemde opmaak van content, blog of forum posts is het juist gewenst om de data met HTML elementen in de output te verwerken. Je wil dan wel zeker weten dat de data alleen uit veilige HTML bestaat en geen VBScript of JavaScript bevat. Daarvoor heb je de eerder genoemde input sanitization nodig. Daarover lees je zo meer.
De ASP.NET runtime biedt de mogelijkheid om encoding en decoding toe te passen voor HTML. De klasse HttpServerUtility heeft methoden voor URL en HTML encoding en decoding. Een instantie van de klasse wordt je aangereikt in de HttpContext van ieder web request.
Het onderstaande fragment doet geen input validatie en geen output encoding. Het is daarmee gevoelig voor onder andere XSS aanvallen.
string categoryID = Request.QueryString["CategoryID"];
categoryLabel.Text = categoryID;
customerNameLabel.Text = customer.Name;
myInvoices.NavigateUrl = "~/MyInvoices.aspx?StartDate=" +
DateTime.Now.Subtract(TimeSpan.FromDays(30)).ToString();
Om dit fragment veiliger te maken zullen we een aantal aanpassingen maken:
int categoryID = Int32.Parse(Request.QueryString["CategoryID"]);
categoryLabel.Text = categoryID.ToString();
customerNameLabel.Text = Server.HtmlEncode(customer.Name);
myInvoices.NavigateUrl = "~/MyInvoices.aspx?StartDate=" +
Server.UrlEncode(DateTime.Now.Subtract(TimeSpan.FromDays(30)).ToString());
Je ziet dat de input CategoryID gevalideerd wordt door het direct te converteren naar het juiste datatype (in dit geval een integer). Daardoor is het niet nodig om output encoding te doen bij de output ervan. De twee properties van het customer object worden opgenomen in HTML en als onderdeel van een URL. In beide gevallen moet encoding toegepast worden. Immers, een klantnaam kan vreemde tekens bevatten (zoals “V&D” of “in<entions”). Voor de startdatum geldt hetzelfde al is dat afhankelijk van de gebruikte locale.
Output encoding kun je ook syntactisch toepassen. In Web Forms bestaat sinds ASP.NET 4.0 het <@: element. De tag lijkt in functionaliteit op <@= met als grote verschil dat het automatisch encoding toepast op de geproduceerde output.
ASP.NET heeft dus veel voorzieningen ten aanzien van security en input validatie en output encoding in het bijzonder. Zoals eerder genoemd is het in sommige situaties nodig om (delen van) daarvan niet in te zetten of zelfs uit te schakelen. Bovendien richt het ASP.NET framework zich vooral op HTML en niet op SQL, LDAP of andere syntaxen. Het is niet eenvoudig om zelf input validatie en alle complexe vormen van encoding goed en veilig te doen en ook nog voor alle verschillende syntaxen. Je kunt het beste vertrouwen in en gebruik maken van een beproefde bibliotheek van een erkende bron, zoals de AntiXss bibliotheek.
AntiXss Library 4.0
De AntiXss library van Microsoft’s Information Security Team is toe aan versie 4.0 en biedt allerlei functionaliteit voor het valideren, filteren en encoden van data. De rest van dit artikel zul je zien hoe je de AntiXss library inzet voor het implementeren van input validatie en output encoding in ASP.NET applicaties, zoals bijvoorbeeld op momenten dat je request validatie hebt moeten uitschakelen.
De AntiXss library bestaat uit de volgende onderdelen:
· Encoder voor encoding van output
· Sanitizer voor het filteren van gevaarlijke input
· Security runtime engine
We zullen in meer detail naar de encoder en sanitizer kijken.
De AntiXss library heeft een tweetal assemblies die je moet refereren in je project dat gebruik gaat maken van de encoder of sanitizer. AntiXss 4.0 is te downloaden via http://wpl.codeplex.com. De library toevoegen aan je project kan ook via Add Library Package Reference. De Microsoft NuGet feed bevat de AntiXss 4.01 library.
De belangrijkste typen bevinden zich in de assemblies AntiXssLibrary.dll en HtmlSanitizationLibrary.dll. De typen bevinden zich allemaal in de namespace Microsoft.Security.Application.
De AntiXss implementatie kenmerkt zich door het hanteren van een whitelist bij het encoden en sanitizen van data. Whitelisting gaat uit van een beperkte toegestane set van syntax en grammatica. Deze set is een beproefde, veilige subset van alle mogelijke waarden. De tegenhanger van whitelisting is blacklisting. Daarin wordt juist een set van niet-toegestane waarden bijgehouden. Het nadeel van blacklisting is dat de set nooit zelden compleet is en lastig actueel te houden is.
Output encoding met AntiXss
De output encoding van de AntiXss library is gericht op HTML, LDAP, JavaScript en Visual Basic Script en Xml. Het doel van de encoding is om onveilige karakters en karakter-reeksen te noteren in een veiliger formaat. Anders dan bij sanitization worden deze karakters niet gefilterd of verwijderd. Ze zijn nodig, maar mogen niet resulteren in ongewenst gedrag van de applicatie.
De belangrijkste klasse voor output encoding is de Encoder. Deze klasse bevat allerhande statische methoden om encoding toe te passen voor een bepaalde doelsyntax.
|
Encoding soort
|
Methode
|
Omschrijving
|
|
HTML
|
HtmlEncode
HtmlAttributeEncode
|
Encoding van tekst, zodat deze opgenomen kunnen worden in HTML, zonder zelf markup te bevatten.
|
|
URL
|
UrlEncode
HtmlFormUrlEncode
|
Encoding van tekst om op te nemen als onderdeel van een URL, of URLs in de action van een <form> HTML element
|
|
LDAP
|
LdapEncode
LdapFilterEncode
LdapDistinguishedNameEncode
|
Encoding van tekst om te gebruiken als waarden in Lightweight Directory Access Protocol queries, filter queries of in distinguished names (DN)
|
|
Script
|
JavaScriptEncode
VisualBasicScriptEncode
|
Encoding van tekst zodat dit opgenomen kan worden in JavaScript of VBScript, zonder dat het een actief deel van het script wordt.
|
|
Xml
|
XmlEncode
XmlAttributeEncode
|
Encoding van tekst voor gebruik in XML resp. Attributen
|
Je kunt het volgende stappenplan aanhouden voor het toepassen van output encoding:
1. Bepaal of de data die je wilt gebruiken encoding nodig heeft.
2. Is encoding al eerder toegepast?
Zo ja, dan is encoding wellicht niet nodig. Dubbele encoding kan leiden tot ongewenste resultaten.
3. Kies het type data waarmee je gaat werken
Daarbij is van belang om na te gaan of de data wel of geen deel mag uitmaken van de uiteindelijke semantiek.
4. Encodeer de data met de meest specifieke encoding methode
Laten we een voorbeeld bekijken om een en ander te illustreren: in een scherm van een webshop-applicatie wordt een melding in de browser gegeven als men een artikel uit het winkelmandje wil verwijderen. We gebruiken voor het tonen van de melding Javascript, dat server-side wordt samengesteld. Het moet de naam van een product uit de winkelwagen tonen als onderdeel van de melding. De code zag er als volgt uit.
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack &&
!ClientScript.IsClientScriptBlockRegistered("Prompt"))
{
string scriptTemplate =
"alert('Are you sure you want to delete {0}');";
string script =
String.Format(scriptTemplate, basket.Products[0].Name);
ClientScript.RegisterClientScriptBlock(
this.GetType(),
"Prompt",
script,
true);
}
}
De aanname dat de data te vertrouwen is, is gevaarlijk. Dit zou betekenen dat we zeker weten dat de productnaam ooit gevalideerd en gefiltered is op mogelijk JavaScript (fragmenten). We willen niet alleen voorkomen dat het script breekt door rare karakters, maar ook dat er geen veiligheidslek ontstaat dat uit te buiten is.
Als we de stappen uit het plan volgen, dan komen we uit op:
1. Ja, encoding is nodig (principe van multilayered defense)
2. Nee, nog niet eerder toegepast (zelf encoding toepassen)
3. De data nemen we op in JavaScript
4. De juiste methode is JavaScriptEncode
De aanpassing voor het toepassen van de encoding is als volgt:
string script = String.Format(scriptTemplate,
Encoder.JavaScriptEncode(basket.Products[0].Name));
Op soortgelijke manieren zul je ook voor HTML, VBScript, XML en URLs encoding moeten toepassen. Dit is vooral nodig als je de input opneemt als onderdeel van deze syntaxen.
De encoder die gebruikt wordt door de ASP.NET runtime is sinds ASP.NET 4.0 te configureren. AntiXss 4.1 (nu nog in beta) bevat een nieuwe encoder voor ASP.NET die gebruik maakt van de AntiXss encoding. Hierdoor kun je AntiXss encoding integraal toepassen, met behulp van expliciete aanroepen via de Microsoft.Security.Applications.Encoder klasse en impliciet via de HttpServerUtility.UrlEncode en HtmlEncode, <%: en @ syntax.
<system.web>
<httpRuntime
encoderType=
"Microsoft.Security.Application.AntiXssEncoder,
AntiXssLibrary"
/>
Door deze bovenstaande, eenvoudige aanpassing in de web.config gaat ASP.NET de AntiXss encoder klasse gebruiken. Let wel, het encoden van data moet nog steeds bewust afgewogen en gedaan worden.
Input validation en sanitization
De AntiXss library heeft geen voorzieningen om input validatie uit te voeren. Daarvoor ben je aangewezen op de middelen uit het ASP.NET framework: de eerder genoemde validatie controls zijn of de data annotaties in MVC. Andere mogelijkheden zijn de Enterprise Library Validation Application Block of je eigen validatielogica. De laatste verdedigingslinie is de ASP.NET request validatie.
Nadat input validatie is gedaan blijven er situaties over dat je wellens en wetens met potentieel onveilige data om wilt gaan. Deze situaties vragen om het veilig maken van de data. AntiXss biedt een sanitization implementatie voor (alleen) HTML in de HtmlSanitizationLibrary assembly. De Sanitizer klasse heeft twee methoden voor het destileren van veilige HTML op basis van een volledige HTML pagina of een HTML fragment: GetSafeHtml en GetSafeHtmlFragment.
Laten we naar nog een voorbeeld kijken. Een webapplicatie biedt een forum aan waarop aangemelde gebruikers reacties kunnen achterlaten. De reacties mogen HTML markup bevatten. Jouw opdracht is om dit veilig te implementeren met behulp van Web Pages en Razor.
De pagina bevat de volgende markup:
<form method="post" action="">
<label for="Name">Type your comment:</label>
@Html.TextArea("Comment")
…
</form>
Bij een postback van het formulier wordt de input uitgelezen. Deze bevat HTML markup en is daarmee mogelijk gevaarlijk. In het geval van een implementatie met Web Forms zou het nodig zijn geweest om request validatie uit te schakelen voor de hele pagina. Bij het Web Pages Framework kun je individuele velden rechtstreeks uitlezen zonder request validatie. Het is daarna noodzakelijk om de HTML sanitization toe te passen. Het volgende codefragment laat zien hoe je dat doet:
@{
if (IsPost)
{
<text>Thanks for your contribution!<br /></text>
string inputWithHtml =
Request.Unvalidated().Form["Comment"];
string sanitizedInput =
Sanitizer.GetSafeHtmlFragment(inputWithHtml);
@Html.Raw(sanitizedInput)
}
}
De aanroep van Sanitizer.GetSafeHtmlFragment voert de filtering uit op de HTML volgens het whitelisting principe. Daarna kan de gefilterde input veilig getoond worden op de pagina, inclusief de toegestane HTML elementen. Razor heeft daarvoor de @Html.Raw helper methode, omdat @ automatisch encoding toepast.
Samenvatting
Het bouwen van veilige webapplicaties begint met de eerste stappen van het daadwerkelijk doen. Het volgen van security principles en best practices is essentieel. “All input is evil” en “multilayered defense” vragen om het toepassen van input validatie, sanitization en output encoding. De standaard voorzieningen van ASP.NET kunnen validatie en encoding van HTML doen. Voor andere syntaxen als SQL, LDAP, XML en script talen zul je extra middelen moeten inzetten. De AntiXss bibliotheek van Microsoft’s Information Security Tools team gaat je helpen om dit op een beproefde, geteste manier te doen. De bibliotheek haakt in op ASP.NET en vult deze aan. Bij elkaar opgeteld zullen de maatregelen die je hiermee treft je applicatie veiliger maken.