Introduction
Lots of ASP.NET MVC developers are great in delivery, writing high performance code and so on. But when it comes to security, there is no planning done. So in this article, we will run through 10 points which will help us to make our MVC code secure.
In case you are new to MVC, I would suggest you start from this YouTube tutorial:
Contents
- Security Misconfiguration (Error Handling Must Setup Custom Error Page)
- Cross-Site Request Forgery (CSRF)
- Cross-Site Scripting (XSS) attacks
- Malicious File Upload.
- Version Discloser
- SQL Injection Attack
- Sensitive Data Exposure
- Audit Trail
- Broken Authentication and Session Management
- Unvalidated Redirects and Forwards
In this kind of attack, the attacker intercepts form data which is submitted by end User and changes values and sends the modified data to the server.
So for such kind of scenarios, developers do put proper validations in place but when these validations display error, lot of information of the server is revealed.
So let us demonstrate the same practically.
Example
For showing demo, I have created an Employee
form which takes basic Employee
details.
Fig 1. Add Employee form View in the browser along with Markup.
EmployeeDetailModel View
Fig 2. EmployeeDetail Model.
So is 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.
In case you are new to validation using Data annotation, please do watch this Youtube video which explains how to use data annotations for validation purposes.
The below snapshot shows that address field is validating. It asks for only 50 characters.
Fig 3. After adding validation to Model, now we are validating form. You can see Address input field which is only accepting 50 characters. It is not accepting more than 50 characters, it is showing Error and message.
Intercepting Add Employee 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 which 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 4. Intercepting Add Employee 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 Address which is only taking 50. I have added more than 50 characters and then submitted to the server.
Intercepted Address Field of Add Employee Form
Fig 5. Intercepted Address field in burp suite and submitted to the server.
Below is the snapshot which shows the request that is submitted to the server which has more than 50 characters.
Debugging Mode of Employee Form
After submitting more than 50 characters to the Address
fields, the server throws an exception. Because in the database, the data type of Address
field isvarchar(50)
and the data is more than 50 so an exception is but obvious.
Fig 6. Intercepted Address field in burp suite and submitted to the server.
Fig 7. Interception of Address field caused an error.
Problem with Displaying Error Occurred to Users
Now exception which occurs is directly displayed to attacker which leaks a lot 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.
Fig 8. Displaying Error directly to Users.
Solution
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.
- Set Custom Error page from Web.config file.
Solution 1
Create a custom Error handling attribute using HandleErrorAttribute
or using IExceptionFilterFilter
:
Showing example using HandleErrorAttribute
.
using System;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
usingSystem.Web.Mvc;
namespaceMvcSecurity.Filters
{
publicclassCustomErrorHandler : HandleErrorAttribute
{
publicoverridevoidOnException(ExceptionContextfilterContext)
{
Exception e = filterContext.Exception;
filterContext.ExceptionHandled = true;
var result = newViewResult()
{
ViewName = "Error"
}; ;
result.ViewBag.Error =
"Error Occur While Processing Your Request Please Check After Some Time";
filterContext.Result = result;
}
}
After creating custom Error
attribute, we need to apply this globally for the entire application. For doing this, we need to call this attribute in FilterConfig
class which is in App_Start folder as shown below:
usingMvcSecurity.Filters;
usingSystem.Web;
usingSystem.Web.Mvc;
namespaceMvcSecurity
{
publicclassFilterConfig
{
publicstaticvoidRegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(newCustomErrorHandler());
}
}
}
Whenever error occurs, the CustomErrorHandler
attribute will get called and it will redirect to Error.cshtml page. And any message you want to pass then, you can pass through @ViewBag.Errorfrom CustomErrorHandler
attribute.
HTML Error Page Code Snippet
@{
Layout = null;
}
<!DOCTYPEhtml>
<html>
<head>
<metaname="viewport"content="width=device-width"/>
<title>Error</title>
</head>
<body>
<hgroup>Error.
<h3>@ViewBag.Error</h3>
<h3> </h3>
</hgroup>
</body> </html>
Error Page View
Fig 9. Displaying Custom Error page if any error occurs in Application.
Solution 2
- Setting Custom Error page from Web.config file.
- If you do not want to write attribute, then you can set Custom Error page in Web.config file. Before doing that, just create a simple Error page in HTML for displaying if any error occurs.
In Web.config file, there is system.web tag. Inside that, add Custom error tag as shown below:
Fig 10. Web.config File View while setting Custom Error page.
HTML Error Page Code Snippet
<!DOCTYPEhtml>
<html>
<head>
<metaname="viewport"content="width=device-width"/>
<title>Error</title>
</head>
<body>
<hgroup>Error.
<h2>An Error Occurred While Processing Your request...........</h2>
<h2> </h2>
</hgroup>
</body>
</html>
Html Error Page View
Fig 11. Displaying Custom HTML Error page if any error occurs in Application.
Wow, we have just started securing our web application. Let’s see the second point.
A CSRF vulnerability allows an attacker to force a validated and logged in user to perform actions without their consent or unknowingly.
Take this simple example:
- User logs in to the bank server.
- Bank authorizes and a secure session is established between 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 transfer money from your account to the attackers 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 @Html.AntiForgeryToken()
helper in your form inside form tag. And on the Action
method which handles your post ([HttpPost])Request
we need to put [ValidateAntiForgeryToken]
attribute which will check if the token is valid.
Adding [AntiForgeryToken] Helper to View
Fig 13. Adding AntiForgeryToken on View.
Adding [ValidateAntiForgeryToken]
attribute to HttpPost[HttpPost]
method.
Fig 14. Adding ValidateAntiForgeryTokenon [HttpPost] Method (Index).
WorkingofAntiForgeryToken
When we add AntiForgeryToken
helper on View, 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 are 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.
RequestVerificationTokenon View Snapshot
Fig 15. RequestVerificationToken generated the hidden field.
RequestVerificationToken Cookie Snapshot
Fig 16. RequestVerificationToken generated a cookie.
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.
Fig 17. Cross Site Scripting (XSS).
In this attack, the attacker visits a website and tries to execute a malicious script in form comment box. Now if website has not checked for malicious code, then the code can get executed on the server causing damage.
Let's try to understand the same using an example. Below is simple Employee
form which we are trying to save data. Now in the text box, I am trying to execute some malicious code using JavaScript using the SCRIPT
tag. But if we try to submit the same, MVC throws an error that something bad is happening.
In short, by default, ASP.NET prevents Cross Site Script attack.
Understanding the Error Displayed
A potentially dangerous Request.Form
value was detected from the client (worktype="<script>alert('hi');")
.
This error occurs because MVC is validating data which is entered by user and if user tried to execute such a script, it does not allow and that’s a good news.
Fig 18. Submitting Malicious scripts in Input fields which lead to Error.
But now what if we want to put SCRIPT
tag. For example, programming sites like CodeProject have a genuine need that end user should submit code and script snippets. In those scenarios, we would like the end user to post code through the UI.
So let us understand how to do the same but at the same time not compromise on security.
So we have four things via which we can allow scripts to be posted.
Solution
[ValidateInput(false)]
[AllowHtml]
[RegularExpressionAttribute]
AntiXSS
Library
Solution 1
ValidateInput
[ValidateInput]
is an attribute which can be applied on Controller or Action Method on which we want the script to go through.
If we want Markup to be allowed, then we need to set enableValidation
properties to False([ValidateInput(false)])
which will not validate input if we set to true
then [ValidateInput(true)])
. It will validate input. In the same way, if you apply it on Controller, then it applies to entire action methods inside the controller and if you apply it on Action Method then it will only be specific to that action method.
But ValidateInput attribute will apply to all properties of Model (EmployeeDetails).
Snapshot of applying ValidateInputAttribute
on HttpPostMethod
.
Fig 19. Applying ValidateInput Attribute on HttpPost Method.
Snapshot after applying ValidateInputAttribute.
Fig 20. After adding ValidateInput Attribute on HttpPost Method, it allows submitting script.
Solution 2
AllowHtml
[AllowHtml]
attributes that is applied to Model
properties such that it will not validate that particular Model
property on which AllowHtml
attribute is added. This allows submitting HTML for avoiding Cross Site Scripting attack.
In the below snapshot, I have applied AllowHtml
attribute on Address
property of EmployeeDetails
model.
Fig 21. Applying [AllowHtml] Attribute on Required Model Property.
After applying AllowHtml
attribute on that Address
property, now Address
property will not validate and allow HTML to be submitted in this field.
Fig 22. After adding [AllowHtml] Attribute on Address Model Property, it allows submitting script.
Solution 3
Regular Expression
The third solution to XSS attack is validating all your Fields with Regular Expression such that only valid data can move in.
Use Regular Expression to validate input to protect from XSS attack below is snapshot.
Fig 23. Applying Regular Expression to Model Property.
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})?))$
Solution 4
AntiXSS Library
The fourth solution to XSS attack is by using MicrosoftAntiXSSLibrary
which will help to protect your application.
Now let’s start with install MicrosoftAntiXSSLibrary
from NuGet. Just right click on Project, then select ManageNuGetPackages.
Fig 24. Choosing Manage NuGet Package for adding Package.
After selecting, a new dialog will pop up with name ManageNuGetPackages
in this dialog search AntiXSSLibrary
in the search box, then choose the first option which is AntiXSS
click on Install button.
Fig 25. Adding Microsoft AntiXSS Library to Project.
Reference added after installing
Fig 26. After adding MicrosoftAntiXSS Library to Project.
After installing, we are going to have a look at how to use AntiXSSLibrary
.
Sanitizer Class
Fig 27. Sanitizer Class which we are going to use for sanitizes inputs.
Below snapshot shows how to use Sanitizer Class Method
Sanitizer
is a static
class we can access anywhere. We just need to provide input which field we need to validate to Sanitizer
class method (GetSafeHtmlFragment
). It will check and return you Sanitize string
.
Fig 28. Here, we are showing how to sanitize inputs usingSanitizer Class. For that, I have taken address field as input to sanitize.
We can use this method to filter malicious script while saving in the database and displaying in the browser.
Tip: Use [ValidateInput(false)]
or [AllowHtml]
before using this AntiXSS
library, else it will throw error “A potentially dangerous Request.Form
”.
Till now, we have learned how to protect all your input fields from attack but still, we are missing one main field, it is File upload control. We need to protect from taking invalid input most attackers try to 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 at 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.
Fig 29. This image shows how people try to upload files, some try valid files and some invalid files.
Solution
- First thing we need to do is validate file uploads.
- Allow access only to files extension which are required.
- Check the file header.
First, the thing I am going to add a file upload control on View.
Adding File Upload Control
Fig 30. Adding File upload control on Add employee View.
We have added File upload control on View. Next, we are going to validate file on Submit.
Validating file uploads on Index HttpPost Method
In this method first, we are going to validate Content-Length
of the file if it is zero [upload.ContentLength == 0
], then user has not uploaded file.
If Content-Length
is greater than zero, then it has file [upload.ContentLength> 0]
and we are going to read Filename of File along with Content Type and File Bytes.
[HttpPost]
[ValidateAntiForgeryToken]
publicActionResult Index(EmployeeDetailEmployeeDetail)
{
if (ModelState.IsValid)
{
HttpPostedFileBase upload = Request.Files["upload"];
if (upload.ContentLength == 0)
{
ModelState.AddModelError("File", "Please Upload your file");
}
elseif (upload.ContentLength> 0)
{
stringfileName = upload.FileName;
stringfileContentType = upload.ContentType;
byte[]tempFileBytes= newbyte[upload.ContentLength];
var data = upload.InputStream.Read(tempFileBytes, 0, Convert.ToInt32(upload.ContentLength));
var types = MvcSecurity.Filters.FileUploadCheck.FileType.Image;
var result = FileUploadCheck.isValidFile
(tempFileBytes, types, fileContentType);
if (result == true)
{
intFileLength = 1024 * 1024 * 2;
if (upload.ContentLength>FileLength)
{
ModelState.AddModelError("File", "Maximum allowed size is: " + FileLength + " MB");
}
else
{
stringdemoAddress = Sanitizer.GetSafeHtmlFragment(EmployeeDetail.Address);
dbcon.EmployeeDetails.Add(EmployeeDetail);
dbcon.SaveChanges();
return View();
}
}
}
}
return View(EmployeeDetail);
}
Till now, it was basic validation we have done. Let’s Validate file which is uploaded. For doing that, I have written a static
class with name FileUploadCheckin
this class. There are various methods for validating different file type. For now, I am going to show you how to validate Images files and only allow image files only.
FileUploadCheck Class
Fig 31. View of FileUploadCheck Class which is custom created for validating File uploads.
In the above snapshot, there is ImageFileExtension enum
which contains Image
formats and File
types.
privateenumImageFileExtension
{
none = 0,
jpg = 1,
jpeg = 2,
bmp = 3,
gif = 4,
png = 5
}
publicenumFileType
{
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 isValidFile
method which take bytes, File type, FileContentType
as input.
publicstaticboolisValidFile(byte[] bytFile, FileTypeflType, StringFileContentType)
{
boolisvalid = false;
if (flType == FileType.Image)
{
isvalid = isValidImageFile(bytFile, FileContentType);
}
elseif (flType == FileType.Video)
{
isvalid = isValidVideoFile(bytFile, FileContentType);
}
elseif (flType == FileType.PDF)
{
isvalid = isValidPDFFile(bytFile, FileContentType);
}
returnisvalid;
}
After calling isValidFilemethod
, 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 the 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 of 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
].
publicstaticboolisValidImageFile(byte[] bytFile, StringFileContentType)
{
boolisvalid = false;
byte[] chkBytejpg = { 255, 216, 255, 224 };
byte[] chkBytebmp = { 66, 77 };
byte[] chkBytegif = { 71, 73, 70, 56 };
byte[] chkBytepng = { 137, 80, 78, 71 };
ImageFileExtensionimgfileExtn = ImageFileExtension.none;
if (FileContentType.Contains("jpg") | FileContentType.Contains("jpeg"))
{
imgfileExtn = ImageFileExtension.jpg;
}
elseif (FileContentType.Contains("png"))
{
imgfileExtn = ImageFileExtension.png;
}
elseif (FileContentType.Contains("bmp"))
{
imgfileExtn = ImageFileExtension.bmp;
}
elseif (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;
}
}
}
}
}
returnisvalid;
}
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 a valid file then true
, else it will return false
.
stringfileName = upload.FileName;
stringfileContentType = upload.ContentType;
byte[] tempFileBytes = newbyte[upload.ContentLength];
var data = upload.InputStream.Read(tempFileBytes, 0, Convert.ToInt32(upload.ContentLength));
var types = MvcSecurity.Filters.FileUploadCheck.FileType.Image;
var result = FileUploadCheck.isValidFile
(tempFileBytes, types, fileContentType);
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 the below form and choose a valid file.
Fig 32. Add Employee form View after adding file upload.
Choosing a valid .jpg file and check how it will validate in details
Choosing a .jpg image from disk.
Fig 33. Choosing File for upload.
Snapshot of Employee Form after Choosing a File.
In this part, we have chosen a file.
Fig 34. After Choosing File for upload.
Snapshot of debugging Index Post Action Method.
In this part, we have posted an Employee
form with a file. Here, we can see how it is validating basic validations.
Fig 35. After Submitting Form for saving data, it shows real time value of file which we have uploaded.
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.
Fig 36. After submitting Form for saving data, it shows real time value of file which we have uploaded.
Snapshot of FileUploadCheck Class while isVaildFile Method gets called.
In this part, after calling isValidFile
method, it is going to call another method according to its FileContentType
.
Fig 37. Calling method inside FileUploadCheck Class according to File Type.
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.
Fig 38. Check header bytes of the image which is uploaded against the bytes which we have.
Version information can be used by an attacker 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-AspNet-Version, X-AspNetMvc-Version, X-Powered-By]
.
The server shows information of which web server is begin used.
X-AspNet-Version
shows information of which specific ASP.NET version used. X-AspNetMvc-Version
shows information of which ASP.NET MVC version used. X-Powered-By
shows information of which framework your website is running on.
Fig 39. Response header disclosing Version Information.
Solution
- For removing
X-AspNetMvc-Version
header
To remove response X-AspNetMvc-Version
which shows information of which ASP.NET MVC version used, we have built in property in MVC.
Just set [MvcHandler.DisableMvcResponseHeader = true;]
in Global.asax. Application start event [Application_Start()]
this will remove header. It won’t be displayed any more.
Fig 40. Setting property in Global.asax to removeX-AspNetMvc-Version from header.
Fig 41. Response after removing X-AspNetMvc-Version from header.
- For
removingX-AspNet-Version
and Server
header
To remove response of Server header which shows information of which web server is begin used and along with that, X-AspNet-Version
header shows information of which specific ASP.NET Version Used.
Just add an event in [Application_PreSendRequestHeaders()]
in global.asax and then to remove header, we need to set the property as below:
protectedvoidApplication_PreSendRequestHeaders()
{
Response.Headers.Remove("Server");
Response.Headers.Remove("X-AspNet-Version");
}
Fig 42. Adding Application_PreSendRequestHeaders event in global.asax and then removing Response headers.
Fig 43. Response after removing X-AspNet-Version and Server from header.
- For
removingX-Powered-Byheader
To remove response of X-Powered-Byheader
which shows information of which framework your website is running on.
Just add this tag under System.webServer
in Web.config file will remove [X-Powered-By]
header.
<httpprotocol>
<customheaders>
</customheaders>
</httpprotocol>
Fig 44. Adding custom Header tag in Web.configfor removing Response headers.
Fig 45. Response after removing X-Powered-By from header.
SQL Injection attack is one of the most dangerous attacks. It is ranked 1 in top 10 vulnerabilities by OWASP2013 [Open Web Application Security Project]. 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, the attacker always tries to enter malicious SQL statement which will get executed in the database and return unwanted data to the attacker.
Fig 46. 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 Employee
data based on EmployeeID
as displayed in the below snapshot.
Fig 47. Employee 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/EmployeeList/index?Id=2], the attacker tries SQL Injection attack as shown below:
Fig 48. Employee 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 of how attacker passed malicious SQL statement which gets executed in the database.
Fig 49. Debug mode View of index Action Method.
SQL Profiler View of SQL Statement
Fig 50. SQL Profiler View.
Solution
- Validate inputs
- Use of low-privileged database logins
- Use Parameterized queries
- Use ORM (e.g., Dapper, Entity framework)
- Use Stored Procedures
1. Validate Inputs
Always validate input on both side Client side and Server side such that no special characters are entered in inputs which allows an attacker to get into the system.
In MVC, we use Data Annotations for Validation.
Data Annotations attributes are a simple rule that are applied on Model to validate Model data.
Client side validating inputs
Fig 51. Client side validating inputs.
Server side validating inputs
Model state is false
- this indicates that Model is not valid.
Fig 52. Server side validating inputs.
2. Give Least-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 activity on it. We must create a new user account with least-privileged and give only that privilege that user must access.
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.
Step to Add New User and Provide Privilege
In this part, I have shown a small sample of how to create User provided specific permission to table.
- Creating New User
- View after creating User
- Selecting object that user can access (table)
- Choosing specific table to provide permission
Fig 53. Creating New Users and providing rights to Users.
After choosing table, we are providing permission to table as shown below, you can see we have only given permission to ‘Insert
’, ‘Select
’, ‘Update
’ for this user.
Fig 54. Giving permission for Insert, Select, Update.
With least-privileged, it will help us to safeguard database from attackers.
3. 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 [@EmpID]
and according to parameter, it is going to get records from database after passing parameter, we have used CommandType
[CommandType.StoredProcedure]
which indicates we are using stored procedure.
Note: Always use parameter with stored procedure if you do not use it, still you are vulnerable to SQL injection attack.
Fig 55. Using Stored Procedure for displaying Employees details.
After Using Stored Procedure, let's Request GetEmployee View
Employee View which displays record.
Fig 56. Employees details View after using stored procedure.
Profiler View after using Stored Procedure
Displaying Trace of stored procedure which we have used.
Fig 57. Trace of stored procedure executed.
After Using Stored Procedure Let’s try SQL injection Attack Again
Fig 58. Trying SQL injection Attack after using stored procedure.
Displaying SQL injection attack execution in Debug Mode after using Stored Procedure
If you have a look at parameter id [?Id=2 or 1=1] which contains MaliciousSQL script after passing it to stored procedure, it shows an error which indicates that it not get executed because parameter which we are passing is an integer which only take numeric values as input if we pass MaliciousSQL script [?Id=2 or 1=1] it throws an error .
Fig 59. Stored procedure Used.
Fig 60. Trying SQL injection attack after using stored procedure.
Profiler View after using Stored Procedure
Fig 61. Trace of stored procedure Executed.
Output
Fig 62. Displaying error after attacking by an attacker.
Meanwhile, you might think that if it was varchar datatype
, then we had successfully attacked. Let's see a demo of it too.
Displaying SQL injection attack execution in Debug Mode after using Stored Procedure with different parameter @name
This is the second demo in which we have passed a different parameter to stored procedure to see is it real protecting against SQL injection attack.
Fig 63. Stored procedure Used.
Fig 64. Trying SQL injection Attack after using stored procedure.
Fig 65. Trace of stored procedure Executed.
Output
Fig 66. After using stored procedure, if we try SQL injection attack, then it does not get executed and no result is shown.
Use Parameterized Queries
Using parameterized queries is another solution to prevent against SQL injection attack. It is similar to stored procedure. Instead of concatenating string
, you need to add parameters to the SQLQuery and pass parameter value using the SqlCommand
.
Fig 68. Parameterized Query also prevents against SQL injection attack.
Profiler View of parameterized Query
Fig 69. Trace of Parameterized query Executed.
Output
Fig 70. After using Parameterized query, if we try SQL injection attack, then it does not get executed and no result is shown.
Use 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 71. Passing parameter after using 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 getting records.
Fig 72. Controller Execution.
Profiler View of Linq Query
Entity Framework internally uses Parameterized query it's true here is a snapshot.
Fig 73. Trace of the query generated by Entity Framework.
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 [?name=Saineshwar or 1=1]
.
Fig 74. TryingSQL injection Attack after using 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 malicious script along with normal parameter.
Fig 75. No output after try 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 76. Trace of the query generated by Entity Framework after trying SQL injection attack.
All websites and applications always have database in which entire data is being stored. This means while we store Users personal information (which may contains Password, PAN number, Passport details, Credit Card Numbers) in this, we mostly encrypt only password right other data are stored in clear text which can lead to Sensitive Data Exposure whenever an attacker attacks, he gets access to database if he finds the table where all this personal and financial details stored steal that information.
Fig 77. Sensitive Data Exposure.
A simple demo of how Sensitive data is been sniffed.
Snapshot of Login Page
Simple code snippet of Login page which comes default if you are choosing Internet Template while creating a project.
Fig 78. Login page Markup and Snapshot.
After having a look at how Login page Mark and View looks now, next we are going to enter credentials and login.
Entering Credentials for Login
In Login page, we are going to enter Username and Password to login to the application.
Fig 79. Entering Credentials.
Intercepting Login Page by Attacker to Steal Credentials
When User enter 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.
Below snapshot shows how your values can be intercepted by an attacker.
Fig 80. Intercepting Login page showing data in the Clear form.
Solution
- Always send sensitive data to server in 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
Always Send Sensitive Data to Server in Encrypted format using Strong Hashing with seed
In this demo, we are going use an MD5 algorithm with seed to encrypted data on client side and send to a server such that it cannot be stolen by the attacker.
In the below snapshot, user enters credentials.
Fig 81. Entering Credentials after Encrypting data.
After entering User credentials when the user clicks on login button, this time, the user entered password that gets encrypted along with seed on the client side using MD5 and then it is sent to the server. In this process, if an attacker tries to sniff network, then encrypted hash is only seen and cannot be decrypted.
In details, you can see the below snapshot where the attacker has sniff network.
Blue line in snapshot indicates Username.
The red line in snapshot indicates Encrypted Password along with the seed.
The green line in snapshot indicates seed value which is generated from the server.
Fig 82. Intercepting login page after Encryption of data.
Till now, we have seen a snapshot of how to do this. Let's take a look at a code snippet.
Login Model
Fig 83. LoginModel view.
Below is a code snippet of [HttpGet]
Login ActionMethod
.
In this method, we are generating random hash [Seed]
and then we are going to assign it to LoginModel
and then pass it to Login View.
Fig 84. Generating and Assigning Random Hash to [hdrandomSeed] and then sending Model to View.
After assigning value [hdrandomSeed]
to model in Login ActionMethod
, now we are going to use it on Login view as a hidden field.
Fig 85. Using the [hdrandomSeed] property as ahidden field on View.
Now after adding a hidden field on the page, now let's see how to Encrypt data using MD5 JavaScript along with the seed.
For doing this, we are going to use jquery library 1.8 and md5.js library when user enters Credentials and clicks on Log In button, then we take password entered by User [var password1 = $('#Password');]
and generate Hash [calcMD5(password).toUpperCase()]
of it first then along with seed, we again generate Hash [var hash = calcMD5(seed + calcMD5(password).toUpperCase());]
which is unique and then it is sent to server.
See below snapshot for details
Fig 86. Code Snippet of Client Side Encryption of MD5 along with Seed for generating Hash.
Debugging View of Login Page
In the variable session, you can see real time values which are generated when user clicks on login button.
Fig 87. Debug mode of Client-side Encryption.
After client side encryption, now let's see attacker is able to intercept and see the password in clear text.
Intercepting Login page
In the below snapshot, you can see password which is entered by the user is in encrypted form and it cannot be understood by an attacker because it is a combination hash of Seed + Password which is entered by User.
Fig 88. Interception of Login page.
After interception, next we are going to see how values are posted to Login Action method.
Login Action Method after posting Values from Login View
Here, we can clearly see password is in encrypted form.
Fig 89. Login Action Method after posting Values from Login View.
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 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 a combination of database password and seed. We compare it with password which is posted.
Fig 90. Code snippet of Login Action Method with pining real time values.
Finally, sensitive data entered by User is Secure.
2. 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).
Fig 91. SSL (Secure Sockets Layer).
SSL is mainly applied on the Login page and payment gateways if you want to apply on the entire application, then also you can apply.
If you want to know in detail how to enable SSL on IIS Server, there is a good article from Scott Guthrie Sir's blog.
3. Do not store Sensitive data in Database in a clear form
Always try not to store Credit Card, Debit Card and financial details and other Sensitive details in the database in Clear form. Always Use Strong Hashing techniques to encrypt data and then store in the database. If an attacker gets direct access to the database, then all data in clear form can be breached.
Below is a list of algorithms which can be used according to need.
Hash Algorithm
If someone wants just Hash, then they can use Hash Algorithm, we mostly use Hash function for Encrypting Password.
SymmetricAlgorithm
If someone wants just one key for encryption and decryption, then they can use SymmetricAlgorithm
.
AsymmetricAlgorithm
If someone wants just one key for encryption (Public key) and another keydecryption (Private key), then they can use AsymmetricAlgorithm
. E.g., we can use this when we are sharing Web Services and WebAPI with clients as the user.
HashAlgorithm
- MD5
- SHA256
- SHA384
- SHA512
Example:
Method to generate MD5 Hash
privatestring Generate_MD5_Hash(stringdata_To_Encrypted)
{
using (MD5encryptor = MD5.Create())
{
MD5md5 = System.Security.Cryptography.MD5.Create();
byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(data_To_Encrypted);
byte[] hash = md5.ComputeHash(inputBytes);
StringBuildersb = newStringBuilder();
for (int i = 0; i < hash.length; i="">
Pass text to generate hash
string Hash = Generate_MD5_Hash("Hello");
Output
SymmetricAlgorithm
- Aes
- DES
- RC2
- Rijndael
- TripleDES
Example
Method of AES for Encryption
privatestringEncrypt_AES(stringclearText)
{
stringEncryptionKey = "##SAI##1990##";
byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);
byte[] array = Encoding.ASCII.GetBytes("##100SAINESHWAR99##");
using (Aesencryptor = Aes.Create())
{
Rfc2898DeriveBytespdb = newRfc2898DeriveBytes(EncryptionKey, array);
encryptor.Key = pdb.GetBytes(32);
encryptor.IV = pdb.GetBytes(16);
using (MemoryStreamms = newMemoryStream())
{
using (CryptoStreamcs =
newCryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(clearBytes, 0, clearBytes.Length);
cs.Close();
}
clearText = Convert.ToBase64String(ms.ToArray());
}
}
returnclearText;
}
Method of AES for Decryption
privatestringDecrypt_AES(stringcipherText)
{
stringEncryptionKey = "##SAI##1990##";
byte[] cipherBytes = Convert.FromBase64String(cipherText);
byte[] array = Encoding.ASCII.GetBytes("##100SAINESHWAR99##");
using (Aesencryptor = Aes.Create())
{
Rfc2898DeriveBytespdb = newRfc2898DeriveBytes(EncryptionKey, array);
encryptor.Key = pdb.GetBytes(32);
encryptor.IV = pdb.GetBytes(16);
using (MemoryStreamms = newMemoryStream())
{
using (CryptoStreamcs =
newCryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(cipherBytes, 0, cipherBytes.Length);
cs.Close();
}
cipherText = Encoding.Unicode.GetString(ms.ToArray());
}
}
returncipherText;
}
Pass Text to Encrypt Value
stringDataEncrypt = Encrypt_AES("Hello");
Pass Text to Decrypt Value
stringDataDecrypt = Decrypt_AES(DataEncrypt);
Output
AsymmetricAlgorithm
- DSA
- ECDiffieHellman
- ECDsa
- RSA
Example
Method of RSA for Encryption
publicbyte[] Encrypt(stringpublicKeyXML, stringdataToDycript)
{
RSACryptoServiceProviderrsa = newRSACryptoServiceProvider();
rsa.FromXmlString(publicKeyXML);
returnrsa.Encrypt(ASCIIEncoding.ASCII.GetBytes(dataToDycript), true);
}
Method of RSA for Decryption
publicstring Decrypt(stringpublicPrivateKeyXML, byte[] encryptedData)
{
RSACryptoServiceProviderrsa = newRSACryptoServiceProvider();
rsa.FromXmlString(publicPrivateKeyXML);
returnASCIIEncoding.ASCII.GetString(rsa.Decrypt(encryptedData, true));
}
Output
Audit Trail in the IT World is used to keep track of User activity on a Webapplication which he using. It is where it is important in detecting security problems, performance problems, and ApplicationsLevel
Error problems. It also helps us to easily track where the problem actually is and resolve it.
Fig 96. Audit.
Solution
Keep Audit Trail of all User activity on Web Application and always Monitor it.
For maintaining Audit Trail, first we are going to create a table in the database for storing Audit data with table name [AuditTB]
. After that, we are going to create an ActionFilterAttribute
with Name [UserAuditFilter]
inside this on Action executing, we are going to write code for inserting data in the database of the users who are accessing our application.
AuditTB Table View
In this table, we have taken common fields which we required to recognize User and his Activity.
Fig 97. AuditTB Table View.
After displaying table view, now let’s have a look at Model which is created according to Table.
AuditTB Model
publicpartialclassAuditTB
{
publicintUsersAuditID { get; set; }
publicintUserID { get; set; }
publicstringSessionID { get; set; }
publicstringIPAddress { get; set; }
publicstringPageAccessed { get; set; }
publicNullable<system>LoggedInAt { get; set; }
publicNullable<system.datetime>LoggedOutAt { get; set; }
publicstringLoginStatus { get; set; }
publicstringControllerName { get; set; }
publicstringActionName { get; set; }
}
After having a look at Model
which is generated now, let’s create an ActionFilter
with UserAuditFilter
.
UserAuditFilterActionfilter Code Snippet
UserAuditFilter
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 or not user is Logged 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 ORM entity Framework.
publicclassUserAuditFilter : ActionFilterAttribute
{
publicoverridevoidOnActionExecuting(ActionExecutingContextfilterContext)
{
AllSampleCodeEntitiesappcontext = newAllSampleCodeEntities();
AuditTBobjaudit = newAuditTB();
stringactionName = filterContext.ActionDescriptor.ActionName;
stringcontrollerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var request = filterContext.HttpContext.Request;
if (HttpContext.Current.Session["UserID"] == null)
{
objaudit.UserID = 0;
}
else
{
objaudit.UserID = Convert.ToInt32(HttpContext.Current.Session["UserID"]);
}
objaudit.UsersAuditID = 0;
objaudit.SessionID = HttpContext.Current.Session.SessionID;
objaudit.IPAddress =
request.ServerVariables["HTTP_X_FORWARDED_FOR"] ?? request.UserHostAddress;
objaudit.PageAccessed = request.RawUrl;
objaudit.LoggedInAt = DateTime.Now;
if (actionName == "LogOff")
{
objaudit.LoggedOutAt = DateTime.Now;
}
objaudit.LoginStatus = "A";
objaudit.ControllerName = controllerName;
objaudit.ActionName = actionName;
appcontext.AuditTBs.Add(objaudit);
appcontext.SaveChanges();
base.OnActionExecuting(filterContext);
}
}
Registering UserAuditFilter in global action filter
Global action filters are mainly used for error handling and logging.
If you have a condition in which you want to apply Action Filter on all action method in the project, then you can use global action filters. Here, we need global action filters because we want to track every request of the user for Audit trail, hence we have used.
Fig 98. Adding UserAuditFilter to Global filter Collection.
Output
The data which got inserted while user requested pages and did some activity in Audit
table.
Fig 99. Audit table View after Insertion of data.
If Authentication and Session management are not properly implemented in a web application which may allow an attacker to steal a password, session tokens, cookies and such issue may also allow an attacker to get access to the entire application and breach all user credentials.
Ways in which an attacker can steal data:
- Not Secure connection (Not Using SSL)
- Predictable login credentials
- Not storing credentials Encrypted form
- Improper Application logout
Attacks Possible
1. Session Fixation
Before finding a solution how to prevent this attack, let's have a look at a small demo of how Session Fixation attack occurs.
Whenever a user sends first request to server, the login page is loaded, then User enters valid Login credits to login web application. After successful login, we assign some values in session to recognize Unique User, meanwhile a [“ASP.NET_SessionId”]
cookie is added to the browser for recognizing specific user who has sent a request and [“ASP.NET_SessionId”]
cookie value will be always sent to the server on every request till you are not logged out from the application and on logout, we basically write code for removing session values which are created but we do not remove [“ASP.NET_SessionId”]
cookie which is created on Login. This value helps an attacker to perform Session Fixation attack.
Fig 100. Session Fixation.
Demo of Session Fixation
When we access login page, we do not have any [“ASP.NET_SessionId”]
cookie in the browser as we can see in cookie manager.
After User Enter Valid Credentials
After entering Valid Login Credentials [“ASP.NET_SessionId”]
, Cookie is added to the browser.
Note: When any data is saved into the Session [“ASP.NET_SessionId”]
cookie is created and added to the user browser.
After Logging out from Application Cookie still exists in browser
After Logout from application, still [“ASP.NET_SessionId”]
Cookie exists.
Note: Pre cookie and post cookie are the same which can lead to the session fixation.
Let’s do Some Session Fixation
The [“ASP.NET_SessionId”]
Cookie which is not removed after logout that helps attacker for doing session fixation will open a browser (Chrome). In that, I will enter URL [http://localhost:3837/] of application in which we are going to do session fixation.
After entering URL in the browser, now let's check if we have any [“ASP.NET_SessionId”]
Cookie created here. Oh, we don’t have any Cookie.
Cookie which is already Created in Firefox browser
In this view, I have shown [“ASP.NET_SessionId”]
cookie which is created in Firefox browser when the user logged in.
Note: For managing Cookie, I have installed Cookie Manager+ addon in Chrome browser.
After having a view on [“ASP.NET_SessionId”]
cookie in Firefox now, let's create [“ASP.NET_SessionId”]
cookie in Chrome browser similar to Cookie which is in Firefox browser with same [“ASP.NET_SessionId”]
Cookie Name and Values.
Created New [“ASP.NET_SessionId”] 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 [“ASP.NET_SessionId”]
Cookie and assign similar values of SessionID
to this Cookie.
Note: For adding Cookie, I have installed Edit this Cookie addon in Chrome browser.
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.
Solution
- Remove
[“ASP.NET_SessionId”]
after logout - Securing Cookies
- Use SSL for Securing Cookies and Session
Remove [“ASP.NET_SessionId”] after logout
On logout, we are removing Session values. Along with that, we are removing [“ASP.NET_SessionId”]
Cookie from browser.
publicActionResultLogOff()
{
Session.Abandon();
Session.Clear();
Session.RemoveAll();
if (Request.Cookies["ASP.NET_SessionId"] != null)
{
Response.Cookies["ASP.NET_SessionId"].Value = string.Empty;
Response.Cookies["ASP.NET_SessionId"].Expires = DateTime.Now.AddMonths(-10);
}
if (Request.Cookies["AuthenticationToken"] != null)
{
Response.Cookies["AuthenticationToken"].Value = string.Empty;
Response.Cookies["AuthenticationToken"].Expires = DateTime.Now.AddMonths(-10);
}
returnRedirectToAction("Login", "Account");
}
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.
Code Snippet
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
publicActionResult Login(LoginModel model, stringreturnUrl)
{
if (ModelState.IsValid)
{
varstoredpassword = ReturnPassword(model.UserName);
if (ReturnHash(storedpassword, model.hdrandomSeed) == model.Password)
{
Session["Username"] = model.UserName;
Session["UserID"] = 1;
stringguid = Convert.ToString(Guid.NewGuid());
Session["AuthenticationToken"] = guid;
Response.Cookies.Add(newHttpCookie("AuthenticationToken", guid));
returnRedirectToAction("Index", "Dashboard");
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
return View(model);
}
Code Snippet Description
Creating a new Guid.
stringguid = Convert.ToString(Guid.NewGuid());
Saving a new Guid in Session.
Session["AuthenticationToken"] = guid;
Saving a new Guid in Cookie and adding.
Response.Cookies.Add(newHttpCookie("AuthenticationToken", guid));
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 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 whether 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
. In this filter we are inheriting IAuthorizationFilter Interface
and FilterAttribute
class. With this, we are implementing method inside interface [OnAuthorization]
and write the whole logic in this method.
using System;
usingSystem.Web.Mvc;
namespaceMvcSecurity.Filters
{
publicclassAuthenticateUser : FilterAttribute, IAuthorizationFilter
{
publicvoidOnAuthorization(AuthorizationContextfilterContext)
{
stringTempSession =
Convert.ToString(filterContext.HttpContext.Session["AuthenticationToken"]);
stringTempAuthCookie =
Convert.ToString(filterContext.HttpContext.Request.Cookies["AuthenticationToken"].Value);
if (TempSession != null&&TempAuthCookie != null)
{
if (!TempSession.Equals(TempAuthCookie))
{
ViewResult result = newViewResult();
result.ViewName = "Login";
filterContext.Result = result;
}
}
else
{
ViewResult result = newViewResult();
result.ViewName = "Login";
filterContext.Result = result;
}
}
}
}
Code Snippet Description
In this method, first we are going to get session and cookie values.
stringTempSession = Convert.ToString(filterContext.HttpContext.Session["AuthenticationToken"]);
stringTempAuthCookie = Convert.ToString(filterContext.HttpContext.Request.Cookies
["AuthenticationToken"].Value);
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 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 = newViewResult();
result.ViewName = "Login";
filterContext.Result = result;
}
}
else
{
ViewResult result = newViewResult();
result.ViewName = "Login";
filterContext.Result = result;
}
After understanding code snippet, now we are going 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.
After applying Action filter on all the controllers which users access after getting logged into the application.
Now if the attacker knows [“ASP.NET_SessionId”]
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 is stored in Session [Session["AuthenticationToken"]]
on Web Server but attacker can’t know the Session value that is stored on the web server and these values keep changing every time user is logging to application and the old session values which attacker used to do attack will not work in this scenario.
Finally, if we will allow those users access to the application who has valid Session["AuthenticationToken"]
value and Cookies["AuthenticationToken"]
value.
Realtime Values of both Cookies.
Use SSL for Securing Cookies and Session Values
SSL (Secure Sockets Layer) is a Layer which has 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 111. SSL (Secure Sockets Layer).
In all web applications, we do redirect from one page to another page and sometimes, we redirect to another application too but while redirecting, we won't validate URL which we are going to redirect which caused Unvalidated Redirects and Forwards Attack.
This attack is mostly used to phish user to get valuable details (User Credentials) or to install malicious malware to the User computer.
Examples
In the below snapshot, you will see simple MVC application URL along with that, a created malicious URL by an attacker that redirects users to a malicious site that performs phishing and installs malware into user computers.
Fig 112.Crafted URL by an attacker.
Original URL: http://localhost:7426/Account/Login
Crafter URL by Attacker: ?returnUrl=https://www.google.co.in
Attack Scenario
In this attack, user gets email from attacker which contains offer related to Ecommerce shopping. When user clicks on link given below, he is redirected to shopping site [http://demotop.com] but if you take a look at the URL closely, you will see that this URL contains redirect [http://demotop.com/Login/Login?url=http://mailicious.com]. Now, after entering valid Username and Password, the User will be redirected to malicious site [http://mailicious.com] which is similar to [http://demotop.com] shopping site. On the malicious site, it will show Message “Invalid Username and Password”, then again user will enter Username and Password and he will be redirected back to the original shopping site but meanwhile User Credentials are stolen in this attack.
Fig 113. Unvalidated Redirects and ForwardsAttack Scenario.
Solution
- Simply avoid using redirects and forwards.
- If you still want to use redirects and forwards, then validate URL first.
- Use
Url.IsLocalUrl
to prevent redirects and forwards in MVC.
Using Url.IsLocalUrlin MVC
Below is a snapshot of the login page in MVC which contains redirect URL to www.google.com when user enters username and password, then he will be redirected to www.google.com which is invalid. To prevent this in MVC4, we have built in method called as Url.IsLocalUrl
which checks that redirect URL which is crafted is Local or not, if not, then it will not redirect it.
Fig 114. Login page in MVC with Redirect URL.
After understanding how redirect is passed, now let’s check how this URL gets checked and executed.
The below [HttpPost
] Login
method gets called when user enter Credentials and submits the form. Along with that, redirect URL is also gets posted which may contain malicious URL. For showing demo, I have just checked that username and password is not null
. After that, we are calling RedirectToLocalAction
method and to this method, we are going to pass redirect URL (returnUrl
).
Fig 115. Login Post Action Method with returnURL.
After passing URL (returnUrl
) to RedirectToLocalAction
method, then it is going pass URL to IsLocalUrl
method [Url.IsLocalUrl(returnUrl)
] which is going to check whether URL is local or not and return a boolean value. If it is not, then it will redirect to Home page, else will redirect to returnUrl
which is passed.
[True if the URL is local]
[False if URL is not local]
Fig 116. RedirectToLocal Action Method which checks whether returnUrl is Local or Not.
History
- 3rd August, 2016: Initial version