29 maart 2016

Beveilig je API in ASP.NET

koppeling

Web services maken een groot deel uit van het dagelijkse data verkeer tussen servers en clients. Veel Applicaties op bijvoorbeeld een smartphone hebben toegang tot het internet nodig om data op te halen of bepaalde services aan te kunnen bieden. Dit kan gebruikersspecifieke data zijn of data die voor iedereen beschikbaar wordt gesteld maar ook informatie over betalingen.

Vaak wordt er te makkelijk gedacht over de security van een web service, met als gevolg dat de integriteit van de data niet gegarandeerd is. In het ergste geval worden er privacygevoelige data gestolen. Dit mag natuurlijk niet gebeuren. Bovendien kan dit -door de vele manier om je web service te het beveiligen en de complexiteit van deze oplossingen- heel verwarrend en overweldigend worden.

Wij willen daarom dat iedereen bewuster wordt van de security van web services. Ik zal een aantal voorbeelden geven wat men allemaal kan doen om een web service wat beter te beveiligen.

Waarom Security

Er wordt vaak misbruik gemaakt van slecht beveiligde web services, waardoor bijvoorbeeld gebruikersgegevens of andere belangrijke data gestolen kan worden. Voorbeelden van mogelijke bedreigingen zijn:

  • Ongeautoriseerde toegang door zwakke authenticatie en/of autorisatie
  • Parameter manipulatie
  • Netwerk afluisteren met behulp van netwerk monitoring software

Tegen het gros van dit soort aanvallen valt de web service goed te beveiligen. 

Soorten Security

Er zijn onwijs veel soorten manieren om een web service te beveiligen. In dit artikel beperk ik mij tot alleen de bovengenoemde bedreigingen. Verder zal ik alleen de meest gebruikte methodes uitlichten.

Om ongeautoriseerde toegang te voorkomen heb je een goede authenticatie/autorisatie nodig op je web service. Voorbeelden van veel gebruikte methodes zijn HTTP Basic Authentication, Forms Authentication, Integrated Windows Authentication en OAuth. Elk van deze methodes heeft voor- en nadelen en welke je kan gebruiken is afhankelijk van je web service.

Om parameter manipulatie te voorkomen is het verstandig om gebruik te maken van Model Validation. Hiermee wordt er direct een validatie uitgevoerd op de website, voordat je er iets mee gaat doen. De validatie wordt gedaan op basis van de data die je van de client krijgt.

Veel Authenticatie methodes zijn vaak niet veilig als je dit over plain HTTPS verstuurd. Voorbeelden van methodes die ook in dit artikel worden uitgelegd zijn basic Authentication en Forms Authentication. Bij deze methodes worden je gebruikersnaam en wachtwoord ongecodeerd mee verstuurd in elke request, waardoor het eenvoudig kan worden onderschept. Om je hier tegen te beveiligen moet je SSL authenticatie gebruiken.

Autorisatie en Authenticatie

Voordat ik dieper in ga op methodes om autorisatie en authenticatie toe te passen op een web service, ga ik eerst even het verschil uitleggen tussen deze twee:

  • Authenticatie is het kennen van de identiteit van de gebruiker. Bijvoorbeeld, Alice logt in met haar gebruikersnaam en wachtwoord. Deze gegevens worden vervolgens gebruikt om te kijken wie het is.
  • Autorisatie is het beslissen of een gebruiker toegang heeft tot een bepaalde functionaliteit of bepaalde data.

Hieronder leg ik 1 manier uit om Autorisatie/Authenticatie toe te passen op een web service.

Basic Authentication

Dit is een van de simpelste vormen van Authenticatie. In combinatie met SSL is dit een goede oplossing voor web services die weinig functionaliteit nodig hebben buiten het registreren van gebruikers en gebruikersspecifieke data.

Voordelen:

  • Het is een Internet standaard
  • Het wordt door alle grote browsers en smartphones operating systems ondersteund.
  • Simpele protocol
  • Relatief makkelijk te implementeren

Nadelen:

  • Stuurt gebruikersnaam en wachtwoord mee in elke request.
  • Gebruikersnaam en wachtwoord worden verstuurd in plain tekst.

Hoe werkt het?

  1. Zodra de client een request naar de web service doet en er is authenticatie nodig dan geeft de web service Unauthorizedterug. De response bevat een WWW-Authentication header wat aangeeft dat de web service basic authentication ondersteund.

  2. De client kan nu een tweede request sturen met de gebruikersnaam en wachwoord in de Authorisation header. De credentials moeten in het volgende format gestuurd worden “name:password”, base64-encoded.

In basic authentication wordt er gebruik gemaakt van realms. Zodra de server een response terug geeft dan wordt hier meteen de naam van de realm mee gegeven. De gebruikersnaam en wachtwoord zijn alleen geldig in die specifieke realm. In de server kan je met een of meerdere realms je web service opdelen.

 C:\Users\Ferdi\Desktop\webapi_auth04.png

Het valideren van gebruikers gaat in veel gevallen via een memberShip provider. Hier is een voorbeeld van een membership provider:

namespace WebHostBasicAuth.Modules
{
    public class BasicAuthHttpModule : IHttpModule
    {
        private const string Realm = "RealmName";

        public void Init(HttpApplication context)
        {
            context.AuthenticateRequest += OnApplicationAuthenticateRequest;
            context.EndRequest += OnApplicationEndRequest;
        }

        private static void SetPrincipal(IPrincipal principal)
        {
            Thread.CurrentPrincipal = principal;
            if (HttpContext.Current != null)
            {
                HttpContext.Current.User = principal;
            }
        }

        // Hier kan je vervolgens de gebruikersnaam en password controlleren
        private static bool CheckPassword(string username, string password)
        {
            return username == "user" && password == "password";
        }

        private static bool AuthenticateUser(string credentials)
        {
            bool validated = false;
            try
            {
                var encoding = Encoding.GetEncoding("iso-8859-1");
                credentials = encoding.GetString(Convert.FromBase64String(credentials));

                int separator = credentials.IndexOf(':');
                string name = credentials.Substring(0, separator);
                string password = credentials.Substring(separator + 1);

                validated = CheckPassword(name, password);
                if (validated)
                {
                    var identity = new GenericIdentity(name);
                    SetPrincipal(new GenericPrincipal(identity, null));
                }
            }
            catch (FormatException)
            {
                // Credentials were not formatted correctly.
                validated = false;

            }
            return validated;
        }

        private static void OnApplicationAuthenticateRequest(object sender, EventArgs e)
        {
            var request = HttpContext.Current.Request;
            var authHeader = request.Headers["Authorization"];
            if (authHeader != null)
            {
                var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);

                // RFC 2617 sec 1.2, "scheme" name is case-insensitive
                if (authHeaderVal.Scheme.Equals("basic",
                        StringComparison.OrdinalIgnoreCase) &&
                    authHeaderVal.Parameter != null)
                {
                    AuthenticateUser(authHeaderVal.Parameter);
                }
            }
        }

	//Als de request unauthorized is dat wordt de WWW-Authenticate header 
	//aan de response toegevoegt
        private static void OnApplicationEndRequest(object sender, EventArgs e)
        {
            var response = HttpContext.Current.Response;
            if (response.StatusCode == 401)
            {
                response.Headers.Add("WWW-Authenticate",
                    string.Format("Basic realm=\"{0}\"", Realm));
            }
        }

        public void Dispose() 
        {
        }
    }
}

Als je dit wilt gebruiken moet je dit wel even aan geven in je Web.Config bij de system.webServer section. Voeg hier het volgende stukje toe:

<system.webServer>
   <modules>
     <addname="BasicAuthHttpModule"
       type="WebHostBasicAuth.Modules.BasicAuthHttpModule, BasicAuth"/>
   </modules>

Zodra er nu in een controller action de [Authorize] attribute wordt geplaatst heb je een werkende basic authentication opgezet.

Model Validation

In ASP.NET Web Api kan je gebruik maken van attributen uit de namespace System.ComponentModel.DataAnnotations. Met deze attributen kunnen er bepaalde voorwaarde en beperkingen worden gegeven aan de properties in je data modellen. Neem bijvoorbeeld het volgende model:

public class Product
{
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    public decimal Price { get; set; }
    [Range(0,999)]
    public double Weight { get; set; }
}


Hier wordt door middel van de ‘Required’ attribute aangegeven dat de property niet null mag zijn. Verder wordt er met het ‘Range’ attribuut aangegeven dat Weight tussen de 0 en de 999 moet liggen.

Als je dit wil controleren in je controller doe je dit met behulp van de ModelState Class. Met de IsValid method kan je vervolgens kijken of de verstuurde data van de client voldoet aan de gestelde voorwaarde in Product.

public HttpResponseMessage Post(Product product)
{
    if (ModelState.IsValid)
    {
        // Doe iets met het product

        return new HttpResponseMessage(HttpStatusCode.OK);
    }
    else
    {
        return new HttpResponseMessage(HttpStatusCode.BadRequest);
    }
}

Er zijn veel situaties waarin je eigenlijk niet wil dat clients één op één database models meesturen in een request. Een simpel voorbeeld hiervan is met een UserProfile model waarin een IsAdmin property staat:

publicclassUserProfile
{
   publicstringName { get; set; }
   publicUriBlog { get; set; }
   publicboolIsAdmin { get; set; }  // not good
}
{"Name":"Gizmo", "Blog":"http://www.tweakers.net/"}

 Zodra de Client een request naar de web service doet met de volgende json string krijgt de IsAdmin property automatisch een default waarde wat False is. Wat als de data gemanipuleerd wordt en er ook een IsAdmin met waarde True mee wordt gestuurd. Dat wil je voorkomen. Daarom is het verstandig om met Data Transfer Objects(DTO) te werken. Een voorbeeld van hoe UserProfile model er als DTO uit zou komen te zien is als volgt:

publicclassUserProfileDTO
{
   publicstringName { get; set; }
   publicUriBlog { get; set; }
}

 Nu kan je in de controller zelf alsnog dit object opslaan in de Database en zelf de controle uitoefenen op de IsAdmin Entity in de Database.

Wanneer HTTPS?

In veel gevallen is het verstandig om SSL te gebruiken maar zodra je gebruikersnamen en wachtwoorden in de request mee gaat sturen is dit een must.

SSL biedt authenticatie met behulp van certificaten. De server moet een certificaat aan de client aanbieden zodat de Client en Server gekoppeld zijn.

Als de web service zowel HTTPS als http gaat ondersteunen dan kan er een custom attribute worden aangemaakt zodat je bepaalde actions alleen kunnen worden aangeroepen via HTTPS. Het maken van een zo een custom attribute gaat als volgt:

public class RequireHttpsAttribute : AuthorizationFilterAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
        {
            actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
            {
                ReasonPhrase = "HTTPS Required"
            };
        }
        else
        {
            base.OnAuthorization(actionContext);
        }
    }
}

Nu kan je bij de desgewenste action of Controller dit attribuut toevoegen:

public class ValuesController : ApiController
{
    [RequireHttps]
    public HttpResponseMessage Get() { ... }
}


Conclusie

Het beveiligen van een web service kan best complex zijn. Het is belangrijk om goed na te denken hoe de webservice beveiligd gaat worden want je wil natuurlijk niet dat er data wordt gestolen! Voor mensen die een simpele oplossing willen voor hun web service is Basic Authentication zeker een aanrader. Het is gemakkelijk te implementeren maar het moet wel met SSL gebeuren.

Verder is het belangrijk dat je nadenkt over hoe je de validatie op je data doet. Het liefst ook gebruik maken van Data Transfer objects waar nodig. Dit kan in veel gevallen ook best wat dataverkeer schelen voor bijvoorbeeld mobiele telefoons. Een andere manier om authenticatie af te handelen is met OAuth, maar dit is wel moeilijker om te implementeren.

Wil je meer berichten ontvangen over data security en development? Schrijf je dan in voor onze nieuwsbrief onderaan deze pagina!

Geschreven door: Ferdi
Cookies?

We gebruiken cookies om het functioneren van onze website te verbeteren. De gegevens worden volledig anoniem verzameld.