It is extremely necessary to secure your .NET applications before you get hacked. To apply best securities, you have to includes secure coding techniques, anticipate risks and perform security checkups at regular intervals. Fortunately, .NET has lots of security API which you can include in your app to make it more robust. In this tutorial we are going to take a detailed look at the necessary .NET security features which you must apply.
There are 5 common types of security areas to look when developing apps:
Let us now see each of them one by one.
OverPosting is an attack carried on Model Binding technique so that the attacker is able to set a value to a field which you don’t want to happen. Take for example – a Job Portal has a job application page where teachers can apply for teaching jobs. This page calls a Create Action method to create a Teacher entry in the database, on the submission of the job application form.
The application form is:
@model Teacher
<h2>Teacher Application Form</h2>
<form asp-action="Create" method="post">
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" />
</div>
<div class="form-group">
<label asp-for="DOB"></label>
<input asp-for="DOB" class="form-control" />
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
This form has just 2 fields “Name” and “DOB” which the applicant has to fill and submit.
The Teacher.cs class to which this form binds to is:
public class Teacher
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime DOB { get; set; }
public bool? Accepted { get; set; }
}
Note that the class has an extra bool field Accepted which we have not included on the form. The recruiter will fill this field value to true or false, from some other page like from admin panel of the Job Portal, only after interviewing the teachers and depending upon whether the teacher passes the interview or not. That means the Accepted field should remain null whenever a teacher submits his/her job application. Later on this field will be set to either true or false by the recruiter.
The Create action method which does the creation of teacher’s record in the database is:
[HttpPost]
public IActionResult Create(Teacher teacher)
{
context.Add(teacher);
await context.SaveChangesAsync();
return View();
}
It seems the whole code is OK but the attacker can still successfully launch an Overposting attack. The attacker can use a tool like Fiddler, Postman, or write some JavaScript, to submit the form and create a teacher entry in the database that has “Accepted” field set as “true”. The trick here is that the model binder would pick up that “Accepted” form value (which the attacker has set) and use it to create the Teacher entity. The same thing can be done during update records procedure also.
That’s certainly impossible – A cat “Accepted” for a job of a teacher.
In Postman we can perform Overposting by selecting “POST” type request. Then select Body > form-data and here put the teachers fields that you want to insert to the database.
See in the below image, I have added Accepted field with value “true” in the form-data. Now when I click Send button, the entry will be added to the database.
Now check the database to confirm the “Accepted” field is also added with true value. In the below image I have shown this thing.
Now let’s find out how to prevent this problem.
Overposting during Creating new records can be prevented by the use of [Bind] attribute. It can be applied to the class to specify which model property should be included in model binding process. Here we want only the “Name” and “DOB” property to be included in Model binding (and not “Accepted” property). Therefore, we can update the Create action with the [Bind] attribute as shown below:
[HttpPost]
public IActionResult Create([Bind("Name", "DOB")]Teacher teacher)
{
context.Add(teacher);
await context.SaveChangesAsync();
return View();
}
The [Bind] attribute doesn’t work well in edit scenarios for preventing overposting because excluded properties are set to null or a default value instead of being left unchanged. In edit scenarios we read the entity from the database first and then call TryUpdateModel method, passing in an explicit allowed properties list.
The TryUpdateModel method is used during the Update action method below. Note that in this method we have not added the Accepted field and so overposting attack will now be prevented.
[HttpPost]
public IActionResult Update(int teacherId)
{
// fetching teacher record from DB
var teacherToUpdate = await _context.Teachers.FirstOrDefaultAsync(s => s.Id == teacherId);
// updating only Name and DOB of the teacher. The “Accepted” field cannot be updated by the attacker.
if (await TryUpdateModelAsync<Teacher>(
teacherToUpdate,
"",
s => s.Name, s => s.DOB))
{
try
{
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error
ModelState.AddModelError("", "Unable to save changes. Try again later");
}
}
}
We can define a TeacherViewModel.cs which will work in Model Binding. Notice this class does not has Accepted field.
public class TeacherViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime DOB { get; set; }
}
Now in the controller we can use our view model as shown below.
[HttpPost]
public IActionResult Create(TeacherViewModel teacher)
{
// insert view model's data to database
}
We should not save app secrets in appsettings.json file instead should use Secret Manager Tool to store them. This tool stores the secrets on a separate location from the project, in a file called secrets.json. The most common example is storing database password with Secret Manager Tool.
Let us now understand how to use Secret Manager Tool. So first run init command from the directory of the project file (.csproj).
dotnet user-secrets init
The command adds UserSecretsId, which is a GUID element, within a PropertyGroup of the project file.
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<UserSecretsId>ad0f2959-b89d-45e7-bdae-2b36ca748ae7</UserSecretsId>
</PropertyGroup>
The UserSecretsId is unique to the project and now all your app secrets will be associated with this id. Right click on the project in solution explorer and select Edit Project File to see the UserSecretsId added there.
Now run the following command to store the database password.
dotnet user-secrets set "DbPassword" "joker@123"
The database password is stored in secrets.json file. You can open this file by right clicking the project and select Manage User Secrets.
You can edit this json file to add, edit or remove new secrets. There are also separate commands to perform each of the tasks.
Now go to the database connection string stored in appsettings.json file and remove the password Password=joker@123 from it.
{
"ConnectionStrings": {
"Students": "Server=(localdb)\\mssqllocaldb;Database=Student;User Id=yogi;MultipleActiveResultSets=true"
}
}
Next, in the Program.cs, use the SqlConnectionStringBuilder object’s Password property to add it to the connection string as shown below.
var conStrBuilder = new SqlConnectionStringBuilder(builder.Configuration.GetConnectionString("Students"));
conStrBuilder.Password = builder.Configuration["DbPassword"];
var connection = conStrBuilder.ConnectionString;
// setting connection string for Entity Framework Core
builder.Services.AddDbContext<CompanyContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString(connection)));
Other important commands:
In Cross-site request forgery (also known as XSRF or CSRF) an attacker manipulate a user into executing malicious actions without their awareness. Take for example, you are currently logged in to your Net Banking account. Now you receive an email stating that you are a lucky winner of a million $ Ferrari car. Seeing this you click the email link and reaches a malicious website.
In this website there is a small form asking for your name and address only. You think it’s Ok as no credit card or bank account they are asking.
This form’s html code is given below.
<h1>Congratulations! Get your Ferrari<h1>
<form action="https://www.bankofamerica.com/api/account" method="post">
<input type="hidden" name="Transaction" value="withdraw" />
<input type="hidden" name="Amount" value="1000000" />
<input type="submit" value="Collect your car now!" />
... other fields for name, email, address, etc
</form>
See the form action targets bank of America /api/account endpoint where $100000 amount is added for transaction. Once you click on the Collect your car now! button, the transaction will happen.
But how it is possible? The reason is you are currently logged in to your bank’s website. The bank has already authenticated you and their session’s cookie is residing on your browser. When you click on the Collect your car now! button on the attacker’s website, this bank cookie is attached to the api request and the transaction happens. The bank has no way to identity this api request is not coming from their website.
The above example is using HTTP POST version of this attack. The GET version can also be used where the attacker will make you click an image which will make a GET request.
In order to safeguard yourself from CSRF attacks follow these steps:
.Things to remember when building apps in ASP.NET Core:
It is also required to apply [ValidateAntiForgeryToken] filter to your apps most important action methods. The .NET will generate a hidden Verification Token which will be added and verified on each user request to the app and so you are safeguarding them from cross-site request forgery (CSRF) attacks.
In the below code we have applied [ValidateAntiForgeryToken] on the action method and rest .NET will take care.
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Transaction(User user)
{
// some code
}
Cross-Site Scripting (XSS) attacks are caused when malicious scripts (mainly JavaScript) are injected to trusted websites. So when other users load the affected pages, these scripts run, and the attacker is able to steal user cookies, tokens, perform DOM manipulation, or redirect the users to another page.
You can prevent XSS attacks by:
HTML encoding in MVC views (.cshtml) and Razor Pages are done automatically. Example:
@{
var untrustedInput = "<\"abc\">";
}
@untrustedInput
It will output:
<"abc">
The characters <, \, ", \,> are encoded since they are used in XSS attacks.
In MVC views and Razor pages the JavaScript encoding is done by placing the value in a data attribute of a tag and retrieve it in your JavaScript. Example:
@{
var untrustedInput = "<script>alert(1)</script>";
}
<div id="injectedData" data-untrustedinput="@untrustedInput" />
<div id="scriptedWrite" />
<script>
var injectedData = document.getElementById("injectedData");
var untrustedInput = injectedData.dataset.untrustedinput;
document.getElementById("scriptedWrite").innerText += untrustedInput;
</script>
Do not use document.write() on dynamically generated data as it can lead to XSS.
In MVC controllers we can inject HTML, JavaScript and URL encoders object (located on the System.Text.Encodings.Web namespace) on the constructor and then encode the values with the Encode() method.
public class HomeController : Controller
{
HtmlEncoder he;
JavaScriptEncoder je;
UrlEncoder ue;
public HomeController(HtmlEncoder htmlEncoder,
JavaScriptEncoder javascriptEncoder,
UrlEncoder urlEncoder)
{
he = htmlEncoder;
je = javascriptEncoder;
ue = urlEncoder;
}
public IActionResult EncodeExample()
{
var example = "I am <, > a hacker.";
var encodedValueHTML = he.Encode(example);
var encodedValueJS = je.Encode(example);
var encodedValueURL = ue.Encode(example);
}
}
Never rely on validation alone, perform encoding of untrusted data before outputting it.
Open Redirect Attack are meant to steal user’s credentials on secured web apps like their bank account username and password, office credentials, etc. In this attack the hacker will convince the user to click a link to a perfectly OK website. But he adds a returnUrl query string parameter to the url. This parameter targets a malicious site where they will be shown a login form to enter their credentials. You know what will happen next.
Example – An office employee ‘Sara’ logs to her account on her office website every day in the morning to get messages from her colleagues. The office website has an address – https://www.myoffice.com. The hacker will now try to get Sara’s username and password so he sends an email with a link saying “Sara, you have important office messages”. But to this link he adds a returnUrl query string as shown below:
https://www.myoffice.com?returnUrl=https://www.myoffice1.com
The querystring targets a hacker site. So, when Sara click the link, she first reaches her office website (myoffice.com) which redirect her to the url specified in the returnUrl parameter i.e. myoffice1.com. Clearly see the hacker’s website has a very similar name with “1” added to it.
The hackers website design is totally same to the office website. This website also has a login form where Sara enters her username and password. That’s it, the hacker gets her credentials and now she is redirected to her original office website.
The office.com website ask here to login. She believes that her first attempt to log in failed and that her second attempt is successful. The attack was successful.
In open redirect attacks the user most likely remains unaware that their credentials are compromised.
Use LocalRedirect method of .NET to Prevent open redirect attacks. The LocalRedirect() method will throw an exception if a non-local URL is specified. Otherwise, it performs the redirect. Example is given below:
public IActionResult Login(string redirectUrl)
{
//login code
return LocalRedirect(redirectUrl);
}
You can also use IsLocalUrl method to check whether a URL is local before redirecting.
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
In SQL Injection attack the attacker injects malicious code in SQL statements which when executed will damage your database or steal data from database. Let us understand it with an example.
Example: A text box for entering userid is given on a website. It will provide user’s details from the database.
<form type="post">
Enter User Id: <input type="text" name="userId">
<input type="submit" value="Submit">
</form>
The SQL query that executes when the form is submitted:
"SELECT * FROM UserTable WHERE UserId = " + txtUserId;
The user enters user id ‘10’ in the text box and he receives the details of the user. Here everything works well.
Now if user enters 10; DROP TABLE UserTable. Then the query will drop the “UserTable” from the database. Why? because the query will become.
SELECT * FROM UserTable WHERE UserId = 10; DROP TABLE UserTable
In SQL, 1=1 is always true so if we append it to queries then we will get back result even if we provide wrong inputs. Suppose there is a form which provides super admin all the usernames and passwords of the app. However the super admin has to enter his very secret password.
<form type="post">
Enter Super Admin Password: <input type="text" name="password">
<input type="submit" value="Submit">
</form>
The SQL query executing when correct password is provided is:
SELECT * FROM Users WHERE SuperAdminPass = "*o*D~I/gpKl*?P]"
A hacker can never guess this strong password so he applied 1=1 trick. He enters “abc OR 1=1” to the password textbox. Now the query that will be executing is:
SELECT * FROM Users WHERE SuperAdminPass = "abc" OR 1=1
That it, with 1=1 in the OR clause the SQL query will return all the users from the table even though the password is wrong.
We can log-in to secured websites with 1=1 SQL Injection trick provided the website uses plain SQL Queries to check for user’s credentials in the database. Let us create an example in .NET to understand this.
We have a login form which looks like:
The login view razor code is:
<div class="text-center">
<h1 class="display-4">Login Form</h1>
<form method="post">
<h1 class="h3 mb-3 fw-normal">Please sign in</h1>
<div class="form-floating">
<input type="text" class="form-control" name="username" placeholder="Username">
<label for="floatingInput">Username</label>
</div>
<div class="form-floating">
<input type="text" class="form-control" name="password" placeholder="Password">
<label for="floatingPassword">Password</label>
</div>
<button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
</form>
</div>
We check the user’s credentials present in the database in the controller’s action method whose code is given below:
[HttpPost]
public IActionResult Login(string username, string password)
{
int count;
string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
using (SqlConnection connection = new SqlConnection(connectionString))
{
string sql = $"Select count(*) from Users where username='{username}' AND Password = '{password}'";
using (SqlCommand command = new SqlCommand(sql, connection))
{
connection.Open();
count = (int)command.ExecuteScalar();
connection.Close();
}
}
if (count == 0)
{
// login failed
return View();
}
else
{
// login successful
return RedirectToAction("Secured");
}
}
In the above code plain sql query is executed – string sql = $"Select count(*) from Users where username='{username}' AND Password = '{password}'";
. This is the main problem for exploitation.
See the if – else block where the checking is done i.e. if count(*) is 0 then login fails since no user is found in the database. The else block executes when login is successful and the user is redirected to secured area of the website.
if (count == 0)
{
// login failed
return View();
}
else
{
// login successful
return RedirectToAction("Secured");
}
Let’s now try to login with a random username ‘test’ and password ‘test’.
Login fails since no user is found in the database. See the below image where if block is executed.
It seems login feature is working correctly. Now we will login by entering ' or 1=1 --
on username textbox and anything on password.
When we enter ' or 1=1 --
the --
is SQL Comment which will prevent further statements from executing and so the SQL query that will be executing becomes:
Select count(*) from Users where username='' or 1=1 --' AND Password = 'abc'
After --
everything is skipped so password is not checked i.e. we are successful in doing login. See the below image where the else block is executed.
In ASP.NET core we can prevent SQL injection attacks by using stored procedures, or parameterized queries. The below code is an example of Inserting records with parameterized query.
[HttpPost]
public IActionResult Login(string username, string password)
{
int count;
string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
using (SqlConnection connection = new SqlConnection(connectionString))
{
string sql = $"Select count(*) from Users where username=@Username AND Password = @Password";
using (SqlCommand command = new SqlCommand(sql, connection))
{
command.Parameters.Add("@Username", SqlDbType.VarChar).Value = username;
command.Parameters.Add("@Password", SqlDbType.VarChar).Value= password;
connection.Open();
count = (int)command.ExecuteScalar();
connection.Close();
}
}
if (count == 0)
{
// login failed
return View();
}
else
{
// login successful
return RedirectToAction("Secured");
}
}
Here we changed the SQL Query by using @Username and @Password parameters.
string sql = $"Select count(*) from Users where username=@Username AND Password = @Password";
We define them in the code as.
command.Parameters.Add("@Username", SqlDbType.VarChar).Value = username;
command.Parameters.Add("@Password", SqlDbType.VarChar).Value= password;
And this will prevent SQL Injection.
I hope you now know some good Security techniques to apply in your .NET applications to make them more secure and prevent attacks. If you like this tutorial kindly share it with your .NET developer friends.
SHARE THIS ARTICLE