Click here to Skip to main content
15,878,748 members
Articles / Web Development / HTML

10 Points to Secure Your ASP.NET Core MVC Applications

Rate me:
Please Sign up or sign in to vote.
4.99/5 (31 votes)
5 Sep 2018CPOL37 min read 71.1K   448   78   12
How to secure ASP.NET Core MVC Applications against top 10 attacks
In this article, we learn how to secure ASP.NET Core MVC Applications against top 10 attacks given by OWSAP (Open Web Application Security Project) in a step by step manner.

Introduction

We are new to the .NET Core Framework and we are using it for developing production applications, but when we develop a production application, we must also think of security. So, in this article, we will run through 10 points which will help us to make our ASP.NET Core MVC code secure.

Table of Contents

  1. Broken authentication and session management
  2. Sensitive Data Exposure & Audit trail
  3. Cross-Site Scripting (XSS) attacks
  4. Malicious File Upload
  5. Security Misconfiguration (Error Handling Must Setup Custom Error Page)
  6. Version Discloser
  7. Cross-Site Request Forgery (CSRF)
  8. XML External Entities (XXE)
  9. Insecure Deserialization
  10. SQL Injection Attack
  11. Conclusion
  12. History

1. Broken Authentication and Session Management

In this part, if we do not manage proper authentication of application, this may allow an attacker to steal user credentials such as session, cookies which may allow an attacker to have a complete access to the entire application and then he may try to access application server and a database server which can lead to a large data breach.

Ways in which an attacker can steal data:

  • Not Secure connection (Not Using SSL)
  • Predictable login credentials
  • Not storing credentials in Encrypted form
  • Improper Application logs out

Attacks Possible

Session Fixation

Before finding a solution on how to prevent this attack, let’s have a look at a small demo how Session Fixation attack occurs.

Whenever a user sends the first request to server, the login page is loaded, then User enters valid Login credits to login in web application. After successful login, we assign some values in session to recognize Unique User, meanwhile a [".AspNetCore.Session"] cookie is added to the browser for recognizing a specific User who has sent a request and the [".AspNetCore.Session"] cookie value will be always sent to the server on every request till you are not logged out from the application. On logout, we basically write code for removing session values which are created, but we do not remove the [".AspNetCore.Session"] cookie which is created on Login. This value helps an attacker to perform a Session Fixation attack.

Image 1

Figure 1. Session Fixation

After User Enter Valid Credentials

After entering Valid Login Credentials, [".AspNetCore.Session"] Cookie is added to the browser.

Note: When any data is saved into the Session, [".AspNetCore.Session"] cookie is created and added to the user browser.

Image 2

Figure 2. Showing Application Cookies in Cookie Manager

After Logout from Application Cookie still exists in the browser

After Logout out from application, still [".AspNetCore.Session"] Cookie exits.

Image 3

Figure 3. Showing Application Cookies in Cookie Manager after Logging out from the application.

Note: Pre-cookie and post cookie are the same which can lead to the session fixation.

Let’s Do Some Session Fixation Demo

The [".AspNetCore.Session"] Cookie which is not removed after logout which helps attacker for doing session fixation. I will open a browser (chrome) in that I will enter URL [http://localhost:53654/] of application in which we are going to do session fixation.

Image 4

Figure 4. Login Page.

In this view, I have shown [".AspNetCore.Session"] cookie which is created in Firefox browser when the user logged in.

Image 5

Figure 5. Showing created cookies after logging into the application.

A cookie which is already created in Firefox browser.

Note: For managing Cookie, I have installed Cookie Manager+ addon in Chrome browser.

Entering URL in the browser, now let’s check if we have any [".AspNetCore.Session"] Cookie created here. Oh, we don’t have any Cookie.

Image 6

Figure 5. Showing cookies.

After having a view of [".AspNetCore.Session"] cookie in Firefox, now let’s create [".AspNetCore.Session"] cookie in Chrome browser similar to Cookie which is in Firefox browser with same [".AspNetCore.Session"] Cookie Name and Values.

Created New [".AspNetCore.Session"] Cookie in Chrome browser similar to Cookie created in Firefox browser.

This is a step where we have Fixed Session. This session is live on another browser [Firefox] we have copied values similar to that and created a [".AspNetCore.Session"] Cookie and assigned similar values of SessionID to this Cookie.

Image 7

Figure 6. Copying old cookie values to create a new session cookie.

Note: For adding Cookie, I have installed Edit this Cookie addon in Chrome browser:

Image 8

Figure 7. Creating New cookies by copying old cookie values.

After fixing cookie, now we no longer require any login to the application if we just enter Inner URL of application, we get direct access because this session is created after authentication.

My Inner URL which is to Access is: http://localhost:53654/Dashboard/Index.

Below, you can see I have accessed Dashboard Page without Login into the application using Session Fixation.

Image 9

Figure 8. View after Creating New Session cookies.

Solution

  1. Remove [".AspNetCore.Session"] after logout
  2. Use SSL for Securing Cookies and Session
  3. Securing Cookies by Setting HTTP only

Remove [".AspNetCore.Session"] after logout

On logout, we are removing Session values. Along with that, we are removing [".AspNetCore.Session"] Cookie from the browser.

We have done a similar process in ASP.NET MVC, but Cookie Names are different in ASP.NET Core MVC.

Code Snippet

C#
[HttpGet]
public ActionResult Logout()
{
    try
    {
        // Removing Session
        HttpContext.Session.Clear();

        // Removing Cookies
        CookieOptions option = new CookieOptions();
        if (Request.Cookies[".AspNetCore.Session"] != null)
        {
            option.Expires = DateTime.Now.AddDays(-1);
            Response.Cookies.Append(".AspNetCore.Session", "", option);
        }

        if (Request.Cookies["AuthenticationToken"] != null)
        {
            option.Expires = DateTime.Now.AddDays(-1);
            Response.Cookies.Append("AuthenticationToken", "", option);
        }
     
        return RedirectToAction("Login", "Account");
    }
    catch (Exception)
    {
        throw;
    }
}

Securing Cookie

For securing cookies On Login [HttpPost] Action Method, we are going to Create a New Session in that Session [Session["AuthenticationToken"]]. We are going to save NewGuid. Along with that, we are going to add a cookie with Name ["AuthenticationToken"] and it will also have same Value [Guid] which we have stored in Session.

Code Snippet

C#
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {   //Getting Password from Database
        var storedpassword = ReturnPassword(model.UserName);
        // Comparing Password with Seed
        if (ReturnHash(storedpassword, model.HdrandomSeed) == model.Password)
        {
            HttpContext.Session.SetString("Username", Convert.ToString(model.UserName));

            // Getting New Guid
            string guid = Convert.ToString(Guid.NewGuid());
            //Storing new Guid in Session
            HttpContext.Session.SetString("AuthenticationToken", Convert.ToString(guid));

            //Adding Cookie in Browser
            CookieOptions option = new CookieOptions {Expires = DateTime.Now.AddHours(24)};
            Response.Cookies.Append("AuthenticationToken", guid, option);

            return RedirectToAction("Index", "Dashboard");
        }
        else
        {
            ModelState.AddModelError("", "The user name or password provided is incorrect.");
        }
    }
    return View(model);
}

Code Snippet Description

  • Creating a new Guid.
    C#
    // Getting New Guid
    string guid = Convert.ToString(Guid.NewGuid());
  • Saving a new Guid in Session.

    The process of adding Session in ASP.NET Core is bit different than that of typical ASP.NET MVC application.

    C#
    //Storing new Guid in Session
     HttpContext.Session.SetString("AuthenticationToken", Convert.ToString(guid));
  • Saving a new Guid in Cookie and insert Cookie to the browser.

    The process of adding Cookie in ASP.NET Core is bit different than that of Typical ASP.NET MVC application.

    C#
    //Adding Cookie in Browser
     CookieOptions option = new CookieOptions {Expires = DateTime.Now.AddHours(24)};
     Response.Cookies.Append("AuthenticationToken", guid, option);

    After storing data in session and adding a cookie in the browser, now let’s match these values on every request and check whether these values are similar. If not, then we are going to redirect to the Login page.

    For doing this part, I am going to add an AuthorizationFilter in the project and inside that, we are going to write logic for checking Session and cookie values are similar or not.

AuthenticateUser ActionFilter

If you check the below code snippet, I have created an AuthorizationFilter with name AuthenticateUser this filter, we are inheriting IAuthorizationFilter Interface and FilterAttribute class. With this, we are implementing method inside interface [OnAuthorization] and write whole logic in this method.

C#
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace WebApplication13.Filters
{
    public class AuthenticateUser : Attribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {           
            string tempSession =
                Convert.ToString(context.HttpContext.Session.GetString("AuthenticationToken"));
            string tempAuthCookie =
                Convert.ToString(context.HttpContext.Request.Cookies["AuthenticationToken"]);

            if (tempSession != null && tempAuthCookie != null)
            {
                if (!tempSession.Equals(tempAuthCookie))
                {
                    ViewResult result = new ViewResult {ViewName = "Login"};
                    context.Result = result;
                }
            }
            else
            {
                ViewResult result = new ViewResult {ViewName = "Login"};
                context.Result = result;
            }
        }
    }
}

Code Snippet Description

In this method first, we are going to get session and cookie values.

C#
string tempSession =
    Convert.ToString(context.HttpContext.Session.GetString("AuthenticationToken"));
string tempAuthCookie =
    Convert.ToString(context.HttpContext.Request.Cookies["AuthenticationToken"]);

After getting values from session and cookie, now we are going to check that both session and cookie values are not null. After that, we are going to see values are equal for both session and a cookie. If they are not, then we are going to redirect to login page.

C#
if (tempSession != null && tempAuthCookie != null)
{
    if (!tempSession.Equals(tempAuthCookie))
    {
        ViewResult result = new ViewResult {ViewName = "Login"};
        context.Result = result;
    }
}
else
{
    ViewResult result = new ViewResult {ViewName = "Login"};
    context.Result = result;
}

After understanding code snippet now, we are going to apply this filter on every controller which user accesses when he is logged into application.

Applying AuthenticateUser Filter

Apply this filter on every controller which user accesses when he is logged into the application.

Image 10

Figure 9. Applying the AuthenticateUser Filter.

After applying Action filter on all the controllers which user accesses after Logged into the application.

Now if the attacker knows [".AspNetCore.Session"] cookie value and a new cookie [Cookies["AuthenticationToken"]] value, he will still not able to Session Fixation attack here because the new [Cookies["AuthenticationToken"]] contains GUID which is unique and same values are stored in Session [Session["AuthenticationToken"]] on Web Server, but attacker can’t know the Session value that is stored on the web server and this values keep changing every time when user is logging to application and the old session values which attacker used to do attack will not work in this scenarios.

Finally, if we will allow that User access to the application who has valid Session["AuthenticationToken"] value and Cookies["AuthenticationToken"] value.

Real-time Values of both Cookies

Image 11

Figure 10. Showing both Cookie Values

Use SSL for Securing Cookies and Session Values

SSL (Secure Sockets Layer) is Layer where there is Secure (Encrypted) communication between client and server such that any data [Banking details, Password, Session, Cookie and another financial transaction] passed from client and server is Secure (Encrypted).

Image 12

Figure 11. SSL (Secure Sockets Layer) is Layer.

Set-Cookie HttpOnly True

Setting HttpOnly Cookies in ASP.NET Core.

HttpOnly is a flag that can be used when setting a cookie to block access to the cookie from client-side scripts. JavaScript, for example, cannot read a cookie that has HttpOnly set.

Code Snippet

  1. Setting HTTP only while creating Cookie:
    C#
    //Adding Cookie in Browser
    
    string guid = Convert.ToString(Guid.NewGuid());
    CookieOptions option = new CookieOptions {Expires = DateTime.Now.AddHours(24), 
                                              HttpOnly = true};
    Response.Cookies.Append("AuthenticationToken", guid, option);
  2. Setting HTTP only Globally in Startup.cs Configure method.

    Image 13

    Figure 12. Setting Cookie HttpOnly property to True.

Code Snippet

C#
app.UseCookiePolicy(new CookiePolicyOptions
 {
     HttpOnly = HttpOnlyPolicy.Always,
     Secure = CookieSecurePolicy.Always,
     MinimumSameSitePolicy = SameSiteMode.None
 });

2. Sensitive Data Exposure & Audit Trail

The data which is traveling (sending and receiving) and data which is at one place should have protections. As we are into web development, we store Users personal information (which may contain Password, PAN number, Passport details, Credit Card Numbers, Health records, financial records, business secrets). In this part as a developer, I saw most of the people only encrypt passwords and store in database, the rest of the data are not encrypted. If the attacker gets access to such data, then he can misuse this data.

Image 14

Solution:

  • Always Send Sensitive Data to Server in an Encrypted format using Strong Hashing with Seed (Random Hash).
  • Always apply SSL to the Web application.
  • Do not store Sensitive data if want to store using Strong Hashing techniques.

Securing Login

Before securing, let’s have a look at Common login pages issues which most developers are creating.

Image 15

Figure 13. Login Page.

Intercepting Login Page by an Attacker to Steal Credentials

When User enters Credentials in Login page and submits to server, the data [username and password] is transferred to the server in the clear text this data [username and password] can be intercepted by the attacker and steal your Credentials.

Image 16

Figure 14. Intercepting Login page showing data in the Clear form.

Always Send Sensitive Data to Server in an Encrypted Format using Strong Hashing with Seed

In this demo, we are going use an MD5 algorithm with seed to encrypted data on the client side and send to a server such that it cannot be stolen by the attacker.

  1. We are going use an MD5 algorithm which is a hashing algorithm.
  2. The first step in this process is to generate a random number after than generating a hash value which is your seed which will be sent to the client.
  3. When the user enters username and password and clicks on login button at that moment, we generate MD5 hash which is a combination of seed +password.
  4. The hashed password is received at your server end when we post form, next we get hashed password (without seed) from database from username and then we combine password (without seed) with seed and password which is along with seed which we have received is compared if it is equal then user is logged in successfully, else we show error message.

Intercepting Login Page

In the below snapshot when user enters credentials and Post form.

Image 17

Figure 15. Debug mode of Client-side Encryption.

Intercepting Post Request to show how the password has travelled in encrypted form.

Image 18

Figure 16. Intercepting login page after Encryption of data.

Login Action Method after Posting Values from Login View

Here we can clearly see password is in encrypted form.

Image 19

In the next step, we are going to compare password which is stored in a database. For doing that, first, we are going to get the password from username which the user has entered and then we are going to compare it against which is stored in the database. The line which is marked green is Seed value along with that, you can see Database stored password is marked as red which we get by passing Username and then yellow line which indicates that this password in posted from Login View and finally blue line which is combination of Database password and Seed, we compare it with password which is posted.

Image 20

Figure 17. Using the [hdrandomSeed] property as a hidden field on View.

Use SSL for Securing Web Application

SSL (Secure Sockets Layer) is Layer which Secure (Encrypted) communication between client and server such that any data [Banking details, Password, and another financial transaction] passed from client and server is Secure (Encrypted).

Image 21

Do Not Store Sensitive Data in Database in a Clear Form

SQL Server provides encryption as a new feature to protect data against attackers' attacks. Attackers might be able to gain access to the database or tables, but owing to encryption, they would not be able to understand the data or make use of it.

A Simple Example of How to Encrypt Data in SQL Server

I have simply created a demo table with three columns. First is int, second is Varchar, third is VARBINARY.

Image 22

After creating a table, we have created "Master Key" for encryption, then "Certificate" and "SYMMETRIC KEY", then using Symmetric key, we are going to encrypt data.

Below is a snapshot that shows how we have encrypted "Encrycolumn" column data.

Image 23

Then Using Same Key, we are going to Decrypt "Encrycolumn" column data.

Image 24

Read Detail process of SQL Data Encryption and Decryption.

Below is a list of algorithms which can be used according to need for encryption and decryption for data.

Hash Algorithm

If someone wants just Hash then they can use Hash Algorithm we mostly use the Hash function for Encrypting Password.

Symmetric Algorithm

If someone wants just one key for encryption and decryption, then they can use Symmetric Algorithm.

Asymmetric Algorithm

If someone wants just one key for encryption (Public key) and another key decryption (Private key), then they can use Asymmetric Algorithm. For example, we can use this when we are sharing Web Services and WebAPI with clients.

Hash Algorithm

  • MD5
  • SHA256
  • SHA384
  • SHA512

Symmetric Algorithm

  • Aes
  • DES
  • RC2
  • Rijndael
  • TripleDES

Asymmetric Algorithm

  • DSA
  • ECDiffieHellman
  • ECDsa
  • RSA

Audit Trails

In a production application, we have millions of transactions that occur in that Client or Users Create data, Update data, Delete Data, along with that we can also get insights of any errors or performance issues in a production application if anyone is trying to attack application, then we can know their attempts.

Image 25

Solution:

Keep Audit Trail of all User activity on Web Application and always Monitor it.

AuditTB Model

C#
[Table("AuditTB")]
public class AuditTb
{
    [Key]
    public int UsersAuditId { get; set; }
    public int? UserId { get; set; }
    public string SessionId { get; set; }
    public string IpAddress { get; set; }
    public string PageAccessed { get; set; }
    public DateTime? LoggedInAt { get; set; }
    public DateTime? LoggedOutAt { get; set; }
    public string LoginStatus { get; set; }
    public string ControllerName { get; set; }
    public string ActionName { get; set; }
    public string UrlReferrer { get; set; }
    public string Method { get; set; }
}

After having a look at Model which is generated, now let’s create an AuditTB table.

AuditTB Table

Image 26

Figure 18. AuditTb table view.

After having a look at Model which is generated, now let’s create an ActionFilter with AuditFilter.

AuditFilterActionfilter Code Snippet

AuditFilter is a Custom ActionFilter which we have created in this filter. We insert data of User Activity into AuditTB table along with this, we also check whether User is Logged in or not into application, in the same way we also insert IP address of User who is accessing the application, and time stamp of User logged in and out. For inserting this data, we use the ORM entity Framework core.

Code Snippet

C#
public class AuditFilter : ActionFilterAttribute
{
    private readonly DatabaseContext _databaseContext;
    public AuditFilter(DatabaseContext databaseContext)
    {
        _databaseContext = databaseContext;
    }
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        try
        {
            string actionName = null;
            string controllerName = null;

            // Getting ActionName
            if (((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)
            context.ActionDescriptor).ActionName != null)
            {
                actionName = ((Microsoft.AspNetCore.Mvc.Controllers.
                             ControllerActionDescriptor)context.ActionDescriptor)
                    .ActionName;
            }
            // Getting ControllerName
            if (((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)
            context.ActionDescriptor).ControllerName != null)
            {
                controllerName = ((Microsoft.AspNetCore.Mvc.Controllers.
                ControllerActionDescriptor)context.ActionDescriptor).ControllerName;
            }

            // Assigning values to AuditTb Class
            var objaudit = new AuditTb
            {
                UserId = context.HttpContext.Session.GetInt32("UserID") ?? 0,
                UsersAuditId = 0,
                SessionId = context.HttpContext.Session.Id,
                IpAddress = context.HttpContext.Connection.RemoteIpAddress.ToString(),
                PageAccessed = context.HttpContext.Request.GetDisplayUrl(),
                LoggedInAt = DateTime.Now,
                Method = context.HttpContext.Request.Method
            };

            if (actionName == "Logout")
            {
                objaudit.LoggedOutAt = DateTime.Now;
            }

            objaudit.LoginStatus = "A";
            objaudit.ControllerName = controllerName;
            objaudit.ActionName = actionName;

            _databaseContext.AuditTb.Add(objaudit);
            _databaseContext.SaveChanges();

            base.OnActionExecuting(context);
        }
        catch (Exception)
        {
            throw;
        }
    }
}

Registering UserAuditFilter in Globally

Image 27

Figure 19. Registering UserAuditFilter in globally.

Output

The data which got inserted while User Request pages and does some activity in Audit Table.

Image 28

Figure 20. Showing Data from Audit Table.

3. Cross-Site Scripting (XSS) Attacks

Cross-site Scripting (XSS) is an attack in which malicious scripts are injected via input fields. This attack is most common and allows an attacker to steal credentials and valuable data that can lead to a big security breach.

Image 29

Ways in which this attack is carried out:

  1. From Inputs
  2. Query Strings
  3. Http Headers
  4. Data Coming from Databases

For showing a demo, I have created a simple form with two fields which takes HTML as inputs.

Image 30

Figure 21. Entering malicious script in inputs.

What is the first question that arises when you see JavaScript is entered into text fields.

This must show the below error "A Potentially Dangerous Request.Form Value Was Detected from The Client".

Image 31

Note

But in ASP.NET Core, you will not get this error because ASP.NET Core 2.0 does not deny HTML in fields.

After posting that form, we do not get any errors and values are received in the model as shown in the below snapshot.

Image 32

Figure 22. Entering malicious script in inputs which are populated in the model.

Solution:

  1. [RegularExpressionAttribute]
  2. HTML Encoding using Razor
  3. URL Encoding

The first solution to XSS attack is validating all your Fields with Regular Expression such that only valid data can move in.

1. [RegularExpressionAttribute]

Use Regular Expression to validate input to protect from XSS attack below is snapshot.

Image 33

After applying Regular expression, we have the first line of defense which does not allow malicious inputs.

Image 34

Figure 23. Showing Errors Messages after Entering malicious script in inputs.

List of Regular Expression to Use

  • Alphabets and Space [a-zA-Z ]+$
  • Alphabets ^[A-z]+$
  • Numbers ^[0-9]+$
  • Alphanumeric ^[a-zA-Z0-9]*$
  • Email
  • [a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?
  • Mobile no. ^([7-9]{1})([0-9]{9})$
  • Date Format( mm/dd/yyyy | mm-dd-yyyy | mm.dd.yyyy)
  • /^(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\\d\\d+$/
  • Website URL ^http(s)?://([\\w-]+.)+[\\w-]+(/[\\w- ./?%&=])?$
  • Credit Card Numbers
  • Visa ^4[0-9]{12}(?:[0-9]{3})?$
  • MasterCard ^5[1-5][0-9]{14}$
  • American Express ^3[47][0-9]{13}$
  • Decimal number ((\\d+)((\\.\\d{1,2})?))$

But Regular Expression was for fields which do not allow HTML as input, but in an application where we need to take HTML as input there we need another techie to defend right.

2. HTML Encoding using Razor

The Razor engine used in MVC automatically encodes all output sourced from variables unless you work really hard to prevent it doing so.

For the demo, I have stored some XSS attack script in the database which must get executed when we render this on View.

Image 35

Figure 24. Stored malicious script into the database.

When I display these values on Index View which do not get executed because Razor engine used in MVC automatically encodes all output values.

Image 36

Figure 25. In Debug mode to show how the malicious script is rendered.

Below are encoded values from Razor View engine which does allow scripts to get executed.

Image 37

Figure 26. Displaying malicious script values which are stored in the database.

Will now show you what if you do not encode this value properly.

Image 38

Figure 27. In Debug mode to show how the malicious script is rendered.

If we don’t encode values properly, then this will allow executing a malicious script.

Image 39

Figure 28. Executed malicious script stored in the database.

3. URL Encoding

We mostly use URL to transfer data from open page another page using query string, but we never encode data which we are sending which can also lead to an XSS attack.

For HTML Encode, URL Encode we have built-in the library which we need to install from NuGet’s.

Image 40

Figure 29. Installing NuGet’s package.

Encoding URL on View.

Image 41

After encoding EmailId field on View, next we are going see the value of an encoded field using developer tools.

Image 42

Figure 30. Debug mode to display the value of an encoded field.

Displaying how we can encode URL and HTML.

Image 43

4. Malicious File Upload

We mostly secure our input fields by applying client-side and server-side validation, but when it comes to file upload control, we ignore validation, am I right? The one thing we validate in file upload is the file extension if it is proper, then we consider the file as a valid file. But in a real scenario, things are different. An attacker can upload a malicious file which may cause a security issue. The attacker can change file extension [tuto.exe to tuto.jpeg] and the malicious script can be uploaded as an image file. Most developers just look on the file extension of the file and save in folder or database, but file extension is valid not file, it may have a malicious script.

Image 44

Solution:

  • The first thing we need to do is validate file uploads.
  • Allow only access to files extension which are required.
  • Check the file header.

Image 45

Figure 31. Displaying file upload control along with HTML helper.

We have added File upload control on View, next we are going validate file on Submit.

Validating File Uploads on Index HttpPost Method

In this method, first we are going to check file upload count if the count is zero, then User has not uploaded any file.

If file count is greater than zero, then it has a file and we are going to read Filename of File along with Content Type and File Bytes.

Code Snippet

C#
[HttpPost]
public IActionResult Document(UploadModel uploadModel)
{
    if (ModelState.IsValid)
    {
        var upload = HttpContext.Request.Form.Files;

        if (HttpContext.Request.Form.Files.Count == 0)
        {
            ModelState.AddModelError("File", "Please Upload Your file");
        }
        else
        {
            foreach (var file in upload)
            {
                if (file.Length > 0)
                {
                    byte[] tempFileBytes = null;
                    var fileName = file.FileName.Trim();

                    using (BinaryReader reader = new BinaryReader(file.OpenReadStream()))
                    {
                        tempFileBytes = reader.ReadBytes((int)file.Length);
                    }

                    var myUniqueFileName = Convert.ToString(Guid.NewGuid());

                    var filetype = Path.GetExtension(fileName).Replace('.', ' ').Trim();

                    var fileExtension = Path.GetExtension(fileName);
                     // Setting Image type
                    var types = CoreSecurity.Filters.FileUploadCheck.FileType.Image;  
                    // Validate Header
                    var result = FileUploadCheck.isValidFile(tempFileBytes, types, filetype); 

                    if (result)
                    {
                        var newFileName = string.Concat(myUniqueFileName, fileExtension);
                        fileName = Path.Combine(_environment.WebRootPath, "images") + 
                                   $@"\{newFileName}";
                        using (FileStream fs = System.IO.File.Create(fileName))
                        {
                            file.CopyTo(fs);
                            fs.Flush();
                        }
                    }
                }
            }
        }
    }
    return View(uploadModel);
}

Until now, we have done basic validation. Let’s validate file, which is uploaded. For doing that, I have written a static class with name FileUploadCheck. In this class, there is Various Method for validating different file type, for now, I am going to show you how to validate Images files and allow image files only.

Image 46

Figure 32. FileUploadCheck class.

In the above snapshot, there is ImageFileExtension enum which contains Image formats and File types.

C#
private enum ImageFileExtension
{
    none = 0,
    jpg = 1,
    jpeg = 2,
    bmp = 3,
    gif = 4,
    png = 5
}
public enum FileType
{
    Image = 1,
    Video = 2,
    PDF = 3,
    Text = 4,
    DOC = 5,
    DOCX = 6,
    PPT = 7,
}

If it has passed basic validation, then we are going to call isValidFileMethod which take bytes, File type, FileContentType as input.

C#
public static bool IsValidFile(byte[] bytFile, FileType flType, String fileContentType)
 {
     bool isvalid = false;

     if (flType == FileType.Image)
     {
         isvalid = IsValidImageFile(bytFile, fileContentType);
     }
     else if (flType == FileType.Video)
     {
         isvalid = IsValidVideoFile(bytFile, fileContentType);
     }
     else if (flType == FileType.PDF)
     {
         isvalid = IsValidPdfFile(bytFile, fileContentType);
     }

     return isvalid;
 }

After calling the isValidFile method, it will call another static method based on File type.

If File type is image, then it will call first method [isValidImageFile], else File type is Video, then second method [isValidVideoFile] and in a similar way, if File type is PDF, then last method [isValidPDFFile] will get called.

After completing with understanding isValidFileMethod, let’s have a look at [isValidImageFile] method which will get called.

Below is the complete code snippet of [isValidImageFile] method.

In this method, we are allowing limited Image file extensions [jpg, jpeg, png, bmp, gif].

Working with this isValidImageFile method.

When we pass bytes and FileContentType to this method, then it will first check for FileContentType on the basis of that, it will set ImageFileExtension, after that it is going to check header bytes which we have against uploaded file bytes if it matches that, then File is valid [true] else file is invalid [false].

C#
public static bool IsValidImageFile(byte[] bytFile, String fileContentType)
{
    bool isvalid = false;

    byte[] chkBytejpg = { 255, 216, 255, 224 };
    byte[] chkBytebmp = { 66, 77 };
    byte[] chkBytegif = { 71, 73, 70, 56 };
    byte[] chkBytepng = { 137, 80, 78, 71 };

    ImageFileExtension imgfileExtn = ImageFileExtension.none;

    if (fileContentType.Contains("jpg") | fileContentType.Contains("jpeg"))
    {
        imgfileExtn = ImageFileExtension.jpg;
    }
    else if (fileContentType.Contains("png"))
    {
        imgfileExtn = ImageFileExtension.png;
    }
    else if (fileContentType.Contains("bmp"))
    {
        imgfileExtn = ImageFileExtension.bmp;
    }
    else if (fileContentType.Contains("gif"))
    {
        imgfileExtn = ImageFileExtension.gif;
    }

    if (imgfileExtn == ImageFileExtension.jpg || imgfileExtn == ImageFileExtension.jpeg)
    {
        if (bytFile.Length >= 4)
        {
            int j = 0;
            for (Int32 i = 0; i <= 3; i++)
            {
                if (bytFile[i] == chkBytejpg[i])
                {
                    j = j + 1;
                    if (j == 3)
                    {
                        isvalid = true;
                    }
                }
            }
        }
    }

    if (imgfileExtn == ImageFileExtension.png)
    {
        if (bytFile.Length >= 4)
        {
            int j = 0;
            for (Int32 i = 0; i <= 3; i++)
            {
                if (bytFile[i] == chkBytepng[i])
                {
                    j = j + 1;
                    if (j == 3)
                    {
                        isvalid = true;
                    }
                }
            }
        }
    }

    if (imgfileExtn == ImageFileExtension.bmp)
    {
        if (bytFile.Length >= 4)
        {
            int j = 0;
            for (Int32 i = 0; i <= 1; i++)
            {
                if (bytFile[i] == chkBytebmp[i])
                {
                    j = j + 1;
                    if (j == 2)
                    {
                        isvalid = true;
                    }
                }
            }
        }
    }

    if (imgfileExtn == ImageFileExtension.gif)
    {
        if (bytFile.Length >= 4)
        {
            int j = 0;
            for (Int32 i = 0; i <= 1; i++)
            {
                if (bytFile[i] == chkBytegif[i])
                {
                    j = j + 1;
                    if (j == 3)
                    {
                        isvalid = true;
                    }
                }
            }
        }
    }

    return isvalid;
}

Calling isValidFile method from Action Method

We are going to call (FileUploadCheck.isValidFile) method and then we are going to pass Parameter File Bytes, Types, FileContentType.

This method will return Boolean value, if it is valid file then true, else it will return false.

C#
byte[] tempFileBytes = null;

 // getting File Name
 var fileName = file.FileName.Trim();

 using (BinaryReader reader = new BinaryReader(file.OpenReadStream()))
 {
     // getting filebytes
     tempFileBytes = reader.ReadBytes((int)file.Length);
 }

 // Creating new FileName
 var myUniqueFileName = Convert.ToString(Guid.NewGuid());

 var filetype = Path.GetExtension(fileName).Replace('.', ' ').Trim();

 // getting FileExtension
 var fileExtension = Path.GetExtension(fileName);

 var types = CoreSecurity.Filters.FileUploadCheck.FileType.Image;  // Setting Image type
 var result = FileUploadCheck.IsValidFile(tempFileBytes, types, filetype); // Validate Header

After understanding code snippet, now let’s see a demo with how it works.

The below snapshot shows an Employee form with file upload control.

We are going to fill the below form and choose a valid file.

Image 47

Figure 33. File Upload.

Snapshot of Debugging Index Post Action Method

In this part, we have posted a form with a file. Here, we can see how it is validating basic validations.

Image 48

Figure 34. Debug mode after uploading an image.

Snapshot of debugging Index Post Action Method

In this part, you can see the real-time value of file which we have uploaded. It has passed basic validation.

Image 49

Snapshot of FileUploadCheck class while isVaildFile method gets called.

In this part, after calling isValidFile method, it going to call another method according to its FileContentType.

Image 50

Snapshot of isVaildImageFile Method while checking Header Bytes

In this method, it will check header bytes of the image which is uploaded against the bytes which we have if it matches, then it is a valid file else it is not a valid file.

Image 51

Figure 35. Checking bytes headers.

5. Security Misconfiguration (Error Handling Must Setup Custom Error Page)

As we develop web application, then end output is always an HTML right, but this is downloaded at the end-user end, if end-user is bit intelligent then he plays with HTML tags and try to change values at the client end and post it to the server. That’s why it is always mandatory to do both client-side and server-side validation.

So, let us demonstrate the same practically.

Example

For showing demo, I have created an Employee form which takes basic Employee details.

Image 52

Figure 36. Registration form.

Registration Model View

C#
public class Registration
    {
        public int? RegistrationId { get; set; }

        [Required(ErrorMessage = "Enter FirstName")]
        [StringLength(50, ErrorMessage = "Only 50 Characters are Allowed")]
        public string FirstName { get; set; }

        [StringLength(50, ErrorMessage = "Only 50 Characters are Allowed")]
        [Required(ErrorMessage = "Enter LastName")]
        public string LastName { get; set; }

        [EmailAddress(ErrorMessage = "Invalid Email Address")]
        [Required(ErrorMessage = "Enter EmailId")]
        public string EmailId { get; set; }

        [Required(ErrorMessage = "Enter Username")]    
        public string Username { get; set; }

        [Required(ErrorMessage = "Enter Password")]
        public string Password { get; set; }

        [NotMapped]
        [Required(ErrorMessage = "Enter ConfirmPassword")]
        [Compare("Password", ErrorMessage = "Password does not match")]
        public string ConfirmPassword { get; set; }


        public DateTime? CreatedDate { get; set; }
        public DateTime? UpdateDate { get; set; }
        public bool? Status { get; set; }
    }

So are data annotation validations more than enough to secure the page. No, that’s not enough for securing page I will show you a small demo of how these validations get bypassed.

The below snapshot shows that FirstName field is validating. It asks for only 50 characters.

Image 53

Figure 37. After adding validation to Model, now we are validating form you can see FirstName input field which only accepts 50 characters, it is not accepting more than 50 characters, it shows Error and message.

Intercepting Add Registration View

Now let’s intercept this form and then submit to the server from the intercept. I am using a tool called as burp suit, which catches your request that is going to the server and coming from the server.

In the below snapshot, I have caught a request which is going to the server.

Image 54

Figure 38. Intercepting Add Registration form using burp suite, you can view that if the user submits the form, then it catches data in this tool.

In the below snapshot, the request which I have caught is going to the server. You can see that I have changed the FirstName field which is only taking 50 I have added more than 50 characters and then submitted to the server.

Intercepted FirstName field of Add Registration Form

Image 55

Figure 39. Intercepted FirstName field in burp suite and submitted to the server.

Below is the snapshot which shows the request which is submitted to the server which has more than 50 characters.

Debugging Mode of the Registration Form

After submitting more than 50 characters to the FirstName fields, the server throws an exception. Because in the database, the data type of FirstName field is varchar(50) and the data is more than 50 so an exception is but obvious.

Image 56

Figure 40. Intercepted FirstName field in burp suite and submitted to the server.

Image 57

Figure 41. Intercepted FirstName field Caused an error.

Problem With Displaying Error Occurred to Users

Now exception which occurs is directly displayed to the attacker which leaks lots of valuable information about the server and our program behavior. Using this error information, he can try various permutations and combinations to exploit our system.

Image 58

Figure 42. Displaying Error directly to Users.

So, the solution here is we need to set some kind of error page which does not show the internal technical error, but rather shows a custom error message.

We have two approaches for it:

  • Create a custom Error handling Attribute
  • Setting Custom Error page

Solution 1

Create a custom Error handling Attribute using ExceptionFilterAttribute.

This attribute will handle the exception and write an exception in a text file according to date, it is stored inside the wwwroot folder -> ErrorLogPath.

If you also store this exception in the database, then you just need to add a table and write ADO.NET code for inserting data.

C#
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
    private readonly IHostingEnvironment _hostingEnvironment;

    public CustomExceptionFilterAttribute(IHostingEnvironment hostingEnvironment)
    {
        _hostingEnvironment = hostingEnvironment;
    }

    public override void OnException(ExceptionContext context)
    {
        string strLogText = "";
        Exception ex = context.Exception;

        context.ExceptionHandled = true;
        var objClass = context;
        strLogText += "Message ---\n{0}" + ex.Message;

        if (context.HttpContext.Request.Headers["x-requested-with"] == "XMLHttpRequest")
        {
            strLogText += Environment.NewLine + ".Net Error ---\n{0}" + 
                          "Check MVC Ajax Code For Error";
        }

        strLogText += Environment.NewLine + "Source ---\n{0}" + ex.Source;
        strLogText += Environment.NewLine + "StackTrace ---\n{0}" + ex.StackTrace;
        strLogText += Environment.NewLine + "TargetSite ---\n{0}" + ex.TargetSite;
        if (ex.InnerException != null)
        {
            strLogText += Environment.NewLine + "Inner Exception is {0}" + ex.InnerException;
            //error prone
        }
        if (ex.HelpLink != null)
        {
            strLogText += Environment.NewLine + "HelpLink ---\n{0}" + 
                          ex.HelpLink;//error prone
        }

        StreamWriter log;

        string timestamp = DateTime.Now.ToString("d-MMMM-yyyy", new CultureInfo("en-GB"));

        string errorFolder = Path.Combine(_hostingEnvironment.WebRootPath, "ErrorLog");

        if (!System.IO.Directory.Exists(errorFolder))
        {
            System.IO.Directory.CreateDirectory(errorFolder);
        }

        // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
        if (!File.Exists(String.Format(@"{0}\Log_{1}.txt", errorFolder, timestamp)))
        {
            log = new StreamWriter(String.Format(@"{0}\Log_{1}.txt", errorFolder, timestamp));
        }
        else
        {
            log = File.AppendText(String.Format(@"{0}\Log_{1}.txt", errorFolder, timestamp));
        }

        var controllerName = (string)context.RouteData.Values["controller"];
        var actionName = (string)context.RouteData.Values["action"];

        // Write to the file:
        log.WriteLine(Environment.NewLine + DateTime.Now);
        log.WriteLine
        ("------------------------------------------------------------------------------------------------");
        log.WriteLine("Controller Name :- " + controllerName);
        log.WriteLine("Action Method Name :- " + actionName);
        log.WriteLine
        ("------------------------------------------------------------------------------------------------");
        log.WriteLine(objClass);
        log.WriteLine(strLogText);
        log.WriteLine();

        // Close the stream:
        log.Close();
        context.HttpContext.Session.Clear();

        if (!_hostingEnvironment.IsDevelopment())
        {
            // do nothing
            return;
        }
        var result = new RedirectToRouteResult(
        new RouteValueDictionary
        {
            {"controller", "Errorview"}, {"action", "Error"}
        });
        // TODO: Pass additional detailed data via ViewData
        context.Result = result;
    }
}

After creating CustomExceptionFilterAttribute, next, we are going to register this filter globally.

Image 59

Figure 43. Registering CustomExceptionFilterAttribute filter globally.

Whenever an error occurs, the CustomExceptionFilter attribute will get called and it will redirect to Errorview Controller and Error Action method.

Image 60

Figure 44. Displaying Custom Error page if any error occurs in Application.

The occurred exception is also being stored in ErrorLog folder which is inside the wwwroot folder.

Image 61

Figure 45. Displaying Errors stored in the error log folder.

Solution 2

The ASP.NET Core has three environments:

  1. Development
  2. Staging
  3. Production

Image 62

Before deploying the application in production, we should set Hosting environment to "Production".

Next, we are going to configure UseExceptionHandler middleware. Before that, we can check our hosting environment name by env.IsProduction()" method if it is set to "Production", the if statement will execute.

Image 63

Figure 46. Configuring Exception Handler.

Inside that, we are going to set "UseExceptionHandler" middleware where you can just set your error handling path for a production application.

If any error occurs, it will call "Home" Controller and Error Action method which will display error view.

Image 64

Figure 47. Exception page in the Development environment.

Note: Enable the developer exception page only when the app is running in the Development environment.

6. Version Discloser

The version in which application is developed must not be disclosed to end-user, because if an attacker gets a specific version in which application is developed, then he may try to target specific attack on that Version which is disclosed.

Whenever browser sends HTTP to request to the server in response we get response header which contains information of [ Server, X-Powered-By, X-SourceFiles]

The server shows information about which web server is begin used.

Server: Kestrel: application as hosted by Kestrel

X-Powered-By: ASP.NET: shows information of which framework your website is running on.

Note

X-SourceFiles header is only generated for localhost requests and serves debugging purposes of Visual Studio and IIS Express.

Image 65

Figure 48. Response header disclosing Version Information.

Solution:

  1. For removing Server: Kestrel header
  2. For removing X-Powered-By: ASP.NET header
  3. Adding NWebsec.AspNetCore.Middleware from NuGet for securing headers

For Removing Server: Kestrel Header

For removing Server: Kestrel header you need to set "AddServerHeader = false".

Code Snippet

C#
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace CoreSecurity
{
    public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseKestrel(c => c.AddServerHeader = false)
                .UseStartup<Startup>()
                .Build();
    }
}

Image 66

Figure 49. Removing the Server header.

Below is the snapshot after removing Server: Kestrel header.

Image 67

Figure 50. Response after Removing Server header.

For Removing X-Powered-By: ASP.NET Header

For removing this header, we need to add a web.config file to project.

Image 68

Figure 51. Adding web.config file to project.

After adding web.config file, we are going to add <httpProtocol> element under <system.webServer> element for removing "X-Powered-By" header.

Code Snippet of web.config File When Newly Added

XML
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <!-- To customize the asp.net core module uncomment and edit the following section. 
  For more info see https://go.microsoft.com/fwlink/?linkid=838655 -->
  <system.webServer>
    <handlers>
      <remove name="aspNetCore"/>
      <add name="aspNetCore" path="*" verb="*" 
      modules="AspNetCoreModule" resourceType="Unspecified"/>
    </handlers>
    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" 
    stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
  </system.webServer>
</configuration>

Elements to Add Under <system.webServer>

XML
<httpProtocol>
  <customHeaders>
    <remove name="X-Powered-By" />
  </customHeaders>
</httpProtocol>

Code Snippet of web.config File after Adding <httpProtocol> Element under <system.webServer>

XML
<?xml version="1.0" encoding="utf-8"?>
<configuration>

  <!-- To customize the asp.net core module uncomment and edit the following section. 
  For more info see https://go.microsoft.com/fwlink/?linkid=838655 -->
  
  <system.webServer>
    <handlers>
      <remove name="aspNetCore"/>
      <add name="aspNetCore" path="*" verb="*" 
      modules="AspNetCoreModule" resourceType="Unspecified"/>
    </handlers>
    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" 
    stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
    <httpProtocol>
      <customHeaders>
        <remove name="X-Powered-By" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

Below is the snapshot after removing "X-Powered-By" header.

Image 69

Figure 52. Snapshot after removing "X-Powered-By" header.

Adding NWebsec.AspNetCore.Middleware from NuGet for Securing Headers

NWebsec middleware for ASP.NET Core applications.

NWebsec helps you set important security headers and detect potentially dangerous redirects.

It NWebsec prevents against cross-site scripting, iframes and prevents clickjacking attacks.

Image 70

Figure 53. Installing NWebsec.AspNetCore.Middleware from NuGet for Securing Headers.

Code Snippet to add in Configure Method in Startup Class

XML
// X-Content-Type-Options header
app.UseXContentTypeOptions();
// Referrer-Policy header.
app.UseReferrerPolicy(opts => opts.NoReferrer());
// X-Xss-Protection header
app.UseXXssProtection(options => options.EnabledWithBlockMode());
// X-Frame-Options header
app.UseXfo(options => options.Deny());
// Content-Security-Policy header
app.UseCsp(opts => opts
    .BlockAllMixedContent()
    .StyleSources(s => s.Self())
    .StyleSources(s => s.UnsafeInline())
    .FontSources(s => s.Self())
    .FormActions(s => s.Self())
    .FrameAncestors(s => s.Self())
    .ImageSources(s => s.Self())
    .ScriptSources(s => s.Self())
);

Image 71

Figure 54. Configuring NWebsec.AspNetCore Middleware.

Below is the snapshot after adding headers to protect.

Image 72

Figure 55. Adding Response header to protect against clickjacking and cross-site scripting (XSS) attacks.

Referenced from: https://damienbod.com/2018/02/08/adding-http-headers-to-improve-security-in-an-asp-net-mvc-core-application/

7. Cross-Site Request Forgery (CSRF)

Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they're currently authenticated. CSRF attacks specifically target state-changing requests, not theft of data since the attacker has no way to see the response to the forged request. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker's choosing. If the victim is a normal user, a successful CSRF attack can force the user to perform state-changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrative account, CSRF can compromise the entire web application.

Definition is referenced from: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)

Image 73

Take this Simple Example

User logs in to the bank server.

Bank authorizes and a secure session is established between the user and the bank server.

The attacker sends an email with a malicious link saying "Earn 100000$ now" to the user.

User clicks on the malicious link and the site tries to transfer money from your account to the attacker's account.

Because the secure session is established, the malicious code can execute successfully.

Microsoft has recognized this threat and for preventing the same, we have something called as AntiForgeryToken.

Solution

We need to add asp-antiforgery="true" HTML attribute on form HTML tag and set its property to true to generate anti-forgery. By default, it is false which means it will not generate an anti-forgery token. And on the Action Method which handles your post ([HttpPost]) Request, we need to add [ValidateAntiForgeryToken] attribute which will check if the token is valid or not.

Image 74

Figure 56. Setting anti-forgery attribute on form tag.

Adding [ValidateAntiForgeryToken] Attribute to [HttpPost] Method.

Image 75

Working of AntiForgeryToken

When we set anti-forgery token to true (asp-antiforgery="true") on form help tag, it creates a hidden field and assigns a unique token value to it and meanwhile a session Cookie is added to the browser.

When we post form HTML, it checks for __RequestVerificationToken Hidden field and whether __RequestVerificationToken Cookie is present or not. If either the cookie or the form __RequestVerificationToken Hidden field values are missing, or the values don't match, ASP.NET MVC does not process the action. This is how we can prevent cross-site request forgery attack in ASP.NET MVC.

Image 76

Figure 56. RequestVerificationToken generated the hidden field.

RequestVerificationToken Cookie snapshot.

Image 77

Figure 57. RequestVerificationToken generated a cookie.

8. XML External Entities (XXE)

This attack is against an application that parses XML. We nowadays consume various web services which return XML if we do not check what response we are receiving, then it may cause the attack to take place.

  1. Billion Laughs attack is illustrated in the XML file
  2. External Entities Attack

Note: Document Type Definition (DTD)

Billion Laughs Attack

Image 78

Figure 58. Billion Laughs attack XML

This kind of attack causes Denial of Service (DoS) attack which makes your server utilization too high which causes server shutdown.

Billion Laughs Attack is Illustrated in the XML File

XML
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>
Figure 59. Billion Laughs attack XML.

For showing demo, I have added a controller with name XXE controller which contains index action method.

In index action method, I am going to consume an XML from "XmlFiles" folder which contains the malicious XML ("Billion Laughs attack XML") then we are going to parse XML using "XmlTextReader".

CodeProject

C#
[HttpGet]
public IActionResult Index()
{
    var fileName = Path.Combine(_environment.WebRootPath, "XmlFiles") + "\\temp.xml";
    XmlTextReader reader = new XmlTextReader(fileName);
    reader.DtdProcessing = DtdProcessing.Parse;
    while (reader.Read())
    {
        var data = reader.Value;
    }
    return View();
}

If you run this example, then you can see your system will have high utilization do at your own risk.

The Solution to this Attack

To avoid this kind of attack, we can set the DtdProcessing property of "XmlTextReader" to Prohibit or Ignore.

  • Prohibit – Throws an exception if a DTD is identified
  • Ignore – Ignores any DTD specifications in the document, skipping over them and continues processing the document
  • Parse (Default) – Will parse any DTD specifications in the document. (Potentially vulnerable)

CodeProject

C#
[HttpGet]
public IActionResult Index()
{
    var fileName = Path.Combine(_environment.WebRootPath, "XmlFiles") + "\\temp.xml";
    XmlTextReader reader = new XmlTextReader(fileName);
    reader.DtdProcessing = DtdProcessing.Ignore;
    while (reader.Read())
    {
        var data = reader.Value;
    }

    return View();
}

External Entities Attack

This attack malicious XML is configured in such a way that it can access files from your server, this attack is from external source hence it is named as XML external entities.

For a demo, I have stored a text file on my D drive with name demo.txt.

Image 79

Now I will try to access this file from using malicious XML.

External Entities Attack is Illustrated in the XML

C#
<?xml version=\"1.0\"?><!DOCTYPE doc 
[<!ENTITY win SYSTEM \"file:///D:/demo.txt\">]><doc>&win;</doc>

Code Snippet

C#
using System;
using System.IO;
using System.Xml;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string xml = "<?xml version=\"1.0\"?><!DOCTYPE doc 
            [<!ENTITY win SYSTEM \"file:///D:/demo.txt\">]><doc>&win;</doc>";

            XmlReaderSettings rs = new XmlReaderSettings();
            rs.DtdProcessing = DtdProcessing.Parse;

            XmlReader myReader = XmlReader.Create(new StringReader(xml), rs);

            while (myReader.Read())
            {
                Console.WriteLine(myReader.Value);
            }
            Console.ReadLine();
        }
    }
}

Image 80

Figure 60. Output after executing malicious XML.

The Solution to this Attack

To avoid this kind of attack, we can set the DtdProcessing property of "XmlTextReader" to Prohibit or Ignore.

After setting this property, it will not allow XML to parse.

Image 81

Figure 61. Error after setting DtdProcessing property to Prohibit.

9. Insecure Deserialization

Before going into understanding what is Insecure Deserialization, let’s understand what is serialization and Deserialization process?

Serialization is a task to converting an object into a stream of bytes such that it can be stored or transported.

Deserialization is the reverse of Serialization in that stream of bytes are again converted into an object.

In a Modern era of web development, we frequently use Serialization and Deserialization with JSON data and XML data. But the Serialization process does do not have Issues because we do that process at our application end, but Deserialization process we received files or stream of bytes from trusted and untrusted source which cause issues.

We frequently use databases, cache servers, file systems to store Serialize data and:

  1. denial-of-Service (DoS) attack
  2. remote code execution attack
  3. data tampering or access control

Image 82

Figure 62. Serialization and Deserialization process.

Affected Libraries snapshot

Breaking .NET Through Serialization by James Forshaw whitepapers@contextis.com

Link to document where this snapshot is taken from.

https://media.blackhat.com/bh-us-12/Briefings/Forshaw/BH_US_12_Forshaw_Are_You_My_Type_WP.pdf

Image 83

Figure 63. Affected Libraries.

Example of Deserialization

I have created a simple API with name "stock" in ASP.NET Core which gets stock data from a database according to company name (name) parameter which consumer sends and in response, we send stock data of that company only.

Valid Request

Request URL: http://localhost:57777/api/Stock

Request Json:

JavaScript
{'Stockid':"1",'Name':"GComp1"}

Image 84

Figure 64. Posting valid request. Debugging view after sending request.

Image 85

Figure 65. Debug view after posting values.

Invalid Request

Request URL: http://localhost:57777/api/Stock

Request Json:

JavaScript
{'Stockid':"1",'Name':" 'or'1'='1 "}

Image 86

Figure 66. Posting invalid request (malicious request).

Debugging view after sending request.

Image 87

Figure 67. Debug view after posting malicious values.

In this process, you can see I am sending a malicious JSON data to API and that is causing an SQL injection attack.

The Solution to this Attack

  1. If you are using third-party NuGet extension, check the latest updates and also check they have a solution to this kind of attack.
  2. Check input data which you receive before deserialization (JSON and XML).
  3. To avoid SQL injection attack using parameterize query or stored procedures, Entity framework also uses parameterize query.

Use Parameterized Queries to Query SQL Server

Image 88

Figure 68. Parameterized queries.

10. SQL Injection Attack

The top attack for the last three years is SQL injection attack. Because every application needs a database where data is been stored, data is valuable. SQL injection attack can give valuable data to the attacker that can lead to a big security breach and can also take full access to the database server.

In SQL Injection, attacker always try to enter malicious SQL statement which will get executed in the database and return unwanted data to the attacker.

Image 89

Figure 69. SQL injection attack example which shows how attack mostly occurs if you are using inline queries.

Simple View which shows User data

View shows single Registration data based on RegistrationId as displayed in the below snapshot.

Image 90

Figure 70. Registration View which displays user data.

Simple View which Shows All User Data after SQL Injection Attack

In this Browser View as attacker saw Application URL which contains some valuable data which is ID [http://localhost:3837/demo/index?Id=1], attacker tries SQL Injection attack as shown below:

Image 91

Figure 71. Registration View which displays all User data after SQL injection.

After trying permutation and combination of SQL injection attack, the attacker gets access to all user data.

Displaying SQL Injection in Debug Mode

Here, we can see in detail how attacker passed malicious SQL statement which gets executed in the database.

Image 92

Figure 72. Debug mode which shows malicious SQL statement while getting executed.

SQL Profiler View of SQL Statement

Image 93

Figure 73. SQL Profiler view after executing the SQL Statement.

Solution:

  1. Validate inputs
  2. Use of low-privileged database logins
  3. Use Parameterized queries
  4. Use ORM (e.g., Dapper, Entity framework)
  5. Use Stored Procedures

Validate Inputs

Always validate input in both side Client side and Server side such that no special characters are entered in inputs which allow an attacker to get into the system.

In MVC, we use Data Annotations for Validation.

Data Annotations attribute are a simple rule that is applied to the Model to validate Model data.

Image 94

Figure 74. View After Applying regular expression.

Server-Side Validating Inputs

Model state is false - this indicates that Model is not valid.

Image 95

Use of Low-Privileged Database Logins

Db_owner is default role for the database which can grant and revoke access, create tables, stored procedures, views, run backups, schedule jobs can even drop the database. If this kind of Role access is given to database, then User who is using can have full access and he can perform various activities on it. We must create a new user account with least-privileged and give only that privilege that user must access.

For example, if the user has to work related to Selecting, Inserting and Updating Employee details, then only select, Insert and update permission should be provided.

Image 96

Figure 75. Creating a New User and assign access to objects.

Step to Add New User and Provide Privilege

After choosing table, we are going to provide permission to the table as shown below, you can see that we have only given permission to ‘Insert’, ‘Select’, ‘Update’ for this User.

Image 97

Figure 76. Assigning Permission.

With least-privileged, it will help us to safeguard database from attackers.

Use Stored Procedures

Stored procedures are a form of parameterized query. Using Stored Procedures is also one way to protect from SQL injection attack.

In the below snapshot, we have removed inline Query which we were using. Before now, we have written code for getting data from database using stored procedure, which helps us to protect against SQL injection attack, then to stored procedure, we need to pass parameter [@RegistrationID] and according to parameter, it is going to get records from database after passing parameter, we have used CommandType [CommadType.StoredProcedure] which indicates we are using a stored procedure.

Note: Always use parameter with the stored procedure if you do not use it, you are still vulnerable to SQL injection attack.

Image 98

Figure 77. Use Stored Procedures.

After using Stored Procedure, let's Request Demo View.

Registration View Which Displays Record

Image 99

Figure 78. Registration View which displays user details according to Registration ID.

Profiler View After Using Stored Procedure

Displaying trace of stored procedure which we have used.

Image 100

Figure 79. SQL Profiler view after executing the SQL Statement.

Using Stored Procedure, let’s try SQL injection attack again.

Image 101

Displaying SQL Injection Attack Execution in Debug Mode after using Stored Procedure

If you have look at parameter id [?Id=1or 1=1] which contains malicious SQL script after passing it to the stored procedure, it shows an error which indicates that it not get executed because parameter which we are passing is an integer which only takes numeric values as input if we pass malicious SQL script [?Id=1 or 1=1] it throws an error.

Image 102

Profiler View after using Stored Procedure

Image 103

Figure 80. SQL Profiler view after executing the SQL Statement.

Output

Image 104

Figure 81. Displaying error page after executing Malicious SQL script.

Use Parameterized Queries

Using parameterized Queries is another solution to prevent against SQL injection attack. It is similar to the stored procedure. Instead of a concatenating string, you need to add parameters to the SQL query and pass parameter value using the SqlCommand.

Image 105

Figure 81. Performing SQL injection attack.

Image 106

Figure 82. An error occurred after executing Malicious SQL script.

Profiler View of parameterized Query

Image 107

Figure 83. SQL Profiler view after executing SQL Statement

Output

Image 108

Figure 84. Displaying error page after executing Malicious SQL script.

Using ORM (Entity Framework)

ORM stands for Object Relational Mapper which maps SQL object to your domain object [C#].

If you are using entity framework properly, you are not prone to SQL injection attack because entity framework internal uses parameterized query.

Normal Execution of Entity Framework

Here, we have passed the name as parameter [?name=Saineshwar].

Image 109

Figure 85. Passing firstName value from the Query string.

Snapshot of the Controller During Execution

In debug mode, we can see that we have passed name parameter from Querystring which we then passed to LINQ query to get records.

Image 110

Figure 86. Debug view Passing firstName value from the Query string.

Profiler View of LINQ Query

Entity Framework internally uses Parameterized query it's true, here is a snapshot.

Image 111

Figure 87. SQL Profiler view after executing the SQL Statement.

Let’s Try SQL Injection Attack on Entity Framework

In this part, we are trying SQL injection attack on entity framework. For that, we have passed the parameter as [?firstName=Saineshwar or 1=1].

Image 112

Figure 88. SQL Injection Attack on Entity Framework.

Snapshot of the Controller During Execution

In debug mode, we can see that we have passed name parameter from Querystring which we then passed to LINQ query to get records, but this time, it has a malicious script along with normal parameter.

Image 113

Figure 89. SQL Injection Attack on Entity Framework.

Profiler View of LINQ Query

If you see trace closely, it is considering the name and malicious script as a single parameter which prevents it from SQL injection attack.

Image 114

Figure 90. SQL Profiler view after executing the SQL Statement.

Conclusion

In this article, we have learned how to secure ASP.NET Core MVC applications against the top 10 attacks given by OWSAP (Open Web Application Security Project) in a step by step manner.

Thank you for reading. I hope you liked my article.

History

  • 5th September, 2018: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
India India
Microsoft Most Valuable Professional
Code Project Most Valuable Author
C# Corner Most Valuable Professional

I am Senior Technical lead Working on.Net Web Technology
ASP.NET MVC,.Net Core,ASP.NET CORE, C#, SQL Server, MYSQL, MongoDB, Windows

Comments and Discussions

 
PraiseGreat article Pin
knoxitus29-Aug-19 4:32
knoxitus29-Aug-19 4:32 
QuestionAbout AuditFilterActionfilter Pin
Majid Shahabfar25-Feb-19 20:00
Majid Shahabfar25-Feb-19 20:00 
QuestionDropbox link is broken Pin
bspengr21-Feb-19 4:20
professionalbspengr21-Feb-19 4:20 
Questionusing MD5 Algorithm Pin
Member 1413503730-Jan-19 6:13
Member 1413503730-Jan-19 6:13 
GeneralMy vote of 5 Pin
Carsten V2.010-Sep-18 5:18
Carsten V2.010-Sep-18 5:18 
GeneralRe: My vote of 5 Pin
Saineshwar Bageri10-Sep-18 21:36
Saineshwar Bageri10-Sep-18 21:36 
Praisethanks for share.. Pin
yenai10-Sep-18 3:31
yenai10-Sep-18 3:31 
GeneralRe: thanks for share.. Pin
Saineshwar Bageri10-Sep-18 21:36
Saineshwar Bageri10-Sep-18 21:36 
QuestionMissing images Pin
Mulga5-Sep-18 19:38
Mulga5-Sep-18 19:38 
AnswerRe: Missing images Pin
Saineshwar Bageri5-Sep-18 21:44
Saineshwar Bageri5-Sep-18 21:44 
QuestionThey've already attacked you... Pin
Dewey5-Sep-18 14:47
Dewey5-Sep-18 14:47 
AnswerRe: They've already attacked you... Pin
Saineshwar Bageri5-Sep-18 17:10
Saineshwar Bageri5-Sep-18 17:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.