web-dev-qa-db-fra.com

Comment déchiffrer manuellement un cookie d'authentification ASP.NET Core?

Prenons un scénario ASP.NET Core connu. Nous ajoutons d'abord le middleware:

public void Configure(IApplicationBuilder app)
{
    app.UseCookieAuthentication(new CookieAuthenticationOptions()
    {
        AuthenticationScheme = "MyCookie",
        CookieName = "MyCookie",
        LoginPath = new PathString("/Home/Login/"),
        AccessDeniedPath = new PathString("/Home/AccessDenied/"),
        AutomaticAuthenticate = true,
        AutomaticChallenge = true
    });
    //...
}

Sérialisez ensuite un principal:

await HttpContext.Authentication.SignInAsync("MyCookie", principal);

Après ces deux appels, un cookie crypté sera stocké côté client. Vous pouvez voir le cookie (dans mon cas, il a été fragmenté) dans tous les devtools du navigateur:

enter image description here

Ce n'est pas un problème (et pas une question) de travailler avec des cookies à partir du code d'application.

Ma question est: comment décrypter le cookie en dehors de l'application ? Je suppose qu'une clé privée est nécessaire pour cela, comment l'obtenir?

J'ai vérifié le docs et n'ai trouvé que des mots communs:

Cela créera un cookie crypté et l'ajoutera à la réponse actuelle. Le AuthenticationScheme spécifié lors de la configuration doit également être utilisé lors de l'appel à SignInAsync.

Sous les couvertures, le cryptage utilisé est le système de protection des données d'ASP.NET. Si vous hébergez sur plusieurs machines, équilibrez la charge ou utilisez une batterie de serveurs Web, vous devrez configurer la protection des données pour utiliser le même trousseau de clés et le même identifiant d'application.

Alors, est-il possible de décrypter le cookie d'authentification, et si oui, comment?

MISE À JOUR # 1: Basé sur Ron C excellente réponse et commentaires , je me suis retrouvé avec du code:

public class Startup
{
    //constructor is omitted...

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDataProtection().PersistKeysToFileSystem(
            new DirectoryInfo(@"C:\temp-keys\"));

        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            AuthenticationScheme = "MyCookie",
            CookieName = "MyCookie",
            LoginPath = new PathString("/Home/Index/"),
            AccessDeniedPath = new PathString("/Home/AccessDenied/"),
            AutomaticAuthenticate = true,
            AutomaticChallenge = true
        });

        app.UseStaticFiles();
        app.UseMvcWithDefaultRoute();
    }
}

public class HomeController : Controller
{
    public async Task<IActionResult> Index()
    {
        await HttpContext.Authentication.SignInAsync("MyCookie", new ClaimsPrincipal());

        return View();
    }

    public IActionResult DecryptCookie()
    {
        var provider = DataProtectionProvider.Create(new DirectoryInfo(@"C:\temp-keys\"));

        string cookieValue = HttpContext.Request.Cookies["MyCookie"];

        var dataProtector = provider.CreateProtector(
            typeof(CookieAuthenticationMiddleware).FullName, "MyCookie", "v2");

        UTF8Encoding specialUtf8Encoding = new UTF8Encoding(false, true);
        byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookieValue);
        byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
        string plainText = specialUtf8Encoding.GetString(plainBytes);

        return Content(plainText);
    }
}

Malheureusement, ce code produit toujours une exception sur l'appel de méthode Unprotect:

CryptographicException dans Microsoft.AspNetCore.DataProtection.dll: Informations supplémentaires: la charge utile n'était pas valide.

J'ai testé différentes variantes de ce code sur plusieurs machines sans résultat positif. J'ai probablement fait une erreur, mais où?

MISE À JOUR # 2: Mon erreur était que le DataProtectionProvider n'a pas été défini dans UseCookieAuthentication. Merci encore à @RonC.

24
Ilya Chumakov

Déchiffrer le cookie d'authentification sans avoir besoin des clés

Il convient de noter que vous n'avez pas besoin d'accéder aux clés pour déchiffrer le cookie d'authentification. Il vous suffit d'utiliser le bon IDataProtector créé avec le bon paramètre et les paramètres de sous-objectif.

Basé sur le code source CookieAuthenticationMiddlewarehttps://github.com/aspnet/Security/blob/rel/1.1.1/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationMiddleware.cs#L4 il semble que le but que vous devez passer soit typeof(CookieAuthenticationMiddleware). Et comme ils transmettent des paramètres supplémentaires au IDataProtector, vous devrez les faire correspondre. Cette ligne de code devrait donc vous fournir un IDataProtector qui peut être utilisé pour déchiffrer le cookie d'authentification:

var dataProtector = provider.CreateProtector(typeof(CookieAuthenticationMiddleware).FullName, Options.AuthenticationScheme, "v2");

Notez queOptions.AuthenticationScheme est juste "MyCookie" dans ce cas, car c'est ce qu'il a été défini dans la méthode Configure du fichier startup.cs.

Voici un exemple de méthode d'action pour décrypter votre cookie d'authentification de deux manières différentes:

public IActionResult DecryptCookie() {

    //Get the encrypted cookie value
    string cookieValue = HttpContext.Request.Cookies["MyCookie"];

    //Get a data protector to use with either approach
    var dataProtector = provider.CreateProtector(typeof(CookieAuthenticationMiddleware).FullName, "MyCookie", "v2");


    //Get the decrypted cookie as plain text
    UTF8Encoding specialUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
    byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookieValue);
    byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
    string plainText = specialUtf8Encoding.GetString(plainBytes);


    //Get the decrypted cookie as a Authentication Ticket
    TicketDataFormat ticketDataFormat = new TicketDataFormat(dataProtector);
    AuthenticationTicket ticket = ticketDataFormat.Unprotect(cookieValue);

    return View();
}

Cette méthode utilise un IDataProtectionProvider appelé provider qui est injecté par le constructeur.


Déchiffrement du cookie d'authentification lors de la persistance des clés d'un répertoire

Si vous souhaitez partager des cookies entre des applications, vous pouvez décider de conserver les clés de protection des données dans un répertoire. Cela peut être fait en ajoutant ce qui suit à la méthode ConfigureServices du fichier startup.cs:

services.AddDataProtection().PersistKeysToFileSystem(
        new DirectoryInfo(@"C:\temp-keys\")); 

ATTENTION car les clés ne sont pas cryptées donc c'est à vous de les protéger !!! Ne conservez les clés d'un répertoire que si vous le devez absolument (ou si vous essayez simplement de comprendre le fonctionnement du système). Vous aurez aussi besoin de spécifier un cookie DataProtectionProvider qui utilise ces clés. Cela peut être fait à l'aide de la configuration UseCookieAuthentication dans la méthode Configure de la classe startup.cs comme ceci:

app.UseCookieAuthentication(new CookieAuthenticationOptions() {
        DataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(@"C:\temp-keys\")),
        AuthenticationScheme = "MyCookie",
        CookieName = "MyCookie",
        LoginPath = new PathString("/Home/Login"),
        AccessDeniedPath = new PathString("/Home/AccessDenied"),
        AutomaticAuthenticate = true,
        AutomaticChallenge = true
    });

Une fois cette configuration terminée. Vous pouvez maintenant déchiffrer le cookie d'authentification avec le code suivant:

 public IActionResult DecryptCookie() {
        ViewData["Message"] = "This is the decrypt page";
        var user = HttpContext.User;        //User will be set to the ClaimsPrincipal

        //Get the encrypted cookie value
        string cookieValue = HttpContext.Request.Cookies["MyCookie"];


        var provider = DataProtectionProvider.Create(new DirectoryInfo(@"C:\temp-keys\"));

        //Get a data protector to use with either approach
        var dataProtector = provider.CreateProtector(typeof(CookieAuthenticationMiddleware).FullName, "MyCookie", "v2");


        //Get the decrypted cookie as plain text
        UTF8Encoding specialUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
        byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookieValue);
        byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
        string plainText = specialUtf8Encoding.GetString(plainBytes);


        //Get teh decrypted cookies as a Authentication Ticket
        TicketDataFormat ticketDataFormat = new TicketDataFormat(dataProtector);
        AuthenticationTicket ticket = ticketDataFormat.Unprotect(cookieValue);

        return View();
    }

Vous pouvez en savoir plus sur ce dernier scénario ici: https://docs.Microsoft.com/en-us/aspnet/core/security/data-protection/compatibility/cookie-sharing

24
Ron C

Voir ci-dessous une méthode d'assistance pour .NET Core 2 pour obtenir des réclamations à partir d'un cookie:

private IEnumerable<Claim> GetClaimFromCookie(HttpContext httpContext, string cookieName, string cookieSchema)
{
    // Get the encrypted cookie value
    var opt = httpContext.RequestServices.GetRequiredService<IOptionsMonitor<CookieAuthenticationOptions>>();
    var cookie = opt.CurrentValue.CookieManager.GetRequestCookie(httpContext, cookieName);

    // Decrypt if found
    if (!string.IsNullOrEmpty(cookie))
    {
        var dataProtector = opt.CurrentValue.DataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", cookieSchema, "v2");

        var ticketDataFormat = new TicketDataFormat(dataProtector);
        var ticket = ticketDataFormat.Unprotect(cookie);
        return ticket.Principal.Claims;
    }
    return null;
}

Comme l'a souligné @Cirem, la façon douteuse de créer un protecteur est exactement la façon dont Microsoft le fait (voir leur code ici ). Il peut donc changer dans les futures versions.

9
Alex Klaus

Une autre variante pour ASP.NET Core 2.2:

var cookieManager = new ChunkingCookieManager();
var cookie = cookieManager.GetRequestCookie(HttpContext, ".AspNetCore.Identity.Application");

var dataProtector = dataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", "Identity.Application", "v2");

//Get the decrypted cookie as plain text
UTF8Encoding specialUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookie);
byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
string plainText = specialUtf8Encoding.GetString(plainBytes);


//Get teh decrypted cookies as a Authentication Ticket
TicketDataFormat ticketDataFormat = new TicketDataFormat(dataProtector);
AuthenticationTicket ticket = ticketDataFormat.Unprotect(cookie);
1
Adiqq