Click here to Skip to main content
13,768,132 members
Click here to Skip to main content
Add your own
alternative version

Stats

10.8K views
103 downloads
59 bookmarked
Posted 5 Sep 2018
Licenced CPOL

10 Points to Secure Your ASP.NET Core MVC Applications

, 5 Sep 2018
Rate this:
Please Sign up or sign in to vote.
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 step by step way.

Introduction

  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

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.

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 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 how to prevent this attack let’s have a look on small demo how Session Fixation attack occurs.

Whenever a user sends the first request to server the login page is loaded then User enter 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 send 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.

Fig 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.

Fig 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.

Fig 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.

Fig 4. Login Page.

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

Fig 5. Showing created cookies after logged 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 we have any [".AspNetCore.Session"] Cookie Created here. Oh, we don’t have any Cookie.

Fig 5. Showing cookies.

After having a view on [".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 assign similar values of SessionID to this Cookie.

Fig 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

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

After fixing cookie now we no more 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.

Fig 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.

CodeSnippet

[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 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.

CodeSnippet

[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.
// 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.

//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.

//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 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 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.

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 get session and cookie values.

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 check that both session and cookie values are not null. After that, we are going see values are equal of both session and a cookie. If they are not then we are going to redirect to login page.

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 apply this filter on every controller which user access when he is logged in to application.

Applying AuthenticateUser Filter

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

Fig 9. Applying the AuthenticateUser Filter.

After applying Action filter on all the controller which user access 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

Fig 10. Showing both Cookie Values

Use SSL for Securing Cookies and Session Values

SSL (Secure Sockets Layer) is Layer which 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).

Fig 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
//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);
  1. Setting HTTP only Globally in Startup.cs Configure Method.

Fig 12. Setting Cookie HttpOnly property to True.

Code snippet

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 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.

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 on Common login pages Issues which most of the developer are creating.

Fig 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.

Fig 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 you 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 below snapshot When User enters credentials and Post form.

Fig 15. Debug mode of Client-side Encryption.

Intercepting Post Request to show how the password is traveled in encrypted form.

Fig 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.

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.

Fig 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).

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.

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 shows how we have encrypted "Encrycolumn" column data.

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

Link for: Detail process of SQL Data Encryption and Decryption.

https://blog.sqlauthority.com/2009/04/28/sql-server-introduction-to-sql-server-encryption-and-symmetric-key-encryption-tutorial-with-script/

Below is a list of Algorithm 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 when the user.

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 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 trying to attack application then we can know their attempts.

Solution:

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

AuditTB Model

[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 look on Model which is generated now let’s create an AuditTB table.

AuditTB Table

Fig 18. AuditTb table view.

After having look on 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 is User 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

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

Fig 19. Registering UserAuditFilter in globally.

Output

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

Fig 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.

Ways 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.

Fig 21. Entering malicious script in inputs.

What is the first question arise 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".

Note:

But in ASP.NET Core you will not get this Error because ASP.NET Core 2.0 does not denies HTML in fields.

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

Fig 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.

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

Fig 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 execute when we render this on View.

Fig 24. Stored malicious script into the database.

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

Fig 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.

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

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

Fig 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.

Fig 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.

Fig 29. Installing NuGet’s package.

Encoding URL on View.

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

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

Displaying how we can encode URL and HTML.

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.

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.

Fig 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 reading Filename of File along with Content Type and File Bytes.

Code Snippet

[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 only allow image files only.

Fig 32. FileUploadCheck class.

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

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.

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 similar way if File type is PDF then last method [isValidPDFFile] will get called.

After completing with understanding isValidFileMethod let’s have a look on [isValidImageFile] Method which will get called.

Below is 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 base 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].

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.

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.

Below snapshot shows an Employee form with file upload control.

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

Fig 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 have it is validating basic validations.

Fig 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.

Snapshot of FileUploadCheck Class while isVaildFile Method gets Called.

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

Snapshot of isVaildImageFile Method while checking Header Bytes

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

Fig 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

Fig 36. Registration form.

Registration Model View.

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.

Below snapshot shows that FirstName field is validating it ask for only 50 characters.

Fig 37. After adding validation to Model now we are validating form you can see FirstName input field which only accepting 50 characters it is not accepting more than 50 characters it showing 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.

Fig 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

Fig 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.

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

Fig 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 leak lots of valuable information about the server and our program behavior. Using this error information, he can try various permutation and combination to exploit our system.

Fig 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 wise, it is stored inside the wwwroot folder -> ErrorLogPath.

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

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.

Fig 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.

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

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

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

Solution 2

The ASP.NET Core has three environments

  1. Development
  2. Staging
  3. Production

Before deploying the application on 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 is set to "Production" the if statement will execute.

Fig 46. Configuring Exception Handler.

Inside that, we are going to set "UseExceptionHandler" middleware where you can just set you 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.

Fig 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.

Fig 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

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();
    }
}

Fig 49. Removing the Server header.

Below is Snapshot after removing Server: Kestrel header.

Fig 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.

Fig 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 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>

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

Code Snippet of web.config file after adding <httpProtocol> element under <system.webServer>

<?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 Snapshot after removing "X-Powered-By" header.

Fig 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 prevent clickjacking attacks.

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

Code Snippet to add in Configure Method in startup class

// 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())
);

Fig 54. Configuring NWebsec.AspNetCore Middleware.

Below is Snapshot after adding headers to protect.

Fig 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)

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 try 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.

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

Adding [ValidateAntiForgeryToken] Attribute to [HttpPost] Method.

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.

Fig 56. RequestVerificationToken generated the hidden field.

RequestVerificationToken Cookie snapshot.

Fig 57. RequestVerificationToken generated a cookie.

8. XML External Entities (XXE)

This attack is against an application that parses XML we now days consume various web services which returns 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

Fig 58. Billion Laughs attack XML

This kind of attack cause Denial of service (DoS) attack which makes you server utilization too high which cause server shutdown.

Billion Laughs attack is illustrated in the XML file

<?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>
Fig 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

[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

[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.

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

External entities attack is illustrated in the XML

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

Code Snippet

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();
        }
    }
}

Fig 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.

Fig 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

Fig 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

Fig 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:

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

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

Fig 65. Debug view after posting values.

Invalid Request

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

Request Json:

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

Fig 66. Posting invalid request (malicious request).

Debugging view after sending request.

Fig 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 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

Fig 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.

Fig 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 below Snapshot.

Fig 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.

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

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

Displaying SQL injection in Debug Mode

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

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

SQL Profiler View of SQL statement

Fig 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.

Fig 74. View After Applying regular expression.

Server-side validating inputs

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

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 using can have full access and he can perform a various activity 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.

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

Step to add new User and Provide Privilege

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

Fig 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 CommadType [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.

Fig 77. Use Stored Procedures.

After Using Stored Procedure lets Request Demo View

Registration View which displays record

Fig 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.

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

Using Stored Procedure Let’s try SQL injection Attack Again

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 gets 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.

Profiler View after using Stored Procedure

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

Output:

Fig 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 concating string, you need to add parameters to the SQL query and pass parameter value using the SqlCommand.

Fig 81. Performing SQL injection attack.

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

Profiler View of parameterized Query

Fig 83. SQL Profiler view after executing SQL Statement

Output:

Fig 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].

Fig 85. Passing firstName value from the Query string.

Snapshot of the controller while Execution

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

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

Profiler View of LINQ query

Entity Framework internally uses Parameterized query its true here is a snapshot.

Fig 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].

Fig 88. SQL Injection Attack on Entity Framework.

Snapshot of the controller while Execution.

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

Fig 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.

Fig 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 top 10 attacks given by OWSAP (Open Web Application Security Project) in a step by step way.

Thank you, I hope you liked my article.

License

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

Share

About the Author

saineshwar bageri
Software Developer (Senior)
India India
I am Microsoft MVP | C# Corner MVP working on.Net Web Technology
[ASP.NET MVC,.Net Core,ASP.NET CORE, C#, Sqlserver, MYSQL, MongoDB, Windows]

Microsoft MVP Profile Link
https://mvp.microsoft.com/en-us/PublicProfile/5003160?fullName=Saineshwar%20%20Bageri

Prize.

First Prize: Best Web Dev Article of August 2016 with 10 Points to Secure Your ASP.NET MVC Applications.

Second Prize: Best Web Dev Article of April 2017 with Securing ASP.NET Web API using Custom Token Based Authentication

Second Prize:
Best Web Dev Article of February 2018 with Securing ASP.NET CORE Web API using Custom API Key based Authentication




You may also be interested in...

Pro

Comments and Discussions

 
QuestionMessage Closed Pin
18-Sep-18 1:44
membersuresh44618-Sep-18 1:44 
GeneralMy vote of 5 Pin
Carsten V2.010-Sep-18 6:18
memberCarsten V2.010-Sep-18 6:18 
GeneralRe: My vote of 5 Pin
saineshwar bageri10-Sep-18 22:36
membersaineshwar bageri10-Sep-18 22:36 
Praisethanks for share.. Pin
yenai10-Sep-18 4:31
memberyenai10-Sep-18 4:31 
GeneralRe: thanks for share.. Pin
saineshwar bageri10-Sep-18 22:36
membersaineshwar bageri10-Sep-18 22:36 
QuestionMissing images Pin
Mulga5-Sep-18 20:38
memberMulga5-Sep-18 20:38 
AnswerRe: Missing images Pin
saineshwar bageri5-Sep-18 22:44
membersaineshwar bageri5-Sep-18 22:44 
QuestionThey've already attacked you... Pin
Dewey5-Sep-18 15:47
memberDewey5-Sep-18 15:47 
AnswerRe: They've already attacked you... Pin
saineshwar bageri5-Sep-18 18:10
membersaineshwar bageri5-Sep-18 18: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.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web06-2016 | 2.8.181116.1 | Last Updated 5 Sep 2018
Article Copyright 2018 by saineshwar bageri
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid