Search code examples
c#asp.net-mvcsecuritydpapi

MS Data Protection API for Query String Protection force same ciphertext for given plaintext


I'm using the Data Protection API to protect my MVC .NET Core 2.2 Web Application against IDOR (Insecure Direct Object Reference) bugs by encrypting the value in the query string.

I have the below code based on the Microsoft Documentation here that illustrates my issue.

When I enter the same input twice, I get a different ciphertext. I would like to know if it is possible to use the Data Protection API to output the same ciphertext, as in my Web App I have somewhere I call the Protect method on the backend in two different places and then I expect the encrypted values to the the same on the UI.

using System;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;

public class ConsoleApp1
{
    public static void Main(string[] args)
    {
        // add data protection services
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddDataProtection();
        var services = serviceCollection.BuildServiceProvider();

        // create an instance of MyClass using the service provider
        var instance = ActivatorUtilities.CreateInstance<MyClass>(services);
        instance.RunSample();
    }

    public class MyClass
    {
        IDataProtector _protector;

        // the 'provider' parameter is provided by DI
        public MyClass(IDataProtectionProvider provider)
        {
            _protector = provider.CreateProtector("Contoso.MyClass.v1");
        }

        public void RunSample()
        {
            Console.Write("Enter input: ");
            string input;
            while ((input = Console.ReadLine()) != "xx")
            {
                // protect the payload
                string protectedPayload = _protector.Protect(input);
                Console.WriteLine($"Protect returned: {protectedPayload}");

                // unprotect the payload
                string unprotectedPayload = _protector.Unprotect(protectedPayload);
                Console.WriteLine($"Unprotect returned: {unprotectedPayload}");
            } 
        }
    }
}

/*
 * SAMPLE OUTPUT
 *
 * Enter input: Hello world!
 * Protect returned: CfDJ8ICcgQwZZhlAlTZT...OdfH66i1PnGmpCR5e441xQ
 * Unprotect returned: Hello world!
 */

My OUTPUT

1
Protect returned: CfDJ8Pj60RReYshEqBeXl2Zl8luwN5Aj4EPgXaSWPinkIFxn3jnRhTFT8cBsZTasBeWeLGwhVJ9g7y6i2KgUFboawqZf-SlkkTl1X3hlmr3Qnnaiuwh3JBIS_cGAvPO7bLLdlg
Unprotect returned: 1

1
Protect returned: CfDJ8Pj60RReYshEqBeXl2Zl8luMYV9VXpAZwn9-6gzprRoC0GHCc_XTmeHs2Ln-DZByY9AzJAOvvrupwPTB9xLXYKj8W7xsDEA4hMxpwCQXuYhTZ-Rs4Kt6D69ldlmMbAHnAw
Unprotect returned: 1

Solution

  • I'm not sure encrypting the query string will actually prevent Insecure Direct Object Reference. If anyone was to intercept the URL or indeed to directly share the URL with another user then they would still be able to access the record.

    To be properly secure you need to implement some sort of auth. If you really don't want to implement that, then using a Guid as an id instead of an int will give you a hard to guess parameter, but it is still vulnerable to interceptions or deliberate sharing