ASPNL logo (1 kb)
Tuesday, September 07, 2010




Microsoft MVP

.NET Codewise Community
<< vorige | overzicht | volgende >>

.NET 2.0: Beveiliging in .NET 2.0

Door Michiel van Otegem
4 oktober 2008

.NET wordt door Microsoft geprofileerd als het platform voor het bouwen van "connected systems", applicaties die op allerlei manieren met elkaar verbonden zijn. Voor het succes daarvan is de beveiliging uitermate belangrijk. De mogelijkheden die .NET daarvoor biedt zijn dan ook zeer uitgebreid.

Beveiliging in .NET bestaat uit een aantal onderdelen. Het meest voor de hand liggend is beveiliging op basis van gebruikers en rollen, zoals dat in elk modern operating system geregeld is. Een applicatie of (web) service kan toegankelijk gemaakt worden voor bepaalde gebruikers of rollen door gebruik te maken van de standaard bestandpermissies van Windows. Dit is echter een betrekkelijk grove manier van beveiliging, iemand heeft geen toegang of helemaal niet. In een .NET applicatie kun je echter in de code aangeven welke gebruikers of rollen toegang hebben tot een component, object, of functie. Dit kan zowel op basis van Windows accounts aan de hand van de WindowsPrincipal die automatisch beschikbaar is als een applicatie uitgevoerd wordt. Het kan echter nodig zijn om juist geen gebruik te maken van Windows accounts, maar te werken met een eigen database met gebruikers en rollen. In dat geval moet je zelf de authenticatie van de gebruiker verzorgen, bijvoorbeeld aan de hand van een gebruikersnaam en wachtwoord combinatie, of via alternatieven als een smart card of biometrische apparatuur. Het autoriseren van de gebruikers vindt dan niet plaats op basis van de WindowsPrincipcal, maar aan de hand van de GenericPrincipal. In Afbeelding 1 staat een functie die gebruikt wordt om een gebruiker in te loggen na het invullen van een gebruikersnaam en wachtwoord. Deze worden eerst gecontroleerd. Hier expliciet op een bepaalde waarde, maar normaal gezien zou dat aan de hand van een database of andere gegevensbron werken. Vervolgens worden de rollen bepaald van de gebruiker (ook voor het voorbeeld weer expliciet), waarna een GenericPrincipal wordt aangemaakt. Die bestaat uit de identiteit die geverifieerd is door de gebruikersnaam en het wachtwoord te controleren, en de rollen doe toegekend zijn. Als dit gebeurd is, wordt de principal toegekend aan de Thread die de applicatie uitvoert, zodat deze bij het aanroepen van iedere functie beschikbaar is.

 1: Public Function Login(ByVal username As String,\
                          ByVal password As String) As Boolean
 2:    'Naam en wachtwoord controleren (bijvoorbeeld met database)
 3:    If username <> "Michiel" Or password <> "wachtwoord" Then
 4:       'Naam of wachtwoord onjuist
 5:       Return False
 6:    End If
 7:  
 8:    'Rollen bepalen (bijvoorbeeld uit database halen)
 9:    Dim roles As String() = {"Gebruiker","Management"}
10:  
11:    'Identiteit maken en nieuwe security principal maken
12:    Dim userIdentity As New GenericIdentity(username)
13:    Dim userPrincipal As New GenericPrincipal(userIdentity, roles)
14:  
15:    'Principal toekennen aan uitvoerende thread
16:    AppDomain.CurrentDomain.SetPrincipalPolicy(
          PrincipalPolicy.UnauthenticatedPrincipal)
17:    Thread.CurrentPrincipal = userPrincipal
18:  
19:    'Status OK retourneren
20:    Return True
21: End Function

Afbeelding 1, Visual Basic code voor het aanmaken van een GenericPrincipal

Op basis van een principal, of dit nu een WindowsPrincipal, GenericPrincipal, of een ander type principal is, kan in code geverifieerd worden of de gebruiker toegang verleend moet worden. Een voorbeeld hiervan zie je in de functie KnoppenInstellen in Afbeelding 2, waarin knoppen zichtbaar worden gemaakt aan de hand van de daarvoor benodigde rol. Hoewel dat werkt, en in dit geval een handige manier is om de gebruikersinterface aan te passen aan de rol van de gebruiker, kan dit voor functies handiger, zoals gedaan is bij de SluitBoekJaar functie. Daar wordt aan de hand van een attribuut opgegeven wie toegang heeft tot de functie. De Common Language Runtime (CLR) verzorgt vervolgens de controle. Deze manier van instellen noemen we declaratief, omdat we aangeven wat we willen. Hoe de controle plaatsvindt is niet van belang. In KnoppenInstellen wordt de controle echter expliciet door de code gedaan.

 1: Friend Sub KnoppenInstellen()
 2:    MaakRapportButton.Visible = 
                     Thread.CurrentPrincipal.IsInRole("Management")
 3:    SluitBoekJaarButton.Visible =
                     Thread.CurrentPrincipal.IsInRole("Administratie")
 4: End Sub
 5:  
 6:  
 7: <PrincipalPermissionAttribute(
     SecurityAction.Demand, Role := "Administratie")>
 8: Friend Sub SluitBoekJaar()
 9:    'Boekjaar sluiten (niet getoond)
10: End Sub

Afbeelding 2, Visual Basic code voor het controleren van een rol

Het voordeel van de beveiliging op basis van principals in .NET biedt mogelijkheden om de beveiliging veel fijnmaziger op te zetten, omdat je per component, object, of functie kunt bepalen wie er wel of geen toegang toe hebben. Zo kan dezelfde applicatie, zonder aanpassingen, gebruikt worden door verschillende gebruikers, terwijl ze verschillende rechten hebben. In een urenadministratie applicatie kun je bijvoorbeeld reguliere gebruikers alleen toegang geven tot het invoer scherm waarin ze hun uren moeten invullen. Administratief personeel kan via dezelfde applicatie de eigen uren invullen, maar krijgt ook toegang tot de administratieve mogelijkheden. Het management tenslotte heeft toegang tot de rapportage, maar heeft niet de administratieve mogelijkheden die het administratieve personeel heeft. De mogelijkheid om applicaties op deze manier op te kunnen zetten vereenvoudigt zowel de ontwikkeling van de applicatie als de uitrol ervan, omdat er maar één applicatie nodig is voor de verschillende doelgroepen. Bovendien kan de toegang tot de onderdelen van de applicatie centraal geregeld worden, in plaats van op ieder werkstation, en kan iemand ongeacht zijn/haar rol ieder werkstation gebruiken.
An sich is het mechanisme van principal gebaseerde beveiliging niet nieuw in .NET. In versie 1.x is dit ook al aanwezig, maar er zijn een aantal verbeteringen. Zo kun je nu zowel bij het Windows account van de gebruiker op basis van de gebruikersnaam, of op basis van de interne binaire representatie van Windows, en kun je de ene naar de andere "vertalen". Dit is bijvoorbeeld handig in gedistribueerde applicaties, waarbij je niet wilt dat er account gegevens over de lijn gaan. Dan is de binaire representatie een stuk veiliger. Afbeelding 3 geeft hier een simpel voorbeeld van.

 1: 'Binaire representatie ophalen
 2: Dim sid As SecurityIdentifier;
 3: sid = WindowsIndentity.GetCurrent().User;
 4:  
 5: 'Vertalen naar NT account
 6: Dim account As NTAccount;
 7: account = CType(sid.Translate(TypeOf(NTAccount)), NTAccount)
 8:  
 9: 'NT account domein en naam weergeven
10: MessageBox.Show(account.Value)

Afbeelding 3, Visual Basic code voor het vertalen tussen de verschillende account representaties

Een andere verbetering is de ondersteuning voor beveiliging op "objecten", zoals het bestandssyteem en het Windows register. Wanneer de gebruiker voldoende rechten heeft kunnen die rechten zonodig gemanipuleerd worden voor anderen. Een laatste verbetering heeft betrekking op de situatie waarin operaties uitgevoerd worden onder de context van een andere gebruiker (impersonation). In versie 1.x is het niet mogelijk om te weten dat er sprake is van impersonation, tenzij je met unmanaged code aan de slag gaat door een Win32 API aan te roepen. Dat is nu verholpen doordat je aankunt geven of je de context gebruiker of de eigenlijke gebruiker wilt hebben.

Code Access Security

Op basis van de gebruiker bepalen wat er uitgevoerd mag worden is erg belangrijk, maar aan dat model ontbreekt nog wel een stuk. Op basis van die beveiliging alleen kunnen we niet voorkomen dat kwaadaardige code uitgevoerd wordt, en zijn systemen onder andere vatbaar voor virussen en spyware. Voor de komst van .NET was hier weinig tegen te doen, omdat alle applicaties bestaan uit binaire executables. Alleen op basis van herkomst en/of aan de hand van een certificaat kun je bepalen of je de betreffende applicatie of component wilt vertrouwen. .NET lost dit op doordat er sprake is van "managed code", code die gecontroleerd wordt door de CLR. Op basis van de meta data die bij een component of applicatie hoort kan de CLR bepalen of het veilig is om deze uit te voeren. Om te beginnen bevat .NET hiervoor enkele voorgedefinieerde "permission sets" waarbinnen een component wordt uitgevoerd. De Internet permission set zie je in Afbeelding 4. De rechten van deze permission set zijn beperkt tot een afgeschermde opslag (Isolated Storage File), enkele user interface mogelijkheden, en printen. Alle andere zaken, zoals bijvoorbeeld toegang tot de Event Log, toegang tot de benodigde onderdelen om een database te benaderen, en toegang tot het bestandssysteem zijn niet beschikbaar voor die permission set. Daarmee hebben alle applicaties van het internet, niet alleen die in de context van Internet Explorer werken, maar beperkte speelruimte. Pas als je een gedownloade applicatie kopieert naar een andere locatie op de harde schijf krijgt deze de volledige rechten.


Afbeelding 4, Voorbeeld van Internet permission set

Het principe van permission sets is niet echt nieuw. De Java Virtual Machine (JVM) maakt hier ook gebruik van evenals de eerdere .NET versies. Ten opzichte van versie 1.x zijn er uiteraard een aantal zaken verfijnd. .NET biedt de mogelijkheid om zelf permission sets te definiëren, en biedt ontwikkelaars de mogelijkheid om in de code aan te geven welke rechten het nodig heeft. Vandaar dat ook gesproken wordt over Code Access Security, omdat bepaald wordt welke rechten de code heeft, onafhankelijk van welke gebruiker de applicatie uitvoert. Wanneer een ontwikkelaar aangeeft welke rechten een component nodig heeft, kan tijdens de installatie, of tijdens het uitvoeren van een applicatie gecontroleerd worden of de benodigde rechten wel aanwezig zijn. Zo niet, dan kan de gebruiker hiervan op de hoogte gebracht worden. De gebruiker kan dan eventueel kiezen om een andere permission set toe te kennen aan de applicatie, zodat deze wel de benodigde rechten heeft. Het is dan ook een best practice voor ontwikkelaars om componenten te ontwikkelen op basis van “least privile”, oftewel zorgen dat een component alleen maar om die rechten vraagt die het ook daadwerkelijk nodig heeft. De beste manier om dit voor elkaar te krijgen is om te beginnen met een component dat geen enkele rechten heeft, en te bepalen welke rechten toegevoegd moeten worden om de component te kunnen uitvoeren. De .NET 2.0 Software Developer Kit bevat permview.exe, waarmee ontwikkelaars kunnen bepalen welke rechten een component nodig heeft op het moment dat dit nog niet gespecificeerd is. Visual Studio 2005 bevat een uitgebreidere versie in de vorm van permcalc.exe. Het nadeel van deze tools is dat ze een hele bak met informatie over je heen storten waar je wel even wijs uit moet worden.
In de code kan op verschillende manieren aangegeven worden wat er nodig is om de code uit te kunnen voeren. Op component (assembly) niveau kan aangegeven worden wat er voor de component als geheel geldt. Hierbij kan gerefereerd worden aan een van de ingebouwde permissions sets, als volgt:

<assembly: PermissionSetAttribute(SecurityAction.RequestMinimum,
 Name="Internet">

Ook kun je per sort permissie aangeven dat die nodig is, bijvoorbeeld zoals hieronder voor toegang tot een specifiek tekst bestand.

<assembly: FileIOPermissionAttribute(SecurityAction.RequestMinimum,
  ViewAndModify="c:\voorbeeld\voorbeeld.txt">

In de bovenstaande voorbeelden wordt met SecurityAction.RequestMinimum aangegeven dat dit een minimum vereiste is om de applicatie uit te kunnen voeren. Als alternatief kun je ook aangeven dat deze permissie optioneel is. Daarmee geef je aan dat de applicatie beter werkt als het wel mag, maar ook werkt als dat niet zo is. Interessant is ook de optie om juist aan te geven dat een component een bepaalde permissie niet nodig heeft. Het voordeel hiervan is dat in het geval van een beveiligingsprobleem je heel snel kunt bepalen welke componenten in elk geval niet verantwoordelijk zijn voor het probleem. De componenten die de permissies niet hebben om de gewraakte operatie uit te voeren kunnen meteen uitgesloten worden. Bedrijven die componenten ontwikkelen voor andere partijen, die eventueel ook gebruikt worden in combinatie met componenten die zij zelf niet gemaakt hebben kunnen zich hier enorm veel ellende (aansprakelijkheid) besparen.

Cryptografie

Als gevoelige gegevens niet versleuteld zijn, dan kunnen andere daar onherroepelijk bij. Op welke manier je een applicatie dan ook beveiligd, uiteindelijk heeft dat weinig nut. Je zult de gegevens in die gevallen dus moeten versleutelen. .NET bevat een heel scala aan mogelijkheden als het gaat om cryptografie. Hieronder valt Public Key Encryption (PKE), hashing, en andere vormen van cryptografie. Een van de nadelen in .NET 1.1 is dat je de key (sleutel) direct gebruikt. Er is geen ondersteuning voor X509 certificaten. In .NET 2.0 is dit verholpen en kun je gebruik maken van de certificaten in de X509 Certificate Store.
Een handige en eenvoudige toevoeging aan het .NET Framework is verder de ondersteuning voor zogenaamde secure strings. Normaal gezien wordt string data in het geheugen gezet op een manier die leesbaar is voor andere applicaties. Iemand die dus in het geheugen kan snuffelen zou wel eens op gevoelige data, waaronder wachtwoorden, kunnen stuiten. De secure string zorgt ervoor dat dit niet kan door de strings versleuteld in het geheugen op te slaan. Dit brengt uiteraard wel wat overhead met zich mee, maar beveiliging mag wat kosten, niet?
Op het vlak van Web Services is de beveiliging ook uitgebreid met mogelijkheden om gegevens te versleutelen. Deze maakten al onderdeel uit van de Web Services Enhancements die een tussentijdse oplossing bieden voor Web Services technologie die standaard nog geen deel uit maakt van het .NET Framework. Alles wat tot nog toe onder Web Services Enhancements is uitgebracht maakt onderdeel uit van .NET 2.0.

Remoting, ASP.NET en ClickOnce

Een van de nadelen van de meeste beveiliging is dat deze in applicaties zelf verwerkt zit. Het is daardoor niet mogelijk om de manier waarop een applicatie beveiligd is te veranderen wanneer een applicatie eenmaal uitgerold is. In .NET 2.0 geldt dit voor een aantal onderdelen niet, en dan met name ASP.NET en .NET Remoting. Remoting wordt gebruikt voor gedistribueerde applicaties waarbij bepaalde functionaliteit door een andere server (of servers) verzorgd wordt. Een belangrijk aspect daarvan is uiteraard de beveiliging van de communicatie tussen de servers. In .NET 1.1 kon dit eigenlijk alleen maar door de functionaliteit onder Internet Information Server (IIS) aan te bieden. De beveiliging van IIS kan dan gebruikt worden, maar je bent wel gebonden aan communicatie via HTTP. Wil je dit niet, dan moet je de beveiliging helemaal zelf verzorgen en ben je wel even zoet. Met .NET 2.0 is dit verleden tijd doordat Remoting nu een eigen faciliteit biedt voor zowel authenticatie als de encryptie van het communicatiekanaal. Omdat dit in het configuratiebestand geregeld kan worden kan dit ook als de applicatie geïnstalleerd is nog gewijzigd worden. Wat dat betreft is dit al een beetje een voorschotje op het communicatiemechanisme in Windows Vista dat bekend staat onder de codenaam Indigo. Indigo moet alle bestaande mechanismen in Windows vervangen en in Indigo is vrijwel ieder onderdeel van de interactie tussen twee punten te configureren, van transport mechanisme tot ondersteuning van transacties en beveiliging.
Wat betreft het kunnen configureren van de beveiliging slaat ASP.NET 2.0 echt wel alles. De beveiliging van een web applicatie kan geheel geregeld worden in het configuratiebestand web.config, maar je hoeft dat bestand nooit aan te raken. Via de Web Site Administration Tool, een administratieve web pagina, kan de beveiliging tot in de puntjes geregeld worden. Je kunt daarbij gebruik maken van Windows beveiliging, Passport beveiliging, en de ingebouwde beveiliging op basis van een database. Als je wilt kun je ook zelf een mechanisme implementeren. Zolang je daarbij de richtlijnen van de zogenaamde Membership API volgt kun je deze via de configuratie instellen zonder dat daarvoor ook maar een regel code in de applicatie zelf aangepast hoeft te worden. Dit is omdat de implementatie van de Membership API losgekoppeld is via het Provider Model Design Pattern. De code in de applicatie maakt gebruik van de Membership API, en is niet afhankelijk van de onderliggende implementatie. Dit biedt allerlei mogelijkheden voor derden om een custom oplossing te maken die voor iedereen werkt.

Meer beveiliging

Dit artikel is uiteraard maar een vluchtige blik op wat .NET 2.0 allemaal te bieden heeft op het gebied van beveiliging. Wie meer wil weten zal de beschikbare documentatie in moeten duiken. Een goed startpunt is altijd http://msdn.microsoft.com/security maar wie echt al in de (pre-release) documentatie van .NET 2.0 wil snuffelen kan beter kijken op //msdn2.microsoft.com/library/9w6t6kwc(en-us,vs.80).aspx. Verder zijn er voor ASP.NET al meerdere tutorials te vinden die een overzicht geven van de beveiliging en laten zien hoe het stap voor stap werkt. Je kunt deze tutorials vinden op http://beta.asp.net/QUICKSTART/aspnet/doc/security

Dit artikel is eerder verschenen in NetOpus, september 2005.
<< vorige | ^ naar boven | overzicht | volgende >>
copyright 2000-2007 ASPNL