The security of .NET applications is necessary and knowing how to protect it is not always an easy task. As developers, we must be clear that an insecure application can be a serious problem: from modifying the operation of the application or stealing the source code, to exposing a company to legal liability.
Each developer, it is clear that he applies the security that he believes convenient for his applications and I also want to add that the possibilities and ways to do this are infinite.
But, there will always be a set of practices that any developer should follow when developing a .NET application. Following these best practices will prevent major application security issues. And that is why I have decided to compile the 10 commandments of .NET application security.
You shall delete cookies when you logout
We use sessions to maintain user login across visits. On some login pages, if you check a box and opt not to sign out, there is no session timeout setting.
All at once, the AspNetCore.Session
cookie is set to the browser to keep a track of the current logged-in user.
When logging out, remember to delete the Cookies your application has created, as a Hacker may be able to take advantage of them in an unauthorized login.
In case we want to overwrite the default cookie session, we can simply use SessionOptions like this:
builder.Services.AddSession(options =>
{
options.Cookie.Name = ".AdventureWorks.Session";
options.IdleTimeout = TimeSpan.FromSeconds(10);
Options.Cookie.IsEssential = true;
});
The options used are used for:
Cookie: Determine the settings used to create the cookie.
IdleTimeout: Indicate how long the session can be idle before its contents are abandoned.
IOTimeout: Indicate the maximum amount of time allowed to load a session from the store or to commit it back to the store.
I don’t want to expand any further here because this topic could be the subject of several articles. I recommend you to consult all the information about sessions and cookies in the article of 31 minutes of reading that I recommend to accompany with a good coffee (or the drink that is in your case😉)
👀Check: Session and state management in ASP.NET Core
You shall never forget XSS injections
Cross Site Scripting (XSS) is a vulnerability that allows an attacker to inject client-side scripts into web pages.
When you load an affected page, the attacker’s scripts will execute, with which they can steal your session tokens and cookies, change the content of the web page through DOM manipulation or even redirect you away from where you want to go. A Cross Site Scripting Vulnerability typically occurs when application takes user input and outputs it to unvalidated pages.
The places where it is easiest to carry out this type of attack are usually:
- HTTP Headers
- Form Inputs
- URL Query Strings
The first solution is to use proper HTTP headers. The X-XSS-Protection
HTTP header will cause a script filter to be enabled in the browser. This filter will prevent certain XSS attacks that can occur between sites.
One way to do this is. This will trigger the XSS filter that will sanitize the page and remove the unsafe parts:
X-XSS-Protection: 1;
It can also be done in this way. Adding mode=block
will enable XSS filtering which, upon detection of an attack, will directly prevent the page from being displayed instead of sanitizing it:
X-XSS-Protection: 1; mode=block;
We must also remember that the data inserted by users (in the case of a form), must be in plain text and not HTML because it could be executed.
Let’s see the correct and incorrect way to do this:
Bad way:
If you look closely, you will quickly notice that the data sent has the possibility of being executed.
document.getElementById("id").innerHTML = "user data";
Good way:
In this way, any data entered will be interpreted as plain text and no matter how hard they try to inject code, it will not be possible:
document.getElementById("id").textContent = "user data";
These ways are very useful when it comes to avoiding XSS attacks, but if you want to go deeper into the subject, I recommend this article in which I talk only about the different types of Cross Site Scripting attacks and how to avoid them.
You shall avoid direct connections to databases
It is clear that many times we need to connect our application to some database. One of these ways is using a connector for our application.
The problem is when that connector is in plain text, let’s see this example:
string connectionString =
"datasource=dev.bytehide.com; port=3306; username=root; password=secret; database=test;";
MySqlConnection databaseConnection = new MySqlConnection(connectionString);
This way of doing it is not safe at all. We can see perfectly well that sensitive data, such as server, username, port or password, are available to anyone who has access to our application.
If you’re looking for some quick fixes, here are some suggestions that might work:
- Don’t use Universal Data Link (UDL) files
- Use Azure Key Vault Secret
- Encrypt the configuration files
- Use Windows Authentication
Of course, if you want the best security for your data, I recommend outsourcing connections.
You shall not store sensitive information in your databases
Almost every web application must have a database for storing user data. However, hackers will always look to steal user data from databases — sometimes even going as far as to attack servers themselves and gaining unauthorised access in order to do so. If someone gains unauthorised access to your database, they can take advantage of it by stealing all the sensitive information they find there, including passwords and credit card details.
This seems like a joke but to this day I still see databases that store passwords in plain text, so this is a commandment.
To do this you must encrypt sensitive information so that it is not in plain text in the database. Regardless of this, common encryption can fall short of password protection. In cases of sensitive data it is best to hash the information and then verify it without the need for decryption keys so that the original information cannot be reversed.
If you want to go deeper into password hashing or any kind of information, I recommend this article (it is very complete): How to hash passwords
You shall always handle errors
Errors when developing an application are something that nobody wants to happen, but they always do. They are practically impossible to avoid, but when they do occur, you have to know how to handle them correctly. If this is not done, errors can leak internal information and this would not be a good thing.
One possible solution to this would be to always keep trace of the exception stack. It is preferable to use throw;
than throw e;
because if we use the latter, then it will return an empty string in production.
try
{
FunctionThatMightThrow();
}
catch (Exception error)
{
logger.LogInfo(error);
throw new CustomException(error);
}
Another possible and good way to do this would be to always analyze the detected errors.
If you spot an error, please don’t ignore it or just let it go without fixing it. There’s no point in doing this because then you will never be able to take care of the problem. If you know that errors are possible, and want to fix them before they happen, use a try/catch block for code where one could occur.
try
{
FunctionThatMightThrow();
}
catch (Exception error)
{
NotifyUserOfError(error); // Another option
ReportErrorToService(error);
}
As we can see, catching the exception could be a good idea because it will stand out in the console among all of the other printouts. If we wrap it up in a try/catch statement, then afterwards (if an error occurs), then we can make a plan to handle this or have code paths set up for such events.
You shall avoid CSRF attacks
Cross Site Request Forgery (CSRF) attacks are a fairly common and dangerous type of attack. They are based on a vulnerability in which the attacker can take over a user session to carry out any action in his favor (make purchases, money transfers and more…).
To explain it quickly with an example, let’s imagine a user with a logged-in session (cookies) and clicks on a button on a malicious page. That button on the malicious site will make unauthorized requests for the real user. As the user is logged in, any request made would be valid.
With AntiforgeryOptions you can easily avoid this by adding, for example:
builder.Services.AddAntiforgery(options =>
{
// Set Cookie properties using CookieBuilder properties.
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.SuppressXFrameOptionsHeader = false;
});
If you want to know all the possibilities and options of AntiforgeryOptions, this article from Microsoft is for you: Prevent Cross-Site Request Forgery attacks
You shall not forget to update your dependencies and libraries
This is probably an obvious way to prevent security breaches, but sadly many developers still don’t do this. In .NET we rely on libraries — lots of them — including Microsoft’s, user-made ones, and those found in NuGet packages.
Checking what library(s) our app uses can stop security breaches before they happen by informing us about any vulnerabilities or potential risks beforehand.
📚Remember: Keeping these libraries up to date is a very simple and effective way to avoid vulnerabilities in any .NET application.
It’s also crucial to keep an eye out for outdated versions of the framework used because there might be some serious vulnerabilities we’re unaware of that need immediate attention so that there won’t be a risk at all — this will make the application more secure and easier to maintain.
In order to achieve this, according to NuGet documentation you can use:
Update-Package
And as NuGet mentions:
“Update all packages in all projects of the current solution to the latest versions.”
This will ensure that all packages used by your solution will be updated.
If you want to know the vulnerabilities of a package, I recommend you to use Snyk Vulnerability DB
You shall avoid click-jacking attacks
For those who do not know what Clickjacking is, it is a type of attack that is based (in most cases) on the ignorance of the user to trick him and force him to click on another element of the web page.
This causes the user to visit unwanted websites, download malicious files, provide confidential information or even transfer money without being aware of it.
It is very important to avoid this, as it can cause serious problems to users visiting our website. We must not let any web site with a domain different from ours in iframe to open. This can be achieved in a very simple way in ASP.NET by adding an x-frame-options
response header and setting it as deny
. Let me show you an example:
void application_beginrequest(object sender, eventargs e)
httpcontext.current.response.addheader("x-frame-options ", "deny");
In this way, the header will always be added to the application response, in order to avoid any possible clickjacking attack.
In this Dotnetcoretutorials article you can learn in more depth How to set X-FRAME-OPTIONS in ASP.NET Core.
You shall prevent SQL injections
An SQL injection attack exploits a vulnerability in which malicious commands are inserted into SQL (Structured Query Language) queries, taking advantage of the dynamic nature of SQL to break down the security walls around data, allowing the attacker to access information that they should not be able to see or modify. Most companies do not have adequate defenses against this type of cyberattack, so it’s important to understand what it is and why it’s dangerous in order to properly defend against it.
By injecting the commands through a SQL injection, hackers can alter what happens on the database. Let’s see a simple example for those who don’t know how SQL injection works:
Let’s think that we have a login form in which we ask for username and password so that a user can log in:
When the information is submitted, a query would be executed in the database similar to the following:
Select id
from users
where username='$username' and password='$password';
Let’s suppose that the query is concatenated, if for example in the user field we enter root
(knowing that this user exists) and in the password field we enter 'or '1'=1
what would happen? I mean like this:
What would happen would be that parameter in the password field would modify the SQL query to something like this:
Select id
from users
where username='root' and password=''or '1'='1';
Result? As it is always true
, we would be able to log in with the user root
.
A very simple way to solve this is to pass the values as a parameter:
select * from users where username=@username and password=@password
But apart from that, Microsoft recommend:
Validate inputs: Validate inputs both client-side and server-side, also remember to use data annotations and regular expressions.
Use least-privileged DB access: To ensure the safety of a database, remove permissions so that no one can alter sensitive data or carry out certain actions. Start by restricting the insert, update, and delete permissions for tables related to payment details or transactions. Next, make sure nobody can access tables containing sensitive user information- such as passwords or usernames — because these entries could lead to an account takeover.
Use an ORM (like Entity Framework): Object-relational mapping (ORM) gives you the power to interact with objects as if they’re directly connected to a database without ever needing to know anything about the backend. A good option for this is Entity Framework.
You shall never write your own cryptography
One security truth that you can’t afford to ignore is that there isn’t a single developer out there who knows how to come up with an unbreakable cryptographic algorithm. It doesn’t matter if your team has tried for years and still hasn’t found the perfect combination — what matters is that you’re not taking chances.
This type of “home-made” cryptography is usually more prone to errors and bugs than actually protecting the source code of the application. As Runa Sandvik (expert security researcher) said on Twitter:
“Asking why you should not roll your own crypto is a bit like asking why you should not design your own aircraft engine” — Runa Sandvik
In the case of the .NET Framework there is good news. Currently there are quite a few implementations of obfuscation and encryption algorithms although, you have to be careful with some of them because over the years they have not been updated and those algorithms have become obsolete — their obfuscation and encryption can be easily removed with free tools on the internet.
This is a very big security problem and that is why I recommend using a Cloud Obfuscator. You don’t even need to download or install it on your computer ( and even less to update it) since you protect from a web panel.