OWASP top 10 considerations for secure C# applications

By Joshua Holden

When developing an application, the integrity of infrastructure and data is paramount, as part of the development process, all developers should be (at the very least) aware of the OWASP top 10 and code defensively against the items included.
It’s also extremely important to ensure an application is penetration tested before and after deployment to a production environment, ideally by means of an integrated into CI/CD solution.
The following text covers at a high level some of the steps you as a developer should take to ensure your application is as secure as possible.


SQL  injection occurs when user input is not sanitised and is processed by the server resulting in unintended SQL commands being executed on the SQL server, this can allow the attacking actor to obtain data not intended to be seen such as user information and password data it could also allow an attacker to drop tables and cause damage to applications, as a developer there are a number of actions we can take to help mitigate the effectivity of injection attacks as enumerated below

SQL access permissions

Run the application under a service account or SQL account and ensure that objects are granted access on a per-item basis for example:

GRANT EXEC on USP_MyProcedure TO SQLUserAccount
Grant SELECT on dbo.MyTable to SQLUserAccount

Although it may seem tempting to give the SQL user account DB owner or another global access scope to avoid having to set permissions on a granular level it is always far more secure to take the extra effort and ensure the application can only access the objects it needs and with the minimum action grants permissible, for example, if the application will only ever read from a table, only give the account select access, delete update etc should not be given.

Parameterisation of SQL queries

A common cause of SQL injection flaws in applications is simply down to laziness when developing, often to save time a developer may pass raw string SQL into a command, if this is constructed using client-side user input then the query can be manipulated to run any SQL commands, this is partially mitigated by ensuring granular permissions as per the previous paragraph but can be fully mitigated by ensuring parameterisation of input an example of vulnerable SQL code is shown below:

public List<User> GetUser(string firstName)
  return context.Users
  .FromSqlRaw("SELECT * FROM dbo.users where firstname = ‘{firstName}’")

As you can see, this method, if provided a specially constructed string such as “bob’ or ‘1’ = ‘1” would result in the query below query being ran which would return every user in the database:

SELECT * FROM dbo.users where firstname = ‘bob’ or ‘1’ = ‘1’ 

To mitigate this, we can use parameterisation, using the same code as above it could made safe by adding the following code instead:

public List<User> GetUser(string firstName)
  return context.Users.FromSqlRaw("SELECT * FROM dbo.users where firstname = @fn",
  new SqlParameter("@fn", firstName)).ToList();

To summarise, never should you ever write code that allows SQL to be constructed and run without parameterising any variable input.

2: Broken Authentication and session management

Bad authentication and session management code design can lead to insecure applications that may allow an attacker accessing other users accounts and information or being able to use an administrative account to cause damage, thankfully with a bit of due care and thought this kind of issue can be mostly mitigated quite easily by addressing the following concerns:

  1. Ensure all passwords are salted and encrypted, ideally use ASP.Net membership and roles.
  2. Ensure sessions are properly destroyed on logout
  3. Avoid using cookieless state (the state is stored in the URL and can easily be hijacked or the user could copy and paste the url to a friend to show them something inadvertently giving their friend a session logged in as themselves)
  4. Flag all cookies as “HTTPOnly” so client-side script cannot access them and read session information.
  5. Set realistic session expiry times in the web.config, ideally as little as possible for example 10 minutes.
  6. Use SSL where possible and transmit all data over HTTPS.
  7. Enforce password strength rules, eg must be at least 8 characters and include non-alpha-numeric characters.
  8. Enforce password strength rules, eg must be at least 8 characters and include non-alpha-numeric characters.
  9. Turn off autocomplete on user authentication forms.

The above list is not exhaustive but does give a general idea of the sort of things you will need to check to ensure authentication and session management is as safe as possible.

3: Sensitive data exposure

A lot of web applications now use API’s to access information for the client-side and a good percentage of these applications furthermore do not have adequate protection on endpoints allowing attackers to steal personal identifying information and credit card details etc, an example of this would be a GET request on /API/Users which would then return the application user accounts.
When designing an application, the following precautions need to be implemented:

Ensure API's are tied down

Make sure that any sensitive information that can be accessed by API calls such as /API/User/UserID or /API/User has a layer of security against it so that only accounts with the correct roles can access the endpoints.

Don't use insecure cryptographic hashes

Do not use MD5 or SHA1 for sensitive information, instead opt for a more secure hashing algorithm such as RSA,3DES or AES hashes with a length of at least 1024 bits.

Ensure cookie security

Ensure any cookies have the following properties “HttpOnly”, ”Secure” (sent over HTTP/SSL), “PATH” (ensure the path matches expectations so as to prevent external web-applications from using it)

4: XXE (External XML Entities)

XXE attacks allow attackers to upload XML with requests for additional data included, an example of this is shown  below:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
  <!ELEMENT file ANY >
  <!ENTITY fle SYSTEM "file:///c:/passwordfile.txt" >]>

This would return the file contents of the passwordfile.txt file on the server assuming it existed.

Fortunately the .Net parser for XML as standard disables such functionality by default, however, if you are using external libraries such as wrappers for calling API’s and deserialising data then it is worth checking it does not allow XXE attacks by attempting to use the library to parse an example such as the one shown above. 

5: Broken access control 

Broken access control, not to be confused with broken authorisation is a method of exploiting an application to access data or functionality they otherwise should not be able to access, this can be done by a number of methods detailed below:

Webserver directory listing

Ensure that directory browsing is disabled to prevent attackers from being able to see filenames and directories on the server.

Prevent efficiency of URL tampering

Oftentimes an application URL is formatted as follows:  Http://xx.com/user/1/AccountInformation but let’s assume the user logged in has an ID of 1, what would happen if the user altered the URL and changed the id of 1 to 2 for example… does it return data?
This vulnerability can be solved easily in .Net usually by the inclusion of an [Authorize] attribute on the endpoint or performing a contextual check on the HTTP request user identity.

Misconfigured CORS configuration

Ensure the correct access control headers are included in the HTTP headers and are not too broad so as to allow untrusted external origins (do not use wildcards)

6: Security misconfiguration

Security misconfigurations on a server are easily missed and can easily allow an otherwise well coded and secure application to have vulnerabilities, ensuring configuration is updated and checked for production servers is incredibly important, some things you may want to ensure are:

Update default configuration

Key to good security is making sure that any third-party libraries are not left with default configurations and passwords, for example when using ELMAH for error logging make sure the logs cannot be accessed externally by setting the configuration to only show logs on the application server and configure filtering to not log sensitive information.
Further to this, always make sure routers and software etc are not configured with the default passwords.

IIS/Apache/NGINX (webserver) hardening

Make sure your web server is security-hardened, details for how to do this can be found on Google quite easily, for example, securing IIS.

7: XSS (Cross-site scripting)

XSS (Cross-site scripting is a vulnerability that allows attackers to place javascript into client-side pages that for example could capture the user's details and forward them to another server when the page is loaded containing the attacker's code.
In order to mitigate such attacks, the following steps need to be adhered to:

Sanitise user input

Whenever a page has a control that allows data to be input into a form and then later displayed in the page mark-up, always ensure that the data is HTML encoded.

Html encode URL parameters

If any data is to be added into a URL string as a URL parameter, first make sure it is HTML encoded.

Validate user input

While you should never rely on validation alone to prevent XSS attacks, some basic validation can be performed on user input to check for script tags etc albeit, you should always encode any stored input before output no matter how much sanitisation has been performed.

8: Insecure deserialisation

When using JSON.Net with type handling set to TypeNameHandling.All, it is possible to pass in a serialised System.Diagnostics.Process, this could be a  command to POST a copy of a file on your server to an endpoint specified by the attacker when deserialised.
As a rule, to avoid this type of issue, where possible you should not use generic or dynamic types for deserialisation in JSON.net, and only cast to known types, if you can’t avoid using TypeNameHandling.All then make sure you add custom validation and never trust external data being deserialised no matter what you are using to deserialise.

9: Using components with known vulnerabilities

You should always where possible ensure you are using the latest version of all libraries being used, for example keeping bootstrap up to date, where possible when working on an application ensure you go into NUGET and update to the latest version of all libraries and scripts each time you have to make changes to it or are aware of an update to a library your applications uses.
Furthermore, you can use mitre’s CVE list to search for versions of libraries and if they have vulnerabilities here.

10: Insufficient logging and monitoring

No matter what attack prevention you have in place, attackers are still going to try which is why it's essential to surface errors and events as visibly, and as quickly as possible, studies have shown that the average time to detect a breech is over 200 days, in-depth and detailed monitoring is extremely important in all applications and where possible everything should be logged and monitored as most attackers rely on not being detected so they can come back and try again multiple times until successful.


Hopefully, the above post provides at least a little information to help you as a developer produce more secure applications, thanks for reading and happy coding adventures!


Comments are closed