5

I need to Encrypt the URLs in my ASP.NET MVC application.

Do I need to write the code in Global page in Route Collection to Encrypt all the URLs?

George Stocker
  • 57,289
  • 29
  • 176
  • 237
kumar
  • 2,944
  • 18
  • 60
  • 89

5 Answers5

7

It's a bad idea to encrypt a URL. Period.

You may wonder why I say that.

I worked on an application for a company that encrypted its URLs. This was a webforms application. From the URL alone, it was nearly impossible to tell what part of the code I was hitting to cause that issue. Because of the dynamic nature of calling the webform controls, you just had to know the path the software was going to go down. It was quite unnerving.

Add to that that there was no role based authorization in the application. It was all based on the URL being encrypted. If you could decrypt the URL (which if it can be encrypted, it can be decrypted), then you could conceivably enter another encrypted URL and impersonate another user. I'm not saying it's simple, but it can happen.

Finally, how often do you use the internet and see encrypted URLs? When you do, do you die a little inside? I do. URLs are meant to convey public information. If you don't want it to do that, don't put it in your URL (or require Authorization for sensitive areas of your site).

The IDs you're using in the database should be IDs that are ok for the user to see. If you're using an SSN as a primary key, then you should change that schema for a web application.

Anything that can be encrypted can be decrypted, and therefore is vulnerable to attack.

If you want a user to only access certain URLs if they're authorized, then you should use the [Authorize] attributes available in ASP.NET MVC.

George Stocker
  • 57,289
  • 29
  • 176
  • 237
  • 6
    "Anything that can be encrypted can be decrypted, and therefore is vulnerable to attack.". What an amazingly useless statement. I agree with the rest of your comments. – Noon Silk Dec 16 '10 at 23:33
  • @Noon How is that useless? It is the state of encryption. That's why passwords aren't encrypted; they're hashed; and the good ones are salted before they're hashed so that you have to do extra work to figure out what the original text was. Man, all this talk about Salts and Hashes is making me hungry. – George Stocker Dec 16 '10 at 23:35
  • 4
    @George: The implication of that statement is nothing should be encrypted; which is clearly ridiculous. – Noon Silk Dec 16 '10 at 23:36
  • 1
    That is certainly not the state of encryption, or else online banking and shopping would be in the gutter. We use password hashes because it means that some rogue employee won't have access to the password that the user probably uses for 50 other websites. I don't think URL encryption makes sense, but something like SSL does a pretty good job of preventing third parties from seeing your data. – StriplingWarrior Dec 17 '10 at 00:11
  • @Noon No, that's not the implication. The implication is that you shouldn't encrypt something that the user can easy figure out and thus use to break your encryption. Unless you're naming your routes `goobleygook` they'll follow a dictionary word or a number sequence that is solvable. – George Stocker Dec 17 '10 at 00:23
  • 2
    @George: I can't continue this discussion with you, it has gone to a ridiculous place. I'll leave it with you. – Noon Silk Dec 17 '10 at 00:38
  • 1
    Sorry, I have to agree with Noon here. There sometimes should be some kind of obfuscation of IDs in urls. Because even "authenticated users" will try to mess around with the ID values in routes to see things they probably should not see. I'm researching this and will give a better answer. – enorl76 Dec 17 '11 at 19:24
  • 1
    @enorl76 The 'better answer' is to have proper role authentication in place and set up your system so users can't see other users' information just by changing some Ids. If you're relying on obsfucation, you've already lost. – George Stocker Dec 17 '11 at 19:42
  • This answer is quite weird. Saying URLs (Uniform Resource Locator) shouldn't be encrypted (or cryptic at best) is like saying GUIDs shouldn't exist for the same reason either. It might be more convenient to have your URLs human-readable, but in no way this should be a requirement. Would be better if the answer provided any links to credible docs/blogs which discuss this more in detail, instead of just stating one's opinion, based on a single project with poor architecture and generalizing the conclusion as a form of a best-practice advice... – Mladen B. Jul 15 '19 at 12:02
  • 1
    @MladenB. Thanks for this feedback; honestly I'd never thought I needed to include 'sources'; as my sources were the creators of the WWW themselves. I had thought it was ingrained in web development not to do this and didn't think about justifying it with those sources as they were the creators of the web. Here are some links for you: https://en.wikipedia.org/wiki/Clean_URL https://www.w3.org/Provider/Style/URI http://www.isoc.org/inet95/proceedings/PAPER/016/html/paper.html http://www.useit.com/alertbox/990321.html – George Stocker Jul 15 '19 at 15:05
  • @GeorgeStocker, thank you for making an effort to support your answer with some credible sources. As was already pointed out, the answer you provided was biased, since it was based on an experience with a poorly designed project, and boils down to a matter of preference. Even the link you provided at isoc.org, in chapter 7, reads: "On the other hand, someone publishing URLs may not want people to infer anything from those URLs." which I think is the exact case of the OP. – Mladen B. Jul 16 '19 at 11:27
  • One example where we need obfuscated/encrypted URLs is when we need to include an ID of some sort in the URL, but the ID itself was poorly designed and leaks too much information to the outside world. We could create a mapping of original/URL ids, but that's what the encryption does, as well. It maps the original information with a cypher. I hope that example shows there are real cases where we need such URLs, instead of SEF/Clean ones. – Mladen B. Jul 16 '19 at 11:29
  • @MladenB. It shouldn't matter what the ID is (if it's a Social then you have to change your identifier anyway, regardless of whether you subsequently obsfucate the ID). You could [salt and hash the ID with Bycrypt](https://stackoverflow.com/a/4435888/16587) and use that instead of the ID itself; and the URL could stay un-encrypted. More over, if you're using URLs for security, that's a bad design practice. You should have authorization in place that checks each authenticated user to make sure they have access to the particular URL they're being sent to. – George Stocker Jul 16 '19 at 12:57
6

Encrypting an entire url, I agree, very bad idea. Encrypting url parameters? Not so much and is actually a valid and widely used technique.

If you really want to encrypt/decrypt url parameters (which isn't a bad idea at all), then check out Mads Kristensen's article "HttpModule for query string encryption".

You will need to modify context_BeginRequest in order to get it to work for MVC. Just remove the first part of the if statement that checks if the original url contains "aspx".

With that said, I have used this module in a couple of projects (have a converted VB version if needed) and for the most part, it works like a charm.

BUT, there are some instances where I have experienced some issues with jQuery/Ajax calls not working correctly. I am sure the module could be modified in order to compensate for those scenarios.

Ed DeGagne
  • 3,250
  • 1
  • 30
  • 46
3

Based on the answers here, which did not work for me BTW, I found another solution based on my particular MVC implementation, and the fact that it also works depending on whether you're using II7 or II6. Slight changes are needed in both cases.

II6

Firstly, you need to add the following into your web.config (root, not the one in View folder).

 <system.web>
    <httpModules>
      <add name="URIHandler" type="URIHandler" />
    </httpModules>

II7

add this instead into your web.config (root, not the one in View folder).

  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="URIHandler" />
      <add name="URIHandler" type="URIHandler" />
    </modules>

Or you could add both. It doesn't matter really.

Next use this class. I called it, as you've probably noticed - URIHandler.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.IO;
using System.Text;
using System.Security.Cryptography;
using System.Diagnostics.CodeAnalysis;

public class URIHandler : IHttpModule
    {
        #region IHttpModule members
        public void Dispose()
        {
        }

        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(context_BeginRequest);
        }
        #endregion

        private const string PARAMETER_NAME = "enc=";
        private const string ENCRYPTION_KEY = "key";

        private void context_BeginRequest(object sender, EventArgs e)
        {
            HttpContext context = HttpContext.Current;
            //if (context.Request.Url.OriginalString.Contains("aspx") && context.Request.RawUrl.Contains("?"))
            if (context.Request.RawUrl.Contains("?"))
            {
                string query = ExtractQuery(context.Request.RawUrl);
                string path = GetVirtualPath();

                if (query.StartsWith(PARAMETER_NAME, StringComparison.OrdinalIgnoreCase))
                {
                    // Decrypts the query string and rewrites the path.
                    string rawQuery = query.Replace(PARAMETER_NAME, string.Empty);
                    string decryptedQuery = Decrypt(rawQuery);
                    context.RewritePath(path, string.Empty, decryptedQuery);
                }
                else if (context.Request.HttpMethod == "GET")
                {
                    // Encrypt the query string and redirects to the encrypted URL.
                    // Remove if you don't want all query strings to be encrypted automatically.
                    string encryptedQuery = Encrypt(query);
                    context.Response.Redirect(path + encryptedQuery);
                }
            }
        }

        /// <summary>
        /// Parses the current URL and extracts the virtual path without query string.
        /// </summary>
        /// <returns>The virtual path of the current URL.</returns>
        private static string GetVirtualPath()
        {
            string path = HttpContext.Current.Request.RawUrl;
            path = path.Substring(0, path.IndexOf("?"));
            path = path.Substring(path.LastIndexOf("/") + 1);
            return path;
        }

        /// <summary>
        /// Parses a URL and returns the query string.
        /// </summary>
        /// <param name="url">The URL to parse.</param>
        /// <returns>The query string without the question mark.</returns>
        private static string ExtractQuery(string url)
        {
            int index = url.IndexOf("?") + 1;
            return url.Substring(index);
        }

        #region Encryption/decryption

        /// <summary>
        /// The salt value used to strengthen the encryption.
        /// </summary>
        private readonly static byte[] SALT = Encoding.ASCII.GetBytes(ENCRYPTION_KEY.Length.ToString());

        /// <summary>
        /// Encrypts any string using the Rijndael algorithm.
        /// </summary>
        /// <param name="inputText">The string to encrypt.</param>
        /// <returns>A Base64 encrypted string.</returns>
        [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
        public static string Encrypt(string inputText)
        {
            RijndaelManaged rijndaelCipher = new RijndaelManaged();
            byte[] plainText = Encoding.Unicode.GetBytes(inputText);
            PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(ENCRYPTION_KEY, SALT);

            using (ICryptoTransform encryptor = rijndaelCipher.CreateEncryptor(SecretKey.GetBytes(32), SecretKey.GetBytes(16)))
            {
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(plainText, 0, plainText.Length);
                        cryptoStream.FlushFinalBlock();
                        return "?" + PARAMETER_NAME + Convert.ToBase64String(memoryStream.ToArray());
                    }
                }
            }
        }

        /// <summary>
        /// Decrypts a previously encrypted string.
        /// </summary>
        /// <param name="inputText">The encrypted string to decrypt.</param>
        /// <returns>A decrypted string.</returns>
        [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
        public static string Decrypt(string inputText)
        {
            RijndaelManaged rijndaelCipher = new RijndaelManaged();
            byte[] encryptedData = Convert.FromBase64String(inputText);
            PasswordDeriveBytes secretKey = new PasswordDeriveBytes(ENCRYPTION_KEY, SALT);

            using (ICryptoTransform decryptor = rijndaelCipher.CreateDecryptor(secretKey.GetBytes(32), secretKey.GetBytes(16)))
            {
                using (MemoryStream memoryStream = new MemoryStream(encryptedData))
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                    {
                        byte[] plainText = new byte[encryptedData.Length];
                        int decryptedCount = cryptoStream.Read(plainText, 0, plainText.Length);
                        return Encoding.Unicode.GetString(plainText, 0, decryptedCount);
                    }
                }
            }
        }
        #endregion
    }

You don't need a NameSpace.

The above class does everything you need to Encrypt and Decrypt any URL parameters starting with '?' character. It even does a nice job of renaming your parameter variables to 'enc', which is a bonus.

Lastly, place the class in your App_Start folder, and NOT the App_Code folder, as that will conflict with 'unambiguous errors'.

Done.

Credits:

https://www.codeproject.com/questions/1036066/how-to-hide-url-parameter-asp-net-mvc

https://msdn.microsoft.com/en-us/library/aa719858(v=vs.71).aspx

HttpModule Init method were not called

C# Please specify the assembly explicitly in the type name

https://stackoverflow.com/questions/1391060/httpmodule-with-asp-net-mvc-not- being-called

Fandango68
  • 4,461
  • 4
  • 39
  • 74
  • This is working good for URL's which include `?`. How would you go about encrypting ID's that are passed without the `?`. For example: `myDomain/myController/myAction/10` Instead of: `myDomain/myController/myAction?myID=10` – DNKROZ Jan 10 '18 at 07:51
  • 1
    I modified your code a little to allow for the encryption of ID's when passed using the default MVC style. Can add the code if needed – DNKROZ Jan 10 '18 at 14:06
  • @DNKROZ To answer your 1st question, that's precisely what I use it for, for paramaterised URL options such as ?CustID=1234 – Fandango68 Jan 11 '18 at 03:50
  • Yes it works perfectly for that - however I meant for encrypting ID's that are not passed in the query string format. For example: `myDomain/myController/myAction/10` encrypting the ID 10 – DNKROZ Jan 11 '18 at 08:56
  • 1
    I also noticed that the user can still carry out query string manipulation when using this solution - I am now working on modifying the code to encrypt the action links in the view to prevent this from happening – DNKROZ Jan 11 '18 at 08:59
  • @DNKROZ That is interesting. Yes, please post your solution here. Would love to try it. Are you using a seach method to search and encrypt beyond the last '/' char for example? – Fandango68 Jan 12 '18 at 00:31
  • 1
    I originally did that and it worked well (I also checked that before the `/` was all numeric values), however I noticed that someone could user f12 dev tools to change the ID params in the view and then successfully manipulate the URL before it has been encrypted. Therefore I've had to abandon the solution above and I am now in the process of implementing another solution which involves extending the `ActionLink`. I'm using https://www.codeproject.com/Articles/130588/Preventive-Method-for-URL-Request-Forgery-An-Examp as a reference – DNKROZ Jan 12 '18 at 09:02
0

You can create a custom html helper to encrypt query string and use custom action filter attribute for decryption and getting original values back. You can implement it globally so won't take much of your time. You can take reference from here Url Encryption In Asp.Net MVC. This will help you out with custom helper and custom action filter attribute.

Shubham
  • 443
  • 2
  • 10
-1

It's likely pointless to globally encrypt all the url parameters (query string). Most parameters are display items used by HttpGet. If everything is encrypted then this won't make for a very informative page. However if there are sensitive parameters that are only hidden fields (keys) on the client that eventually are returned to the server to identify a record, this might be worth encrypting.

Consider this viewModel:

public viewModel
{
    public int key {get;set;}           // Might want to encrypt
    public string FirstName {get;set;}  // Don't want this encrypted
    public string LastName {get;set;}   // Don't want this encrypted
}

The viewModel gets converted into a query string, something close to.... appName.com/index?Id=2;FirstName="John";LastName="Doe"

If this viewModel is passed as a query string, what's the point in encrypting the first and last names?

It should be noted that query strings are HttpGet. HttpPost use the session to pass values not query strings. HttpPost sessions are encrypted. But there is overhead to httpPost. So, if your page does actually contain sensitive data that needs to be displayed (perhaps the users current password) then consider going to HttpPost instead.

Russ Ebbing
  • 478
  • 5
  • 7