Security Code Scan - static code analyzer for .NET

Quick Facts

Two modes: for Developers and Auditors.

Detects various security vulnerability patterns: SQL Injection, Cross-Site Scripting (XSS), Cross-Site Request Forgery (CSRF), XML eXternal Entity Injection (XXE), etc.

Taint analysis to track user input data.

One click refactoring for some vulnerabilities.

Analyzes .NET and .NET Core projects in a background (IntelliSense) or during a build.

Continuous Integration (CI) through MSBuild. For Unix CI runners please use VS2017 NuGet package. See Continuous Integration Builds section for instructions.

Works with Visual Studio 2015 or higher. Visual Studio Community, Professional and Enterprise editions are supported. Other editors that support Roslyn based analyzers like Rider or OmniSharp should work too.

Open Source

Installation

Security Code Scan (SCS) can be installed as:

  • Visual Studio extension. Use the link or open “Tools > Extensions and Updates…” Select “Online” in the tree on the left and search for SecurityCodeScan in the right upper field. Click “Download” and install.
  • NuGet package.
    • Right-click on the root item in your solution. Select “Manage NuGet Packages for Solution…”. Select “Browse” on the top and search for Security Code Scan. Select project you want to install into and click “Install”.
    • Another option is to install the package into all projects in a solution: use “Tools > NuGet Package Manager > Package Manager Console”. Run the command Get-Project -All | Install-Package SecurityCodeScan.

Installing it as NuGet package gives an advantage to choose projects in a solution that should be analyzed. It is a good idea to exclude test projects, because they do not make it into a final product.

⚠️Note: In a .NET Core project, if you add a reference to a project that has SCS as a NuGet package, it is automatically added to the dependent project too. To disable this behavior, for example if the dependent project is a unit test project, mark the NuGet package as private in the .csproj or .vbproj file of the referenced project:

<PackageReference Include="SecurityCodeScan" Version="3.0.0" PrivateAssets="all" />

However it requires discipline to install SCS into every solution a developer works with. Installing it as a Visual Studio extension is a single install action.

Because of the Roslyn technology SCS is based on, only the NuGet version runs during a build (VS extension provides IntelliSense only) and can be integrated to any Continuous Integration (CI) server that supports MSBuild.

Continuous Integration Builds

If the CI server of your choice is using MSBuild, then integration of SCS is just a matter of adding NuGet packages and collecting the output from the build. SCS warnings are in the form of
[source file](line,column): warning SCS[rule id]: [warning description] [project_file]
If your CI server doesn’t support MSBuild, here is an example how it can be scripted to use Docker container for building:

  • git clone or copy by other means the sources to a local directory.
  • docker run -ti --rm --volume $PWD/SourcesFolderName:/tmp/app -w /tmp/app microsoft/dotnet:2.0-sdk
  • dotnet add src/SourcesFolderName/ProjectName.csproj package SecurityCodeScanVS2017 to reference SCS NuGet package in specific project file. Repeat for every project you want to analyze. Strictly speaking the step is not necessary if the SCS NuGet package is already referenced in project during development.
  • dotnet build
  • Grep the output.

Configuration

Full Solution Analysis

Full solution analysis is a Visual Studio (2015 Update 3 RC and later) feature that enables you to choose whether you see code analysis issues only in open Visual C# or Visual Basic files in your solution, or in both open and closed Visual C# or Visual Basic files in your solution. For performance reasons it is disabled by default. It is not needed if SCS is installed as NuGet package. In VS extension case open Tools > Options in Visual Studio. Select Text Editor > C# (or Basic) > Advanced. Make sure the “Enable full solution analysis” is checked:

Full Solution Analysis
Since Full solution analysis for IntelliSense has performance impact this is another reason to use SCS during a build only as a NuGet instead of Visual Studio extension. Microsoft has some additional information on the configuration option.

Testing on WebGoat.NET

Download an intentionally vulnerable project WebGoat.NET for testing. Open the solution. If you have installed SCS as a VS extension you should see warning after few seconds in the “Errors” tab. Make sure IntelliSense results are not filtered in the window:

Intellisense

If SCS is installed as NuGet package you’ll need to build the solution. Then you should see the warning in the “Errors” and “Output” tabs:

Intellisense

Analyzing .aspx and web.config Files

To enable analysis of these files you need to modify all C#(.csproj) and VB.NET(.vbproj) projects in a solution and add “AdditionalFileItemNames” element as shown below:

<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    [..]
    <TargetFrameworkProfile />
    <!-- Add the line below -->
    <AdditionalFileItemNames>$(AdditionalFileItemNames);Content</AdditionalFileItemNames>
  </PropertyGroup>

The helper PowerShell script can be used to do it automatically for all projects in a subfolder:

Get-ChildItem *.csproj -Recurse | ForEach-Object {
$content = [xml] (Get-Content $_)
     
if (-not $content.Project.PropertyGroup[0].AdditionalFileItemNames)
    {
    Write-Host "AdditionalFileItemNames missing in $_"
    $additionalFileItemNamesElt = $content.CreateElement("AdditionalFileItemNames",
    "http://schemas.microsoft.com/developer/msbuild/2003")
    $additionalFileItemNamesElt.set_InnerText('$(AdditionalFileItemNames);Content')
    $content.Project.PropertyGroup[0].AppendChild($additionalFileItemNamesElt)
    }

Set-ItemProperty $_ -name IsReadOnly -value $false
$content.Save($_)
# Normalize line endings
(Get-Content $_ -Encoding UTF8) | Set-Content $_ -Encoding UTF8
}

External Configuration Files

There are two types of external configuration files that can be used together: per user account and per project. It allows you to customize settings from built-in configuration or add new rules. Global settings file location is %LocalAppData%\SecurityCodeScan\config-2.0.yml on Windows and $XDG_DATA_HOME/.local/share on Unix.

For project specific settings add SecurityCodeScan.config.yml into a project. Go to file properties and set the Build Action to AdditionalFiles:

image

Custom taint source, sinks, sanitizers and validators

An example of user’s (per OS user) config-2.0.yml with custom Anti CSRF token:

CsrfProtectionAttributes:
  -  HttpMethodsNameSpace: Microsoft.AspNetCore.Mvc
     AntiCsrfAttribute: MyNamespace.MyAntiCsrfAttribute

An example of SecurityCodeScan.config.yml (per project) with custom sink function (method that shouldn’t be called with untrusted data without first being sanitized):

Version: 2.0

Behavior:
  UniqueKey:
    Namespace: MyNamespace
    ClassName: Test
    Name: VulnerableFunctionName
    InjectableArguments: [SCS0001: [0: HtmlEscaped]]

See the configuration file for comments and examples of usage.

Audit Mode

Audit mode is off by default. It can be turned on in an external configuration file to get more potentially false positive warnings about data with unknown taint state.

Suppressing and Fixing the Warnings

If Code Fixer is not implemented for the warning the link “Show potential fixes” won’t work. For many warnings there are too many options to resolve the issue, so the code has to be modified manually. If the warning is false positive it can be suppressed that is standard functionality for Visual Studio however the UI not very intuitive, because you have to click on the underlined piece of code, only then a bubble appears at the beginning of the line where suppress menu is available:

Suppress

Another place where the menu is available is Error List:

Suppress

It is possible to filter shown item in Error List by different criteria: warning id, project name, etc. You can permanently suppress entire warning type for a project by setting it’s warning id severity to None. Microsoft has it’s own documentation about suppressions, rule sets and severities.

Severity

Each warning severity is configurable: expand References > Analyzers > SecurityCodeScan under the project in a Solution window, right click on a warning ID and modify the severity. WebGoat.NET.ruleset will be automatically saved in the project’s directory:

Intellisense

Troubleshooting

If no SCS warnings are displayed, temporarily disable other installed analyzers. A buggy analyzer may affect results from other analyzers.

Rules

Injection

References

OWASP: Top 10 2013-A1-Injection

SCS0001 - Command Injection

The dynamic value passed to the command execution should be validated.

Risk

If a malicious user controls either the FileName or Arguments, he might be able to execute unwanted commands or add unwanted argument. This behavior would not be possible if input parameter are validate against a white-list of characters.

Vulnerable Code

var p = new Process();
p.StartInfo.FileName = "exportLegacy.exe";
p.StartInfo.Arguments = " -user " + input + " -role user";
p.Start();

Solution

Regex rgx = new Regex(@"^[a-zA-Z0-9]+$");
if(rgx.IsMatch(input))
{
    var p = new Process();
    p.StartInfo.FileName = "exportLegacy.exe";
    p.StartInfo.Arguments = " -user " + input + " -role user";
    p.Start();
}

References

OWASP: Command Injection
OWASP: Top 10 2013-A1-Injection
CWE-78: Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’)

SCS0003 - XPath Injection

The dynamic value passed to the XPath query should be validated.

Risk

If the user input is not properly filtered, a malicious user could extend the XPath query.

Vulnerable Code

var doc = new XmlDocument {XmlResolver = null};
doc.Load("/config.xml");
var results = doc.SelectNodes("/Config/Devices/Device[id='" + input + "']");

Solution

Regex rgx = new Regex(@"^[a-zA-Z0-9]+$");
if(rgx.IsMatch(input)) //Additional validation
{
    XmlDocument doc = new XmlDocument {XmlResolver = null};
    doc.Load("/config.xml");
    var results = doc.SelectNodes("/Config/Devices/Device[id='" + input + "']");
}

References

CWE-643: Improper Neutralization of Data within XPath Expressions (‘XPath Injection’)
OWASP: XPATH Injection
Black Hat Europe 2012: Hacking XPath 2.0
WASC-39: XPath Injection

SCS0007 - XML eXternal Entity Injection (XXE)

The XML parser is configured incorrectly. The operation could be vulnerable to XML eXternal Entity (XXE) processing.

Risk

Vulnerable Code

Prior to .NET 4.5.2

// DTD expansion is enabled by default
XmlReaderSettings settings = new XmlReaderSettings();
XmlReader reader = XmlReader.Create(inputXml, settings);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(pathToXmlFile);
Console.WriteLine(xmlDoc.InnerText);

Solution

Prior to .NET 4.5.2

var settings = new XmlReaderSettings();
// Prior to .NET 4.0
settings.ProhibitDtd = true; // default is false!
// .NET 4.0 - .NET 4.5.2
settings.DtdProcessing = DtdProcessing.Prohibit; // default is DtdProcessing.Parse!

XmlReader reader = XmlReader.Create(inputXml, settings);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.XmlResolver = null; // Setting this to NULL disables DTDs - Its NOT null by default.
xmlDoc.Load(pathToXmlFile);
Console.WriteLine(xmlDoc.InnerText);

.NET 4.5.2 and later

In .NET Framework versions 4.5.2 and up, XmlTextReader’s internal XmlResolver is set to null by default, making the XmlTextReader ignore DTDs by default. The XmlTextReader can become unsafe if if you create your own non-null XmlResolver with default or unsafe settings.

References

OWASP.org: XML External Entity (XXE) Prevention Cheat Sheet (.NET)
CWE-611: Improper Restriction of XML External Entity Reference (‘XXE’)
CERT: IDS10-J. Prevent XML external entity attacks
OWASP.org: XML External Entity (XXE) Processing
WS-Attacks.org: XML Entity Expansion
WS-Attacks.org: XML External Entity DOS
WS-Attacks.org: XML Entity Reference Attack
Identifying Xml eXternal Entity vulnerability (XXE)

SCS0018 - Path Traversal

A path traversal attack (also known as directory traversal) aims to access files and directories that are stored outside the expected directory.By manipulating variables that reference files with “dot-dot-slash (../)” sequences and its variations or by using absolute file paths, it may be possible to access arbitrary files and directories stored on file system including application source code or configuration and critical system files.

Risk

With a malicious relative path, an attacker could reach a secret file.

Vulnerable Code

[RedirectingAction]
public ActionResult Download(string fileName)
{
    byte[] fileBytes = System.IO.File.ReadAllBytes(Server.MapPath("~/ClientDocument/") + fileName);
    return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

The following request downloads a file of the attacker choice: http://www.address.com/Home/Download?fileName=../../somefile.txt

Solution

Do not try to strip invalid characters. Fail if any unexpected character is detected.

private static readonly char[] InvalidFilenameChars = Path.GetInvalidFileNameChars();

[RedirectingAction]
public ActionResult Download(string fileName)
{
    if (fileName.IndexOfAny(InvalidFilenameChars) >= 0)
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        
    byte[] fileBytes = System.IO.File.ReadAllBytes(Server.MapPath("~/ClientDocument/") + fileName);
    return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

If the input is not supplied by user or a validation is in place the warning can be suppressed.

References

OWASP: Path Traversal
OS Command Injection, Path Traversal & Local File Inclusion Vulnerability - Notes

SCS0029 - Cross-Site Scripting (XSS)

A potential XSS was found. The endpoint returns a variable from the client input that has not been encoded. To protect against stored XSS attacks, make sure any dynamic content coming from user or data store cannot be used to inject JavaScript on a page. Most modern frameworks will escape dynamic content by default automatically (Razor for example) or by using special syntax (<%: content %>, <%= HttpUtility.HtmlEncode(content) %>).

Risk

XSS could be used to execute unwanted JavaScript in a client’s browser. XSS can be used to steal the cookie containing the user’s session ID. There is rarely a good reason to read or manipulate cookies in client-side JavaScript, so consider marking cookies as HTTP-only, meaning that cookies will be received, stored, and sent by the browser, but cannot be modified or read by JavaScript.

Vulnerable Code

public class TestController : Controller
{
    [HttpGet(""{myParam}"")]
    public string Get(string myParam)
    {
        return "value " + myParam;
    }
}

Solution

public class TestController : Controller
{
    [HttpGet(""{myParam}"")]
    public string Get(string myParam)
    {
        return "value " + HttpUtility.HtmlEncode(myParam);
    }
}

References

WASC-8: Cross Site Scripting
OWASP: XSS Prevention Cheat Sheet
OWASP: Top 10 2013-A3: Cross-Site Scripting (XSS)
CWE-79: Improper Neutralization of Input During Web Page Generation (‘Cross-site Scripting’)

SCS0031 - LDAP Injection

The dynamic value passed to the LDAP query should be validated.

Risk

If the user input is not properly filtered, a malicious user could extend the LDAP query.

Vulnerable Code

var searcher = new DirectorySearcher();
searcher.Filter = "(cn=" + input + ")";

or

var dir = new DirectoryEntry();
dir.Path = $"GC://DC={input},DC=com";

Solution

Use proper encoder (LdapFilterEncode or LdapDistinguishedNameEncode) from AntiXSS library:

var searcher = new DirectorySearcher();
searcher.Filter = "(cn=" + Encoder.LdapFilterEncode(input) + ")";

or

var dir = new DirectoryEntry();
dir.Path = $"GC://DC={Encoder.LdapDistinguishedNameEncode(input)},DC=com";

References

OWASP: LDAP Injection
OWASP: LDAP Injection Prevention Cheat Sheet
MSDN Blog - Security Tools: LDAP Injection and mitigation
WASC-29: LDAP Injection
CWE-90: Improper Neutralization of Special Elements used in an LDAP Query (‘LDAP Injection’)

SQL Injection

SQL injection flaws are introduced when software developers create dynamic database queries that include user supplied input.

Risk

Malicious user might get direct read and/or write access to the database. If the database is poorly configured the attacker might even get Remote Code Execution (RCE) on the machine running the database.

References

WASC-19: SQL Injection
OWASP: SQL Injection Prevention Cheat Sheet
OWASP: Query Parameterization Cheat Sheet
CAPEC-66: SQL Injection
CWE-89: Improper Neutralization of Special Elements used in an SQL Command (‘SQL Injection’)

SCS0002 - SQL Injection (LINQ)

Vulnerable Code

db.ExecuteQuery(@"SELECT name FROM dbo.Users WHERE UserId = " + inputId + " AND group = 5");
var query = "SELECT name FROM dbo.Users WHERE UserId = " + userId + " AND group = 5";
var id = context.ExecuteQuery<IEnumerable<string>>(query).SingleOrDefault();

Solution

var query = from user in db.Users
where user.UserId == inputId
select user.name;
var query = "SELECT name FROM dbo.Users WHERE UserId = {0} AND group = 5";
var id = context.ExecuteQuery<IEnumerable<string>>(query, userId).SingleOrDefault();

References

LINQ: How to Query for Information

SCS0014 - SQL Injection (WebControls)

Unsafe usage of System.Web.UI.WebControls.SqlDataSource, System.Web.UI.WebControls.SqlDataSourceView or Microsoft.Whos.Framework.Data.SqlUtility.

Vulnerable Code

"Select * From Customers where CustomerName = " & txtCustomerName.Value

Solution

To help protect against SQL statement exploits, never create SQL queries using string concatenation. Instead, use a parameterized query and assign user input to parameter objects. By default, the SqlDataSource control uses the System.Data.SqlClient data provider to work with SQL Server as the data source. The System.Data.SqlClient provider supports named parameters as placeholders, as shown in the following example:

<asp:sqlDataSource ID="EmployeeDetailsSqlDataSource" 
  SelectCommand="SELECT EmployeeID, LastName, FirstName FROM Employees WHERE EmployeeID = @EmpID"

  InsertCommand="INSERT INTO Employees(LastName, FirstName) VALUES (@LastName, @FirstName); 
                 SELECT @EmpID = SCOPE_IDENTITY()"
  UpdateCommand="UPDATE Employees SET LastName=@LastName, FirstName=@FirstName 
                   WHERE EmployeeID=@EmployeeID"
  DeleteCommand="DELETE Employees WHERE EmployeeID=@EmployeeID"

  ConnectionString="<%$ ConnectionStrings:NorthwindConnection %>"
  OnInserted="EmployeeDetailsSqlDataSource_OnInserted"
  RunAt="server">

  <SelectParameters>
    <asp:Parameter Name="EmpID" Type="Int32" DefaultValue="0" />
  </SelectParameters>

  <InsertParameters>
    <asp:Parameter Name="EmpID" Direction="Output" Type="Int32" DefaultValue="0" />
  </InsertParameters>

</asp:sqlDataSource>

If you are connecting to an OLE DB or ODBC data source, you can configure the SqlDataSource control to use the System.Data.OleDb or System.Data.Odbc provider to work with your data source, respectively. The System.Data.OleDb and System.Data.Odbc providers support only positional parameters identified by the “?” character, as shown in the following example:

...
<asp:SqlDataSource ID="EmployeeDetailsSqlDataSource" 
  SelectCommand="SELECT EmployeeID, LastName, FirstName, Address, City, Region, PostalCode
                 FROM Employees WHERE EmployeeID = ?"

  InsertCommand="INSERT INTO Employees(LastName, FirstName, Address, City, Region, PostalCode)
                 VALUES (?, ?, ?, ?, ?, ?); 
                 SELECT @EmpID = SCOPE_IDENTITY()"

  UpdateCommand="UPDATE Employees SET LastName=?, FirstName=?, Address=?,
                   City=?, Region=?, PostalCode=?
                 WHERE EmployeeID=?"
...

References

MSDN: Using Parameters with the SqlDataSource Control
MSDN: Script Exploits Overview
MSDN: Filtering Event
See references in the main SQL Injection section

SCS0020 - SQL Injection (OLE DB)

Use parametrized queries to mitigate SQL injection.

Vulnerable Code

string queryString = "SELECT OrderID, CustomerID FROM Orders WHERE OrderId = " + userInput;

using (var connection = new OleDbConnection(connectionString))
{
    OleDbCommand command = new OleDbCommand(queryString, connection);
    connection.Open();
    OleDbDataReader reader = command.ExecuteReader();
}

Solution

string queryString = "SELECT OrderID, CustomerID FROM Orders WHERE OrderId = ?";

using (var connection = new OleDbConnection(connectionString))
{
    OleDbCommand command = new OleDbCommand(queryString, connection);
    command.Parameters.Add("@p1", OleDbType.Integer).Value = userInput;
    connection.Open();
    OleDbDataReader reader = command.ExecuteReader();
}

References

OleDbCommand Documentation
See references in the main SQL Injection section

SCS0025 - SQL Injection (ODBC)

Use parametrized queries to mitigate SQL injection.

Vulnerable Code

var command = new OdbcCommand("SELECT * FROM [user] WHERE id = " + userInput, connection);
OdbcDataReader reader = command.ExecuteReader();

Solution

var command = new OdbcCommand("SELECT * FROM [user] WHERE id = ?", connection);
command.Parameters.Add("@id", OdbcType.Int).Value = 4;
OdbcDataReader reader = command.ExecuteReader();

References

OdbcCommand Documentation
See references in the main SQL Injection section

SCS0026 - SQL Injection (MsSQL Data Provider)

Use parametrized queries to mitigate SQL injection.

Vulnerable Code

var cmd = new SqlCommand("SELECT * FROM Users WHERE username = '" + username + "' and role='user'");

Solution

var cmd = new SqlCommand("SELECT * FROM Users WHERE username = @username and role='user'");
cmd.Parameters.AddWithValue("username", username);

References

SqlCommand Class Documentation
See references in the main SQL Injection section

SCS0035 - SQL Injection (Entity Framework)

Use parametrized queries to mitigate SQL injection.

Vulnerable Code

var cmd = "SELECT * FROM Users WHERE username = '" + input + "' and role='user'";
ctx.Database.ExecuteSqlCommand(
    cmd);

Solution

var cmd = "SELECT * FROM Users WHERE username = @username and role='user'";
ctx.Database.ExecuteSqlCommand(
    cmd,
    new SqlParameter("@username", input));

References

Entity Framework Documentation
Execute Raw SQL Queries in Entity Framework 6
Executing Raw SQL Queries in Entity Framework Core
Bobby Tables: A guide to preventing SQL injection > Entity Framework
See references in the main SQL Injection section

SCS0036 - SQL Injection (EnterpriseLibrary.Data)

Use parametrized queries to mitigate SQL injection.

Vulnerable Code

db.ExecuteDataSet(CommandType.Text, "SELECT * FROM Users WHERE username = '" + input + "' and role='user'");

Solution

DbCommand cmd = db.GetSqlStringCommand("SELECT * FROM Users WHERE username = @username and role='user'");
db.AddInParameter(cmd, "@username", DbType.String, input);
db.ExecuteDataSet(cmd);

References

Microsoft.Practices.EnterpriseLibrary.Data.Sql Namespace
See references in the main SQL Injection section

Cryptography

SCS0004 - Certificate Validation Disabled

Certificate Validation has been disabled. The communication could be intercepted.

Risk

Disabling certificate validation is often used to connect easily to a host that is not signed by a root certificate authority. As a consequence, this is vulnerable to Man-in-the-middle attacks since the client will trust any certificate.

Vulnerable Code

ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;

Solution

  • Make sure the validation is disabled only in testing environment or
  • Use certificate pinning for development or
  • Use properly signed certificates for development
#if DEBUG
    ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
#endif

References

WASC-04: Insufficient Transport Layer Protection
CWE-295: Improper Certificate Validation

SCS0005 - Weak Random Number Generator

The random numbers generated could be predicted.

Risk

The use of a predictable random value can lead to vulnerabilities when used in certain security critical contexts.

Vulnerable Code

var rnd = new Random();
byte[] buffer = new byte[16];
rnd.GetBytes(buffer);
return BitConverter.ToString(buffer);

Solution

using System.Security.Cryptography;
var rnd = RandomNumberGenerator.Create();

References

OWASP: Insecure Randomness
CWE-330: Use of Insufficiently Random Values
CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)
CWE-331: Insufficient Entropy

SCS0006 - Weak hashing function

MD5 or SHA1 have known collision weaknesses and are no longer considered strong hashing algorithms.

Vulnerable Code

var hashProvider = new SHA1CryptoServiceProvider();
var hash = hashProvider.ComputeHash(str);

Solution

Use SHA256 or SHA512. Notice, that hashing algorithms are designed to be fast and shouldn’t be used directly for hashing passwords. Use adaptive algorithms for the purpose.

var hashProvider = SHA256Managed.Create();
var hash = hashProvider.ComputeHash(str);

References

MSDN: SHA256 Class documentation
Salted Password Hashing - Doing it Right

SCS0010 - Weak cipher algorithm

DES and 3DES are not considered a strong cipher for modern applications. Currently, NIST recommends the usage of AES block ciphers instead.

Risk

Broken or deprecated ciphers have typically known weakness. A attacker might be able to brute force the secret key use for the encryption. The confidentiality and integrity of the information encrypted is at risk.

Vulnerable Code

DES DESalg = DES.Create();

// Create a string to encrypt. 
byte[] encrypted;
ICryptoTransform encryptor = DESalg.CreateEncryptor(key, zeroIV);

// Create the streams used for encryption. 
using (MemoryStream msEncrypt = new MemoryStream())
{
    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt,
                                                     encryptor,
                                                     CryptoStreamMode.Write))
    {
        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
        {
            //Write all data to the stream.
            swEncrypt.Write(Data);
        }
        encrypted = msEncrypt.ToArray();
        return encrypted;
    }
}

Solution

Use AES for symmetric encryption.

// Create a string to encrypt. 
byte[] encrypted;
var encryptor = new AesManaged();
encryptor.Key = key;
encryptor.GenerateIV();
var iv = encryptor.IV;

// Create the streams used for encryption. 
using (MemoryStream msEncrypt = new MemoryStream())
{
    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt,
                                                     encryptor.CreateEncryptor(),
                                                     CryptoStreamMode.Write))
    {
        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
        {
            //Write all data to the stream.
            swEncrypt.Write(Data);
        }
        encrypted = msEncrypt.ToArray();
        return encrypted;
    }
}

Notice that AES itself doesn’t protect from encrypted data tampering. For an example of authenticated encryption see the Solution in Weak Cipher Mode

References

NIST Withdraws Outdated Data Encryption Standard
CWE-326: Inadequate Encryption Strength
StackOverflow: Authenticated encryption example

SCS0011 - Weak CBC Mode

The CBC mode alone is susceptible to padding oracle attack.

Risk

If an attacker is able to submit encrypted payload and the server is decrypting its content. The attacker is likely to decrypt its content.

Vulnerable Code

using (var aes = new AesManaged {
    KeySize = KeyBitSize,
    BlockSize = BlockBitSize,
    Mode = CipherMode.CBC,
    Padding = PaddingMode.PKCS7
})
{
    //Use random IV
    aes.GenerateIV();
    iv = aes.IV;
    using (var encrypter = aes.CreateEncryptor(cryptKey, iv))
    using (var cipherStream = new MemoryStream())
    {
        using (var cryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
        using (var binaryWriter = new BinaryWriter(cryptoStream))
        {
            //Encrypt Data
            binaryWriter.Write(secretMessage);
        }
        cipherText = cipherStream.ToArray();
    }
}
//No HMAC suffix to check integrity!!!

Solution

See the Solution in Weak Cipher Mode.

References

Padding Oracles for the masses (by Matias Soler)
Wikipedia: Authenticated encryption
NIST: Authenticated Encryption Modes
CAPEC: Padding Oracle Crypto Attack
CWE-696: Incorrect Behavior Order

SCS0012 - Weak ECB Mode

ECB mode will produce the same result for identical blocks (ie: 16 bytes for AES). An attacker could be able to guess the encrypted message. The use of AES in CBC mode with a HMAC is recommended guaranteeing integrity and confidentiality.

Risk

The ECB mode will produce identical encrypted block for equivalent plain text block. This could allow an attacker that is eavesdropping to guess the content sent. This same property can also allow the recovery of the original message. Furthermore, this cipher mode alone does not guarantee integrity.

Vulnerable Code

using (var aes = new AesManaged {
    KeySize = KeyBitSize,
    BlockSize = BlockBitSize,
    Mode = CipherMode.ECB, // !!!
    Padding = PaddingMode.PKCS7
})
{
    //Use random IV
    aes.GenerateIV();
    iv = aes.IV;
    using (var encrypter = aes.CreateEncryptor(cryptKey, iv))
    using (var cipherStream = new MemoryStream())
    {
        using (var cryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
        using (var binaryWriter = new BinaryWriter(cryptoStream))
        {
            //Encrypt Data
            binaryWriter.Write(secretMessage);
        }
        cipherText = cipherStream.ToArray();
    }
}
//No HMAC suffix to check integrity!!!

Solution

Use some other mode, but notice that CBC without authenticated integrity check is vulnerable to another type of attack. For an example of authenticated integrity check see the Solution in Weak Cipher Mode.

References

Wikipedia: ECB mode
Padding Oracles for the masses (by Matias Soler)
Wikipedia: Authenticated encryption
NIST: Authenticated Encryption Modes
CAPEC: Padding Oracle Crypto Attack
CWE-696: Incorrect Behavior Order

SCS0013 - Weak Cipher Mode

The cipher text produced is susceptible to alteration by an adversary.

Risk

The cipher provides no way to detect that the data has been tampered with. If the cipher text can be controlled by an attacker, it could be altered without detection. The use of AES in CBC mode with a HMAC is recommended guaranteeing integrity and confidentiality.

Vulnerable Code

using (var aes = new AesManaged {
    KeySize = KeyBitSize,
    BlockSize = BlockBitSize,
    Mode = CipherMode.OFB,
    Padding = PaddingMode.PKCS7
})
{
    using (var encrypter = aes.CreateEncryptor(cryptKey, new byte[16]))
    using (var cipherStream = new MemoryStream())
    {
        using (var cryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
        using (var binaryWriter = new BinaryWriter(cryptoStream))
        {
            //Encrypt Data
            binaryWriter.Write(secretMessage);
        }
        cipherText = cipherStream.ToArray();
    }
}
//Missing HMAC suffix to assure integrity

Solution

Using bouncy castle:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;

public static readonly int BlockBitSize = 128;
public static readonly int KeyBitSize = 256;

public static byte[] SimpleEncrypt(byte[] secretMessage, byte[] key)
{
    //User Error Checks
    if (key == null || key.Length != KeyBitSize / 8)
        throw new ArgumentException(String.Format("Key needs to be {0} bit!", KeyBitSize), "key");

    if (secretMessage == null || secretMessage.Length == 0)
        throw new ArgumentException("Secret Message Required!", "secretMessage");

    //Using random nonce large enough not to repeat
    var nonce = new byte[NonceBitSize / 8];
    Random.NextBytes(nonce, 0, nonce.Length);

    var cipher = new GcmBlockCipher(new AesFastEngine());
    var parameters = new AeadParameters(new KeyParameter(key), MacBitSize, nonce, new byte[0]);
    cipher.Init(true, parameters);

    //Generate Cipher Text With Auth Tag
    var cipherText = new byte[cipher.GetOutputSize(secretMessage.Length)];
    var len = cipher.ProcessBytes(secretMessage, 0, secretMessage.Length, cipherText, 0);
    cipher.DoFinal(cipherText, len);

    //Assemble Message
    using (var combinedStream = new MemoryStream())
    {
        using (var binaryWriter = new BinaryWriter(combinedStream))
        {
            //Prepend Nonce
            binaryWriter.Write(nonce);
            //Write Cipher Text
            binaryWriter.Write(cipherText);
        }
        return combinedStream.ToArray();
    }
}

Custom implementation of Encrypt and HMAC:

using System.IO;
using System.Security.Cryptography;
public static byte[] SimpleEncrypt(byte[] secretMessage, byte[] cryptKey, byte[] authKey, byte[] nonSecretPayload = null)
{
    //User Error Checks
    if (cryptKey == null || cryptKey.Length != KeyBitSize / 8)
        throw new ArgumentException(String.Format("Key needs to be {0} bit!", KeyBitSize), "cryptKey");

    if (authKey == null || authKey.Length != KeyBitSize / 8)
        throw new ArgumentException(String.Format("Key needs to be {0} bit!", KeyBitSize), "authKey");

    if (secretMessage == null || secretMessage.Length < 1)
        throw new ArgumentException("Secret Message Required!", "secretMessage");

    byte[] cipherText;
    byte[] iv;
    using (var aes = new AesManaged {
        KeySize = KeyBitSize,
        BlockSize = BlockBitSize,
        Mode = CipherMode.CBC,
        Padding = PaddingMode.PKCS7
    })
    {
        //Use random IV
        aes.GenerateIV();
        iv = aes.IV;
        using (var encrypter = aes.CreateEncryptor(cryptKey, iv))
        using (var cipherStream = new MemoryStream())
        {
            using (var cryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
            using (var binaryWriter = new BinaryWriter(cryptoStream))
            {
            //Encrypt Data
            binaryWriter.Write(secretMessage);
            }
            cipherText = cipherStream.ToArray();
        }
    }
    //Assemble encrypted message and add authentication
    using (var hmac = new HMACSHA256(authKey))
    using (var encryptedStream = new MemoryStream())
    {
        using (var binaryWriter = new BinaryWriter(encryptedStream))
        {
            //Prepend IV
            binaryWriter.Write(iv);
            //Write Ciphertext
            binaryWriter.Write(cipherText);
            binaryWriter.Flush();
            //Authenticate all data
            var tag = hmac.ComputeHash(encryptedStream.ToArray());
            //Postpend tag
            binaryWriter.Write(tag);
        }
        return encryptedStream.ToArray();
    }
}

References

Padding Oracles for the masses (by Matias Soler)
Wikipedia: Authenticated encryption
NIST: Authenticated Encryption Modes
CAPEC: Padding Oracle Crypto Attack
CWE-696: Incorrect Behavior Order

Cookies

It is recommended to specify the Secure flag to new cookie.

Risk

The Secure flag is a directive to the browser to make sure that the cookie is not sent by unencrypted channel

Vulnerable Code

The requireSSL value is explicitly set to false or the default is left.

<httpCookies requireSSL="false" [..] />
// default is left
var cookie = new HttpCookie("test");
// or explicitly set to false
var cookie = new HttpCookie("test");
cookie.Secure = false;

Solution

<httpCookies requireSSL="true" [..] />
var cookie = new HttpCookie("test");
cookie.Secure = true; //Add this flag
cookie.HttpOnly = true;

References

CWE-614: Sensitive Cookie in HTTPS Session Without ‘Secure’ Attribute
CWE-315: Cleartext Storage of Sensitive Information in a Cookie
CWE-311: Missing Encryption of Sensitive Data
OWASP: Secure Flag
Rapid7: Missing Secure Flag From SSL Cookie

It is recommended to specify the HttpOnly flag to new cookie.

Risk

Cookies that doesn’t have the flag set are available to JavaScript running on the same domain. When a user is the target of a “Cross-Site Scripting”, the attacker would benefit greatly from getting the session id.

Vulnerable Code

The httpOnlyCookies value is explicitly set to false or the default is left.

<httpCookies httpOnlyCookies="false" [..] />
// default is left
var cookie = new HttpCookie("test");
// or explicitly set to false
var cookie = new HttpCookie("test");
cookie.HttpOnly = false;

Solution

<httpCookies httpOnlyCookies="true" [..] />
var cookie = new HttpCookie("test");
cookie.Secure = true;
cookie.HttpOnly = true; //Add this flag

References

Coding Horror blog: Protecting Your Cookies: HttpOnly
OWASP: HttpOnly
Rapid7: Missing HttpOnly Flag From Cookie

View State

SCS0023 - View State Not Encrypted

The viewStateEncryptionMode is not set to Always in configuration file.

Risk

Web Forms controls use hidden base64 encoded fields to store state information. If sensitive information is stored there it may be leaked to the client side.

Vulnerable Code

The default value is Auto:

<system.web>
   ...
   <pages [..] viewStateEncryptionMode="Auto" [..]/>
   ...
</system.web>

or

<system.web>
   ...
   <pages [..] viewStateEncryptionMode="Never" [..]/>
   ...
</system.web>

Solution

Explicitly set to Always and encrypt with with the .NET machine key:

<system.web>
   ...
   <pages [..] viewStateEncryptionMode="Always" [..]/>
   ...
</system.web>

References

MSDN: pages Element (ASP.NET Settings Schema)
MSDN: ViewStateEncryptionMode Property
MSDN: machineKey Element (ASP.NET Settings Schema)

SCS0024 - View State MAC Disabled

The enableViewStateMac is disabled in configuration file. (This feature cannot be disabled starting .NET 4.5.1)

Risk

The view state could be altered by an attacker.

Vulnerable Code

<system.web>
   ...
   <pages [..] enableViewStateMac="false" [..]/>
   ...
</system.web>

Solution

The default value is secure - true. Or set it explicitly:

<system.web>
   ...
   <pages [..] enableViewStateMac="true" [..]/>
   ...
</system.web>

References

MSDN: pages Element (ASP.NET Settings Schema)

Request Validation

SCS0017 - Request Validation Disabled (Attribute)

Request validation is disabled. Request validation allows the filtering of some XSS patterns submitted to the application.

Risk

XSS

Vulnerable Code

public class TestController
{
    [HttpPost]
    [ValidateInput(false)]
    public ActionResult ControllerMethod(string input) {
        return f(input);
    }
}

Solution

Although it performs blacklisting (that is worse than whitelisting by definition) and you should not rely solely on it for XSS protection, it provides a first line of defense for your application. Do not disable the validation:

public class TestController
{
    [HttpPost]
    public ActionResult ControllerMethod(string input) {
        return f(input);
    }
}

Always user proper encoder (Html, Url, etc.) before displaying or using user supplied data (even if it is loaded from database).

References

MSDN: Request Validation in ASP.NET
OWASP: ASP.NET Request Validation
See XSS references.

SCS0021 - Request Validation Disabled (Configuration File)

The validateRequest which provides additional protection against XSS is disabled in configuration file.

Risk

XSS

Vulnerable Code

<system.web>
   ...
   <pages [..] validateRequest="false" [..]/>
   ...
</system.web>

Solution

Although it performs blacklisting (that is worse than whitelisting by definition) and you should not rely solely on it for XSS protection, it provides a first line of defense for your application. Do not disable the validation: The default value is true. Or set it explicitly:

<system.web>
   ...
   <pages [..] validateRequest="true" [..]/>
   ...
</system.web>

References

MSDN: pages Element (ASP.NET Settings Schema)
MSDN: Request Validation in ASP.NET
OWASP: ASP.NET Request Validation
See XSS references.

SCS0030 - Request validation is enabled only for pages (Configuration File)

The requestValidationMode which provides additional protection against XSS is enabled only for pages, not for all HTTP requests in configuration file.

Risk

XSS

Vulnerable Code

<system.web>
   ...
   <httpRuntime [..] requestValidationMode="2.0" [..]/>
   ...
</system.web>

Solution

<system.web>
   ...
   <httpRuntime [..] requestValidationMode="4.5" [..]/>
   ...
</system.web>

References

MSDN: pages Element (ASP.NET Settings Schema)
MSDN: Request Validation in ASP.NET
OWASP: ASP.NET Request Validation
MSDN: RequestValidationMode Property
See XSS references.

Password Management

SCS0015 - Hardcoded Password

The password configuration to this API appears to be hardcoded.

Risk

If hard-coded passwords are used, it is almost certain that malicious users will gain access through the account in question.

Vulnerable Code

config.setPassword("NotSoSecr3tP@ssword");

Solution

It is recommended to externalize configuration such as password to avoid leakage of secret information. The source code or its binary form is more likely to be accessible by an attacker than a production configuration. To be managed safely, passwords and secret keys should be stored encrypted in separate configuration files. The certificate for decryption should be installed as non-exportable on the server machine.

Configuration file :

<configuration>
    <appSettings>
    <add key="api_password" value="b3e521073ca276dc2b7caf6247b6ddc72d5e2d2d" />
  </appSettings>
</configuration>

Code:

string apiPassword = ConfigurationManager.AppSettings["api_password"];
config.setPassword(apiPassword);

References

CWE-259: Use of Hard-coded Password

SCS0034 - Password RequiredLength Not Set

The RequiredLength property must be set with a minimum value of 8.

Risk

Weak password can be guessed or brute-forced.

Vulnerable Code

ASP.NET Identity default is 6.

PasswordValidator pwdv = new PasswordValidator();

Solution

See the solution for Password Complexity

References

MSDN: ASP.NET Identity PasswordValidator Class

SCS0032 - Password RequiredLength Too Small

The minimal length of a password is recommended to be set at least to 8.

Risk

Weak password can be guessed or brute-forced.

Vulnerable Code

PasswordValidator pwdv = new PasswordValidator
{
    RequiredLength = 6,
};

Solution

See the solution for Password Complexity

References

MSDN: ASP.NET Identity PasswordValidator Class

SCS0033 - Password Complexity

PasswordValidator should have at least two requirements for better security (RequiredLength, RequireDigit, RequireLowercase, RequireUppercase and/or RequireNonLetterOrDigit).

Risk

Weak password can be guessed or brute-forced.

Vulnerable Code

PasswordValidator pwdv = new PasswordValidator
{
    RequiredLength = 6,
};

Solution

PasswordValidator pwdv = new PasswordValidator
{
    RequiredLength = 8,
    RequireNonLetterOrDigit = true,
    RequireDigit = true,
    RequireLowercase = true,
    RequireUppercase = true,
};

References

MSDN: ASP.NET Identity PasswordValidator Class

Other

SCS0016 - Cross-Site Request Forgery (CSRF)

Anti-forgery token is missing.

Risk

An attacker could send a link to the victim. By visiting the malicious link, a web page would trigger a POST request (because it is a blind attack - the attacker doesn’t see a response from triggered request and has no use from GET request and GET requests should not change a state on the server by definition) to the website. The victim would not be able to acknowledge that an action is made in the background, but his cookie would be automatically submitted if he is authenticated to the website. This attack does not require special interaction other than visiting a website.

Vulnerable Code

public class TestController
{
    [HttpPost]
    public ActionResult ControllerMethod(string input)
    {
        //Do an action in the context of the logged in user
    }
}

Solution

In your view:

@Html.AntiForgeryToken()

In your controller:

public class TestController
{
    [HttpPost]
    [ValidateAntiForgeryToken] //Annotation added
    public ActionResult ControllerMethod(string input)
    {
        //Do an action in the context of the logged in user
    }
}

References

OWASP: Cross-Site Request Forgery
OWASP: CSRF Prevention Cheat Sheet

SCS0019 - OutputCache Conflict

Caching conflicts with authorization.

Risk

Having the annotation [OutputCache] will disable the annotation [Authorize] for the requests following the first one.

Vulnerable Code

[Authorize]
public class AdminController : Controller
{
    [OutputCache]
    public ActionResult Index()
    {
        return View();
    }
}

Solution

Remove the caching:

[Authorize]
public class AdminController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

References

Improving Performance with Output Caching

SCS0022 - Event Validation Disabled

The enableEventValidation is disabled in configuration file.

Risk

This feature reduces the risk of unauthorized or malicious post-back requests and callbacks. It is strongly recommended that you do not disable event validation. When the EnableEventValidation property is set to true, ASP.NET validates that a control event originated from the user interface that was rendered by that control.

Vulnerable Code

<system.web>
   ...
   <pages [..] enableEventValidation="false" [..]/>
   ...
</system.web>

Solution

The default value is secure - true. Or set it explicitly:

<system.web>
   ...
   <pages [..] enableEventValidation="true" [..]/>
   ...
</system.web>

References

MSDN: pages Element (ASP.NET Settings Schema)
MSDN: Page.EnableEventValidation Property

SCS0027 - Open Redirect

The dynamic value passed to the Redirect should be validated.

Risk

Your site may be used in phishing attacks. An attacker may craft a trustworthy looking link to your site redirecting a victim to a similar looking malicious site: https://www.yourdomain.com/loginpostback?redir=https://www.urdomain.com/login

Vulnerable Code

[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        if (MembershipService.ValidateUser(model.UserName, model.Password))
        {
            FormsService.SignIn(model.UserName, model.RememberMe);
            if (!String.IsNullOrEmpty(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("Index", "Home");
            }
        }
        else
        {
            ModelState.AddModelError("", "The user name or password provided is incorrect.");
        }
    }
 
    // If we got this far, something failed, redisplay form
    return View(model);
}

Solution

[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        if (MembershipService.ValidateUser(model.UserName, model.Password))
        {
            FormsService.SignIn(model.UserName, model.RememberMe);
            if (Url.IsLocalUrl(returnUrl)) // Make sure the url is relative, not absolute path
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("Index", "Home");
            }
        }
        else
        {
            ModelState.AddModelError("", "The user name or password provided is incorrect.");
        }
    }
 
    // If we got this far, something failed, redisplay form
    return View(model);
}

References

Microsoft: Preventing Open Redirection Attacks (C#)
OWASP: Unvalidated Redirects and Forwards Cheat Sheet
Hacksplaining: preventing malicious redirects

SCS0028 - Insecure Deserialization

Untrusted data passed for deserialization.

Risk

Arbitrary code execution, full application compromise or denial of service. An attacker may pass specially crafted serialized .NET object of specific class that will execute malicious code during the construction of the object.

Vulnerable Code

private void ConvertData(string json)
{
    var mySerializer = new JavaScriptSerializer(new SimpleTypeResolver());
    Object mything = mySerializer.Deserialize(json, typeof(SomeDataClass)/* the type doesn't matter */);
}

Solution

There is no simple fix. Do not deserialize untrusted data: user input, cookies or data that crosses trust boundaries.

In case it is unavoidable:
1) If serialization is done on the server side, then crosses trust boundary, but is not modified and is returned back (like cookie for example) - use signed cryptography (HMAC for instance) to ensure it wasn’t tampered.
2) Do not get the type to deserialize into from untrusted source: the serialized stream itself or other untrusted parameter. BinaryFormatter for example reads type information from serialized stream itself and can’t be used with untrusted streams:

// DO NOT DO THIS!
var thing = (MyType)new BinaryFormatter().Deserialize(untrustedStream);

JavaScriptSerializer for instance without a JavaScriptTypeResolver is safe because it doesn’t resolve types at all:

private void ConvertData(string json)
{
    var mySerializer = new JavaScriptSerializer(/* no resolver here */);
    Object mything = mySerializer.Deserialize(json, typeof(SomeDataClass));
}

Pass the expected type (may be hardcoded) to the deserialization library. Some libraries like Json.Net, DataContractJsonSerializer and FSPickler validate expected object graph before deserialization. However the check is not bulletproof if the expected type contains field or property of System.Object type somewhere nested in hierarchy.

// Json.net will inspect if the serialized data is the Expected type
var data = JsonConvert.DeserializeObject<Expected>(json, new
JsonSerializerSettings
{
    // Type information is not used, only simple types like int, string, double, etc. will be resolved
    TypeNameHandling = TypeNameHandling.None
});
// DO NOT DO THIS! The cast to MyType happens too late, when malicious code was already executed
var thing = (MyType)new BinaryFormatter().Deserialize(untrustedStream);

3) If the library supports implement a callback that verifies if the object and its properties are of expected type (don’t blacklist, use whitelist!):

class LimitedBinder : SerializationBinder
{
    List<Type> allowedTypes = new List<Type>()
    {
        typeof(Exception),
        typeof(List<Exception>),
    };

    public override Type BindToType(string assemblyName, string typeName)
    {
        var type = Type.GetType(String.Format("{0}, {1}", typeName, assemblyName), true);
        foreach(Type allowedType in allowedTypes)
        {
            if(type == allowedType)
                return allowedType;
        }

        // Don’t return null for unexpected types –
        // this makes some serializers fall back to a default binder, allowing exploits.
        throw new Exception("Unexpected serialized type");
    }
}

var formatter = new BinaryFormatter() { Binder = new LimitedBinder () };
var data = (List<Exception>)formatter.Deserialize (fs);

Determining which types are safe is quite difficult, and this approach is not recommended unless necessary. There are many types that might allow non Remote Code Execution exploits if they are deserialized from untrusted data. Denial of service is especially common. As an example, the System.Collections.HashTable class is not safe to deserialize from an untrusted stream – the stream can specify the size of the internal “bucket” array and cause an out of memory condition.

4) Serialize simple Data Transfer Objects (DTO) only. Do not serialize/deserialize type information. For example, use only TypeNameHandling.None (the default) in Json.net:

class DataForStorage
{
    public string Id;
    public int    Count;
}

var data = JsonConvert.SerializeObject<DataForStorage>(json, new
JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.None
});

will produce the following JSON without type information that is perfectly fine to deserialize back:

{
  "Id": null,
  "Count": 0
}

References

BlackHat USA 2017: Friday the 13th: JSON Attacks
BlueHat v17: Dangerous Contents - Securing .Net Deserialization
BlackHat USA 2012: Are you my type?
OWASP: Deserialization of untrusted data
Deserialization payload generator for a variety of .NET formatters
.NET Deserialization Passive Scanner

Release Notes

3.0.0

This is a major release that introduces configurable taint sources, sanitizers and validators. Configuration file schema version has changed to 2.0, so if you had custom config settings, you’ll need to adjust to the schema and bump your file name from config-1.0.yml to config-2.0.yml or change from Version: 1.0 to Version: 2.0 if it was added to a project.
With the introduction of taint sources and taint entry points warning are shown only for the tainted data. Unknowns are reported only in the Audit Mode.
Multiple improvements and fixes were done to Taint, Anti-CSRF token, XSS, SQL injection, Path traversal, XPath injection, Certificate validation analyzers.
New LDAP injection detection was added.
An issue was fixed that could surface as Session Terminated unexpectedly. Disabling 'Security Code Scan' might help prevent....

I would like to thank all contributors to this and previous releases. Also to everyone who has reported issues or feature requests.

2.8.0

Important: This release targets full .NET framework and may not run on Unix machines. Although as tested it runs fine in microsoft/dotnet 2.1 docker container on Linux, still for Unix based Continuous Integration builds it is better to use SecurityCodeScan.VS2017 NuGet package, that targets netstandard.

Added external configuration files: per user account and per project. It allows you to customize settings from built-in configuration or add your specific Sinks and Behaviors.

⚠️Note: Configuration schema has changed in version 3.0.0 please refer to the documentation above for examples.

Audit Mode setting (Off by default) was introduced for those interested in warnings with more false positives.

2.7.1

Couple of issues related to VB.NET fixed:

  • VB.NET projects were not analyzed when using the analyzer from NuGet.
  • ‘Could not load file or assembly ‘Microsoft.CodeAnalysis.VisualBasic, Version=1.0.0.0…’ when building C# .NET Core projects from command line with dotnet.exe

2.7.0

Insecure deserialization analyzers for multiple libraries and formatters:

Added warning for the usage of AllowHtml attribute.
Different input validation analyzer and CSRF analyzer improvements.

2.6.1

Exceptions analyzing VB.NET projects fixed.

2.6.0

XXE analysis expanded. More patterns to detect Open Redirect and Path Traversal. Weak hash analyzer fixes. Added request validation aspx analyzer. False positives reduced in hardcoded password manager.

Web.config analysis:

  • The feature was broken. See how to enable.
  • Added detection of request validation mode.
  • Diagnostic messages improved.

Taint improvements:

  • Area expanded.
  • Taint diagnostic messages include which passed parameter is untrusted.

2.5.0

Various improvements were made to taint analysis. The analysis was extended from local variables into member variables. False positive fixes in:

  • XSS analyzer.
  • Weak hash analyzer. Added more patterns.
  • Path traversal. Also added more patterns.

New features:

  • Open redirect detection.