Skip to main content

Secure Coding Guideline, .NET

This document describes the secure coding guidelines for the .NET programming language. Some of the guidelines are generic, whereas some are specific to the .NET programming language.

.Net Framework Guidance

Data Access

  • Use Parameterized SQL commands for all data access, without exception.
  • Do not use SqlCommand with a string parameter made up of a concatenated SQL String.
  • Whitelist allowable values coming from the user. Use enums, TryParse, or lookup values to assure that the data coming from the user is as expected.
    • Enums are still vulnerable to unexpected values because .NET only validates a successful cast to the underlying data type, integer by default. Enum.IsDefined can validate whether the input value is valid within the list of defined constants.
  • Apply the principle of least privilege when setting up the Database User in your database of choice. The database user should only be able to access items that make sense for the use case.
  • The use of the Entity Framework is a very effective SQL injection prevention mechanism. Remember that building your own ad hoc queries in EF is just as susceptible to SQLi as a plain SQL query.
  • When using SQL Server, prefer integrated authentication over SQL authentication.
  • Use Always Encrypted where possible for sensitive data (SQL Server 2016 and SQL Azure),

Encryption

  • Never, ever write your own encryption.
  • Use the Windows Data Protection API (DPAPI) for secure local storage of sensitive data.
  • Use a strong hash algorithm.
    • In .NET (both Framework and Core), the strongest hashing algorithm for general hashing requirements is System.Security.Cryptography.SHA512.
    • In the .NET framework, the strongest algorithm for password hashing is PBKDF2, implemented as System.Security.Cryptography.Rfc2898DeriveBytes.
    • In .NET Core, the strongest algorithm for password hashing is PBKDF2, implemented as Microsoft.AspNetCore.Cryptography.KeyDerivation.Pbkdf2, which has several significant advantages over Rfc2898DeriveBytes.
    • When using a hashing function to hash non-unique inputs such as passwords, use a salt value added to the original value before hashing.
  • Make sure your application or protocol can easily support a future change in cryptographic algorithms.
  • Use Nuget to keep all of your packages up to date. Watch the updates on your development setup, and plan updates to your applications accordingly.

General

  • Lockdown the config file.
    • Remove all aspects of configuration that are not in use.
    • Encrypt sensitive parts of the web.config using aspnet_regiis -pe
  • For Click Once applications, the .Net Framework should be upgraded to use version 4.6.2 to ensure TLS 1.1/1.2 support.

Securing State Data

Applications that handle sensitive data or make any kind of security decisions need to keep that data under their own control and cannot allow other potentially malicious code to access the data directly. The best way to protect data in memory is to declare the data as private or internal (with a scope limited to the same assembly) variables. However, even if this data is subject to access you should be aware of:

  • Using reflection mechanisms, highly trusted code that can reference your object can get and set private members.
  • Using serialization, highly trusted code can effectively get and set private members if it can access the corresponding data in the serialized form of the object.
  • Under debugging, this data can be read.

Make sure none of your methods or properties exposes these values unintentionally.

Security and User Input

User data, which is any kind of input (data from a Web request or URL, input to controls of a Microsoft Windows Forms application, and so on), can adversely influence code because often that data is used directly as parameters to call other code. This situation is analogous to malicious code calling your code with strange parameters, and the same precautions should be taken. User input is harder to make safe because there is no stack frame to trace the presence of potentially untrusted data.

These are among the subtlest and hardest security bugs to find because, although they can exist in code that is seemingly unrelated to security, they are a gateway to pass bad data through to other code. To look for these bugs, follow any kind of input data, imagine what the range of possible values might be, and consider whether the code seeing this data can handle all those cases. You can fix these bugs through range checking and rejecting any input the code cannot handle.

Some important considerations involving user data include the following:

  • Any user data in a server response runs in the context of the server's site on the client. If your Web server takes user data and inserts it into the returned Web page, it might, for example, include a <script> tag and run as if from the server.
  • Remember that the client can request any URL.
  • Consider tricky or invalid paths:
    • ..\, extremely long paths.
    • Use of wild card characters (*).
    • Token expansion (%token%).
    • Strange forms of paths with special meaning.
    • Alternate file system stream names such as filename::$DATA.
    • Short versions of file names such as longfi~1 for longfilename.
  • Remember that Eval(userdata) can do anything.
  • Be wary of late binding to a name that includes some user data.
  • If you are dealing with Web data, consider the various forms of escapes that are permissible, including:
    • Hexadecimal escapes (%nn).
    • Unicode escapes (%nnn).
    • Overlong UTF-8 escapes (%nn%nn).
    • Double escapes (%nn becomes %mmnn, where %mm is the escape for '%').
  • Be wary of user names that might have more than one canonical format. For example, you can often use either the MYDOMAIN\username form or the username@mydomain.example.com form.

Security and Race Conditions

Another area of concern is the potential for security holes exploited by race conditions. There are several ways in which this might happen. The subtopics that follow outline some of the major pitfalls that the developer must avoid.

Race Conditions in the Dispose Method

If a class's Dispose method (for more information, see Garbage Collection) is not synchronized, the cleanup code inside Dispose can be run more than once, as shown in the following example.

void Dispose()   
{
if (myObj != null)
{
Cleanup(myObj);
myObj = null;
}
}

Because this Dispose implementation is not synchronized, it is possible for Cleanup to be called by the first thread and then a second thread before _myObj is set to null. Whether this is a security concern depends on what happens when the Cleanup code runs.

A major issue with unsynchronized Dispose implementations involves the use of resource handles such as files. Improper disposal can cause the wrong handle to be used, which often leads to security vulnerabilities.

Race Conditions in Constructors

In some applications, it might be possible for other threads to access class members before their class constructors have completely run. You should review all class constructors to make sure that there are no security issues if this should happen or synchronize threads if necessary.

Race Conditions with Cached Objects

Code that caches security information or uses the code access security Assert operation might also be vulnerable to race conditions if other parts of the class are not appropriately synchronized, as shown in the following example.

void SomeSecureFunction()   
{
if (SomeDemandPasses())
{
fCallersOk = true;
DoOtherWork();
fCallersOk = false;
}
}
void DoOtherWork()
{
if (fCallersOK)
{
DoSomethingTrusted();
}
else
{
DemandSomething();
DoSomethingTrusted();
}
}

If there are other paths to DoOtherWork that can be called from another thread with the same object, an untrusted caller can slip past a demand.

If your code caches security information, make sure that you review it for this vulnerability.

Race Conditions in Finalizers

Race conditions can also occur in an object that references a static or unmanaged resource that it then frees in its finalizer. If multiple objects share a resource that is manipulated in a class's finalizer, the objects must synchronize all access to that resource.

Security and On-the-Fly Code Generation

Some libraries operate by generating code and running it to perform some operations for the caller. The basic problem is generating code on behalf of lesser-trust code and running it at a higher trust. The problem worsens when the caller can influence code generation, so you must ensure that only code you consider safe is generated.

You need to know exactly what code you are generating at all times. This means that you must have strict controls on any values that you get from a user, be they quote-enclosed strings (which should be escaped so they cannot include unexpected code elements), identifiers (which should be checked to verify that they are valid identifiers), or anything else. Identifiers can be dangerous because a compiled assembly can be modified so that its identifiers contain strange characters, which will probably break it (although this is rarely a security vulnerability).

It is recommended that you generate code with reflection emit, which often helps you avoid many of these problems.

When you compile the code, consider whether there is some way a malicious program could modify it. Is there a small window of time during which malicious code can change source code on disk before the compiler reads it or before your code loads the .dll file? If so, you must protect the directory containing these files, using an Access Control List in the file system, as appropriate.

Role-Based Security

Roles are often used in financial or business applications to enforce a policy. For example, an application might impose limits on the size of the transaction being processed depending on whether the user making the request is a member of a specified role. Clerks might have the authorization to process transactions that are less than a specified threshold, supervisors might have a higher limit, and vice presidents might have a still higher limit (or no limit at all). Role-based security can also be used when an application requires multiple approvals to complete an action. Such a case might be a purchasing system in which an employee can generate a purchase request, but only a purchasing agent can convert that request into a purchase order that can be sent to a supplier.

.NET Framework role-based security supports authorization by making information about the principal, which is constructed from an associated identity, available to the current thread. The identity (and the principal it helps to define) can be either based on a Windows account or be a custom identity unrelated to a Windows account. .NET Framework applications can make authorization decisions based on the principal's identity or role membership or both. A role is a named set of principals that have the same security privileges (such as a teller or a manager). A principal can be a member of one or more roles. Therefore, applications can use role membership to determine whether a principal is authorized to perform the requested action.

To provide ease of use and consistency with code access security, the .NET Framework role-based security provides System.Security.Permissions.PrincipalPermission objects that enable the common language runtime to perform authorization in a way that is similar to code access security checks. The PrincipalPermission class represents the identity or role that the principal must match and is compatible with both declarative and imperative security checks. You can also access a principal's identity information directly and perform role and identity checks in your code when needed.

The .NET Framework provides role-based security support that is flexible and extensible enough to meet the needs of a wide spectrum of applications. You can choose to interoperate with existing authentication infrastructures, such as COM+ 1.0 Services, or to create a custom authentication system. Role-based security is particularly well-suited for use in ASP.NET Web applications, which are processed primarily on the server. However, .NET Framework role-based security can be used on either the client or the server.

Securing resource access

When designing and writing your code, you need to protect and limit the access that code has to resources, especially when using or invoking code of unknown origin. So, keep in mind the following techniques to ensure your code is secure:

  • Do not use Code Access Security (CAS).
  • Do not use partially trusted code.
  • Do not use the AllowPartiallyTrustedCaller attribute (APTCA).
  • Do not use .NET Remoting.
  • Do not use Distributed Component Object Model (DCOM).
  • Do not use binary formatters.

Code Access Security and Security-Transparent Code are not supported as a security boundary with partially trusted code. We advise against loading and executing code of unknown origins without putting alternative security measures in place. The alternative security measures are:

  • Virtualization
  • AppContainers
  • Operating System (OS) users and permissions
  • Hyper-V containers

External References

Owner: Software Development Team