ASP.NET Core 5 Secure Coding Cookbook

By Roman Canlas
    Advance your knowledge in tech with a Packt subscription

  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Chapter 2: Injection Flaws

About this book

ASP.NET Core developers are often presented with security test results showing the vulnerabilities found in their web apps. While the report may provide some high-level fix suggestions, it does not specify the exact steps that you need to take to resolve or fix weaknesses discovered by these tests.

In ASP.NET Secure Coding Cookbook, you’ll start by learning the fundamental concepts of secure coding and then gradually progress to identifying common web app vulnerabilities in code. As you progress, you’ll cover recipes for fixing security misconfigurations in ASP.NET Core web apps. The book further demonstrates how you can resolve different types of Cross-Site Scripting. A dedicated section also takes you through fixing miscellaneous vulnerabilities that are no longer in the OWASP Top 10 list. This book features a recipe-style format, with each recipe containing sample unsecure code that presents the problem and corresponding solutions to eliminate the security bug. You’ll be able to follow along with each step of the exercise and use the accompanying sample ASP.NET Core solution to practice writing secure code.

By the end of this book, you’ll be able to identify unsecure code causing different security flaws in ASP.NET Core web apps and you’ll have gained hands-on experience in removing vulnerabilities and security defects from your code.

Publication date:
July 2021
Publisher
Packt
Pages
324
ISBN
9781801071567

 

Chapter 2: Injection Flaws

Injection flaws in code can have the most devastating effects on ASP.NET Core web applications. The lack of validation and sanitization of untrusted input allows this vulnerability to be exploited, leading to the execution of arbitrary OS commands, authentication bypass, unexpected data manipulation, and content. At worse, it can disclose sensitive information and lead to an eventual data breach.

This chapter introduces you to various injection flaws and explains how you can remediate this security defect in code.

In this chapter, we're going to cover the following recipes:

  • Fixing SQL injection with Entity Framework
  • Fixing SQL injection in ADO.NET
  • Fixing NoSQL injection
  • Fixing command injection
  • Fixing LDAP injection
  • Fixing XPath injection

By the end of this chapter, you will learn how to properly write secure code and remove security bugs that will prevent injection attacks.

 

Technical requirements

This book was written and designed to use with Visual Studio Code, Git, and .NET 5.0. Code examples in recipes are presented in ASP.NET Core Razor pages. The code exercises have been tested in a Windows environment but should work in Linux-based operating systems with some minor differences. The sample solution also uses SQLite as the database engine for a more simplified setup. MongoDB is required to be able to test the recipes for NoSQL injection. A tool that can open and browse SQLite databases such as the DB Browser for SQLite is also required. You can find the code for this chapter at https://github.com/PacktPublishing/ASP.NET-Core-Secure-Coding-Cookbook/tree/main/Chapter02.

 

What is SQL injection?

ASP.NET Core web applications interact with databases to store data and records. We use Standard Query Language (SQL) to communicate with a Database Management System (DBMS) to access and manage data. These queries are composed utilizing the programming language, platform, or library of choice, but the code to generate these queries can be written insecurely.

A developer can write code that produces a dynamic SQL by concatenating strings along with untrusted user input. Without proper countermeasures, a malicious actor can inject suspicious commands into the input string, thereby changing the query's intent, or execute an arbitrary SQL. Identified as SQL Injection, this vulnerability in code still prevails in web applications today.

 

Fixing SQL injection with Entity Framework

Entity Framework Core (EF Core) is a popular Object-Relational Mapping (ORM) framework of choice for ASP.NET Core developers. This framework is cross-platform, and its ease of use allows developers to instantly model and query data into objects. Nevertheless, ORM frameworks such as EF Core can still be misused.

In this recipe, we will execute a simple SQL injection to exploit the vulnerability, locate the security bug, and remediate the risk by rewriting a more secure version of the code.

Getting ready

Using Visual Studio Code, open the sample Online Banking app folder at \Chapter02\sql-injection\razor\ef\before\OnlineBankingApp\.

Testing a SQL injection

Here are the steps:

  1. Navigate to Terminal | New Terminal in the menu or simply press Ctrl + Shift + ' in Visual Studio Code.
  2. Type the following command in the terminal to build and run the sample app:
    dotnet run
  3. Open a browser and go to http://localhost:5000/FundTransfers.
  4. The browser will display the web page for searching fund transfers using keywords in the Filter By Notes field, as shown in the following screenshot:

    Ystem.

    Figure 2.1 – Fund Transfers page

    Figure 2.1 – Fund Transfers page

  5. In the Filter By Notes textbox, type C and then hit the Search button.
  6. The web page will now return one entry finding one match for the Contingency Fund note:
    Figure 2.2 – Fund Transfers search result

    Figure 2.2 – Fund Transfers search result

  7. Now try entering the SQL injection payload: %';create table tbl1(one varchar(10), two smallint);Select * from Customers where id like '1.
  8. Notice that no error was thrown on the web page:
    Figure 2.3 – Successful SQL injection

    Figure 2.3 – Successful SQL injection

  9. To confirm that the SQL injection payload was executed successfully, open the \Chapter02\sql-injection\razor\ef\before\OnlineBankingApp\OnlineBank.db SQLite database using the DB Browser for SQLite tool:
Figure 2.4 – OnlineBank.db in DB Browser for SQLite

Figure 2.4 – OnlineBank.db in DB Browser for SQLite

Notice the newly created tbl1 SQLite table

Now, let's see how to identify the SQL injection vulnerability in code that uses EF and mitigate the preceding issue by fixing this security flaw and applying a countermeasure.

How to do it…

Let's take a look at the steps for this recipe:

  1. Launch Visual Studio Code and open the starting exercise folder by typing the following command:
    code .
  2. Navigate to Terminal | New Terminal in the menu or simply press Ctrl + Shift + ' in Visual Studio Code.
  3. Type the following command in the terminal to build the sample app to confirm that there are no compilation errors:
    dotnet build
  4. Open the Pages/FundTransfers/Index.cshtml.cs file and locate the vulnerable part of the OnGetAsync method, where a dynamic query is composed:
    public async Task OnGetAsync()
    {
        var fundtransfer = from f in _context.FundTransfer
            select f;
        if (!string.IsNullOrEmpty(SearchString))
        {
            fundtransfer = _context.FundTransfer.            FromSqlRaw("Select * from FundTransfer                 Where Note Like'%" + SearchString +                    "%'");
        }
        FundTransfer = await fundtransfer.ToListAsync();
    }
  5. To remediate the SQL injection vulnerability, let's start by adding a reference to System.
  6. Next, change the preceding highlighted code into the following by using the FromSqlInterpolated method:
    fundtransfer = _context.FundTransfer.FromSqlInterpolated($"Select * from FundTransfer Where Note Like {"%" + SearchString + "%"}");
  7. The FromSqlInterpolated method will create a LINQ query from the interpolated string supplied.

The interpolated parameter, SearchString, will then be converted into a DbParameter object, making the code safe from SQL injection.

How it works…

The Entity Framework allows you to execute raw SQL queries using the FromSQLRaw method. However, this method is dangerous as you can supply the argument with concatenated strings with the user input, SearchString:

_context.FundTransfer.FromSqlRaw("Select * from FundTransfer Where Note Like'%" + SearchString + "%'");

Using the payload used in the SQL injection test, imagine replacing the SearchString value with the malicious string %';create table tbl1(one varchar(10), two smallint);Select * from Customers where id like '1.

With FromSqlRaw blindly concatenating the injected input, the SQL statement now reads as follows:

Select * from FundTransfer Where Note Like'%%';create table tbl1(one varchar(10), two smallint);Select * from Customers where id like '1 %'

This is a perfectly valid series of SQL statements, except that it has a dangerous command that creates a new table or, in other cases or DBMS, could turn into a remote code execution by spawning a shell.

This way of forming SQL statements is regarded as bad coding practice. To write better and secure code, use methods such as FromSqlInterpolated to help compose harmless SQL statements with parameterized values.

There's more…

Parameterization is a proven secure coding practice that will prevent SQL injection. Another way to rewrite the code in this recipe is to use the DbParameter classes.

Introduce an instance of SqLiteParameter (which is derived from DbParameter) into the code as follows:

var searchParameter =     new SqliteParameter("searchString", SearchString);
fundtransfer = _context.FundTransfer     .FromSqlRaw("Select * from FundTransfer         Where Note Like'%@searchString%'",searchParameter);

Whitelisting is also a useful technique as a means to filter user input. You will have already seen this approach discussed in detail in Chapter 1, Secure Coding Fundamentals. Whitelisting will cause ASP.NET Core web applications to only process data that is in an expected format, but this technique is not as effective as using prepared statements or parameterized queries.

 

Fixing SQL injection in ADO.NET

ADO.NET is a data provider platform that is integral to the .NET Framework. Since the advent of the .NET Framework, ADO.NET has been the component used to query and manipulate data in the database. ADO.NET can be used in developing data-driven ASP.NET Core web applications, but similar to any data providers, developers may write insecure code when using any of the System.Data.* or Microsoft.Data.* classes.

In this recipe, we will identify the SQL injection vulnerability in the code when using the ADO.NET and mitigate the issue by fixing this security flaw and applying a countermeasure.

Getting ready

Using Visual Studio Code, open the sample Online Banking app folder at \Chapter02\sql-injection\razor\ado.net\before\OnlineBankingApp\.

How to do it…

Let's take a look at the steps for this recipe:

  1. Launch Visual Studio Code and open the starting exercise folder by typing the following command:
    code .
  2. Navigate to Terminal | New Terminal in the menu or simply press Ctrl + Shift + ' in Visual Studio Code.
  3. Type the following command in the terminal to build the sample app to confirm that there are no compilation errors:
    dotnet build
  4. Open the Data/FundTransferDAL.cs file, which is the class that represents the data access layer of the sample application and locate the vulnerable part of the GetFundTransfers method where the user-controlled input is passed into the search parameter:
    public IEnumerable<FundTransfer> GetFundTransfers(string   search)
    {
        List<FundTransfer> fundTransfers =         new List<FundTransfer>();
        
        using (SqliteConnection con =         new SqliteConnection(connectionString))  
        {
            SqliteCommand cmd =             new SqliteCommand("Select *                 fromFundTransfer where Note like '%"                     + search + "%'", con);
            cmd.CommandType = CommandType.Text;
      
            con.Open();  
            SqliteDataReader rdr = cmd.ExecuteReader(); 
  5. The preceding highlighted code is where the query is composed, and the search concatenated to form a SQL query.
  6. To remediate the SQL injection vulnerability, change the preceding highlighted code:
    public IEnumerable<FundTransfer> GetFundTransfers(string   search)
    {
        List<FundTransfer> fundTransfers =         new List<FundTransfer>();
      
        using (SqliteConnection con =         new SqliteConnection(connectionString))
        {
            SqliteCommand cmd =             new SqliteCommand("Select * from                 FundTransfer where Note like '%" +                     @search + "%'", con);
            cmd.CommandType = CommandType.Text;
            cmd.Parameters.AddWithValue("@search",search);
            con.Open();
            SqliteDataReader rdr = cmd.ExecuteReader();

Using the parameterization approach, we have converted the search string into a SQL parameter and passed the value into SqlLiteParameterCollection.

How it works…

The SqlLiteCommand instance is blindly passed with a raw SQL concatenated user input. This supplied string is a source for a SQL injection. The input string search is not validated and unsanitized, letting an adversary insert an arbitrary SQL command or modify the query's intention:

SqliteCommand cmd = new SqliteCommand("Select * from FundTransfer where Note like '%" + search + "%'", con);  

You can rewrite the vulnerable ADO.NET code and make it secure by using query parameters. The AddWithValue method from SqliteParametersCollection of the SQliteCommand object allows you to add query parameters and safely pass values into the query:

cmd.Parameters.AddWithValue("@search", search);  

Changing the search string into a placeholder makes the query parameterized:

SqliteCommand cmd = new SqliteCommand("Select * from FundTransfer where Note like '%" + @search + "%'", con);  

When your ASP.NET Core web application executes the preceding lines of code, the query is now parameterized, safely passing the search value, and preventing malicious actors from altering the SQL.

There's more…

This recipe uses SQLite as the DBMS for the sample solution, but if you were to use Microsoft SQL Server, another option is to convert the query into a stored procedure and use it with DB parameters. You would then have to utilize the SQLCommand object and set the CommandType property to System.Data.CommandType.StoredProcedure, allowing the execution of parameterized stored procedures from code. These classes are available under the System.Data.SqlClient namespace and in the new Microsoft.Data.SqlClient package.

Here's a sample code snippet:

SqlCommand cmd = new     SqlCommand("sp_SearchFundTransfer",con);  
cmd.CommandType = CommandType.StoredProcedure;  
cmd.Parameters.AddWithValue("@search", search);  

To write better and secure code, use the built-in support for database features such as prepared statements or parameterized queries made possible by its data provider frameworks.

 

Fixing NoSQL injection

NoSQL databases are a different type of database in which non-relational and semi-structured data is stored. There are many kinds of NoSQL databases to name, such as Cassandra, Redis, DynamoDB, and MongoDB, each with its own query language. Although distinct from one another, these queries are also prone to injection attacks.

In this recipe, we will identify the NoSQL injection vulnerability in code that is using MongoDB as the backend and fix the problem by applying several countermeasures.

Getting ready

Using Visual Studio Code, open the sample Online Banking app folder at Chapter02\nosql-injection\before\OnlineBankingApp.

How to do it…

Let's take a look at the steps for this recipe:

  1. Launch Visual Studio Code and open the starting exercise folder by typing the following command:
    code .
  2. Navigate to Terminal | New Terminal in the menu or simply press Ctrl + Shift + ' in Visual Studio Code.
  3. Type the following command in the terminal to build the sample app to confirm that there are no compilation errors:
    dotnet build
  4. Open the Services/PayeeService.cs file and locate the vulnerable part of the code in the Get(string name) method:
    public List<Payee> Get(string name) {
        var filter = "{$where: \"function()         {return this.Name == '" + name + "'}\"}";
        return payees.Find(filter).ToList();
    }
  5. To remediate the NoSQL injection vulnerability, change the preceding highlighted code:
    public List<Payee> Get(string name) {
        return payees.Find(payee => payee.Name ==         name).ToList();
    }  

The filter passed into the Find method is now replaced with a Lambda expression, a much more secure way of searching for a payee by name.

How it works…

The Get method has a string parameter that can be supplied with a non-sanitized or validated value. This value can alter the MongoDB filter composed with it, making the NoSQL database perform an unintended behavior.

The name parameter can be appended with an expression that would evaluate the query into a logical result different from what the query was expected to perform. A JavaScript clause can also be inserted into a query that can terminate the statement and add a new block of arbitrary code.

By way of some general advice, avoid using the $where operator. Simply apply a C# Lambda expression as a filter to prevent any injectable JSON or JavaScript expression.

There's more…

Suppose the preceding options are not possible and it is necessary to use the $where clause, you must then JavaScript-encode the input. Use the JavaScriptEncoder class from the System.Text.Encodings.Web namespace to encode the value being passed into the parameter:

  1. First, modify the PayeeService.cs file to add a reference to the Encoder namespace:
    using System.Text.Encodings.Web;
  2. Next, define a property for JavaScriptEncoder:
    private readonly JavaScriptEncoder _jsEncoder;
  3. Change the PayeeService constructor and add a new parameter to inject JavaScriptEncoder:
    public PayeeService(IOnlineBankDatabaseSettings settings,JavaScriptEncoder jsEncoder)
  4. Lastly, encode the name parameter using the Encode function of JavaScriptEncoder:
    var filter = "{$where: \"function() {return this.Name == '" + _jsEncoder.Encode(name) + "'}\"}";

If a malicious input was passed into the name parameter and was escaped by the Encode method, the C# MongoDB driver will throw an exception if the escaped value could not be interpreted as a valid JavaScript expression.

To prevent NoSQL injections, developers must avoid building dynamic queries using string concatenation. NoSQL databases offer ways to query and process data, but you must be aware of potential security implications a feature might bring into the ASP.NET Core web application.

 

Fixing command injection

Web applications such as the ones developed with ASP.NET Core have a plethora of components and libraries that enable them to execute OS commands in the host. If not written securely, the code that composes and runs these commands can likely expose the ASP.NET Core web application to command injection exploitation. Shell commands can be executed unexpectedly if this security flaw in code is not prevented.

In this recipe, we will identify the command injection vulnerability in code and fix the security vulnerability.

Getting ready

Using Visual Studio Code, open the sample Online Banking app folder at Chapter02\command-injection\before\OnlineBankingApp.

Testing command injection

Here are the steps:

  1. Navigate to Terminal | New Terminal in the menu or simply press Ctrl + Shift + ' in Visual Studio Code.
  2. Type the following command in the terminal to build and run the sample app:
    dotnet run
  3. Open a browser and go to http://localhost:5000/Backups/Create.
  4. The browser will display the web page for initiating database backup, as shown in the following screenshot:
    Figure 2.5 – Backup page

    Figure 2.5 – Backup page

  5. Enter this command injection payload, backup & calc, in the Backup Name field, and hit the Create button.
  6. Notice that the page redirected to the list of backup pages and the backup was created. However, the calculator app has appeared:
Figure 2.6 – Successful command injection

Figure 2.6 – Successful command injection

If this security bug is not handled, this problem could also expose the underlying hosts to Remote Code Execution (RCE).

How to do it…

Let's take a look at the steps for this recipe:

  1. Launch Visual Studio Code and open the starting exercise folder by typing the following command:
    code .
  2. Navigate to Terminal | New Terminal in the menu or simply press Ctrl + Shift + ' in Visual Studio Code.
  3. Type the following command in the terminal to build the sample app to confirm that there are no compilation errors:
    dotnet build
  4. Open the Services/BackupService.cs file and locate the vulnerable part of the code in the BackupDB(string backupname) method:
    public async Task BackupDB(string backupname)
    {
        using (Process p = new Process())
        {
            string source =             Environment.CurrentDirectory +                 "\\OnlineBank.db";
            string destination =            Environment.CurrentDirectory +                "\\backups\\" + backupname;
            p.StartInfo.Arguments =             " /c copy " + source + " " + destination;
            p.StartInfo.FileName = "cmd";
            p.StartInfo.CreateNoWindow = true;
    ...code removed for brevity
  5. To remediate the command injection vulnerability, add a new method that utilizes the built-in file copying function:
    public async Task FileCopyAsync(string sourceFileName,    string destinationFileName,     int bufferSize = 0x1000,     CancellationToken cancellationToken =         default(CancellationToken))
    {
        using (var sourceFile =         File.OpenRead(sourceFileName))
        {
            using (var destinationFile =            File.OpenWrite(destinationFileName))
            {
                await             sourceFile.CopyToAsync(destinationFile,                bufferSize, cancellationToken);
            }
        }
    }      
  6. Rewrite the entire body of the BackupDB method and use the newly created method:
    public async Task BackupDB(string backupname)
    {
        string source =         Environment.CurrentDirectory +             "\\OnlineBank.db";
        string destination =         Environment.CurrentDirectory + "\\backups\\"             + backupname;
        await FileCopyAsync(source, destination);
    }

We have refactored the BackUpDB method to use the FileCopyAsync method to limit your code to just perform file copying tasks, thereby preventing the execution of unwanted shell commands.

How it works…

In our sample solution, administrators are allowed to provide a name to create a database backup. The BackUpDB method accepts a user-controlled input parameter of the string type. The input string is used to form a command that will initiate a command shell to have files copied from the source to the destination.

The added input string is expected to have the destination filename, but this can be manipulated to include commands that are more than just a value for an argument. Without validation or sanitization, this could cause the application to execute unwanted shell commands under the web application's identity and authorization.

There's more…

One option of stopping OS command injection is to implement proper validation through the whitelisting technique. This technique can be achieved by using regular expressions (see the Input validation recipe in Chapter 1, Secure Coding Fundamentals):

  1. Add a reference to the System.Text.RegularExpressions namespace:
    using System.Text.RegularExpressions;
  2. Then, use the RegEx class and its IsMatch method to validate the input against a pattern to only accept valid characters:
    public async Task BackupDB(string backupname)
    {
        var regex = new Regex(@"^[a-zA-Z0-9]+$");
        if (!regex.IsMatch(backupname)) return;
        using (Process p = new Process())
        {
            string source =             Environment.CurrentDirectory +                "\\OnlineBank.db";
            string destination =             Environment.CurrentDirectory +                "\\backups\\" + backupname;
            p.StartInfo.Arguments = " /c copy " + source +             " " + destination;
            p.StartInfo.FileName = "cmd";
            p.StartInfo.CreateNoWindow = true;
    // code removed for brevity

We have now added a whitelisting validation with the use of the IsMatch method. The IsMatch method prevents non-alphanumeric characters and input from being processed in the succeeding lines of code, mitigating the risk of command injection.

 

Fixing LDAP injection

The Light Directory Access Protocol (LDAP) is a standard protocol used to access directory services such as Microsoft's Active Directory and Apache Directory. Web applications use LDAP to search the directory server to get users and group information, which also serves as a means of authentication. This retrieval of data from the web application to the LDAP directory server is possible because of the LDAP query language and its filters. Developers write code to compose these queries. Like any other dynamic query construction, this method can open the code to injection, particularly LDAP injection, when the concatenated user-controlled input is not validated or sanitized.

In this recipe, we will identify the LDAP injection vulnerability in code and fix the security vulnerability.

Getting ready

Using Visual Studio Code, open the sample Online Banking app folder at \Chapter02\ldap-injection\before\OnlineBankingApp\.

How to do it…

Let's take a look at the steps for this recipe:

  1. Launch Visual Studio Code and open the starting exercise folder by typing the following command:
    code .
  2. Navigate to Terminal | New Terminal in the menu or simply press Ctrl + Shift + ' in Visual Studio Code.
  3. Type the following command in the terminal to build the sample app to confirm that there are no compilation errors:
    dotnet build
  4. Open the Services/LdapDirectoryService.cs file and locate the vulnerable part of the code in the Search(string userName) method:
    public User Search(string userName)
    {
        using (DirectoryEntry entry =         new DirectoryEntry(config.Path))
        {
            entry.AuthenticationType =            AuthenticationTypes.Anonymous;
            using (DirectorySearcher searcher =             new DirectorySearcher(entry))
            {
                searcher.Filter = "(&(" +                UserNameAttribute + "="                     + userName + "))";
                searcher.PropertiesToLoad.Add                 (EmailAttribute);
                searcher.PropertiesToLoad.Add                 (UserNameAttribute);
                var result = searcher.FindOne();
    // code removed for brevity
  5. To fix the LDAP injection vulnerability, refactor the code to include a whitelist validation of the userName parameter:
    public User Search(string userName)
    {
        if (Regex.IsMatch(userName, "^[a-zA-Z][a-zA-Z0-        9]*$")){
            using (DirectoryEntry entry =             new DirectoryEntry(config.Path))
            {
                entry.AuthenticationType =                AuthenticationTypes.Anonymous;
                using (DirectorySearcher searcher =                 new DirectorySearcher(entry))
                {
                    searcher.Filter = "(&(" +                     UserNameAttribute + "=" + userName                         + "))";
                    searcher.PropertiesToLoad.Add                     (EmailAttribute);
                    searcher.PropertiesToLoad.Add                     (UserNameAttribute);
                    var result = searcher.FindOne();
    // code removed for brevity

Reusing the whitelisting technique through the use of regular expressions, we again utilize the IsMatch method to ascertain whether the pattern matches the input. If the input does not match the regular expression, the input is then rejected.

How it works…

In our sample solution, we have a web page that allows an admin user to search for a specific user account using the search bar:

Figure 2.7 – Manage Users page

Figure 2.7 – Manage Users page

Entering a user ID and hitting the Search button will send an LDAP query to the LDAP directory service to search for a user that has the exact user ID:

Figure 2.8 – Search user result

Figure 2.8 – Search user result

Note

The steps on setting up your LDAP directory service are not provided in this book. Suppose you want a working directory server that runs in your local machine to work with the sample solution. In that case, I suggest you install ApacheDS and follow the steps from the Setting up an LDAP server for development/testing using Apache Directory Studio page in the official Crafter CMS documentation: https://docs.craftercms.org/en/3.1/developers/cook-books/how-tos/setting-up-an-ldap-server-for-dev.html.

Change the Ldap entry in appsettings.json if necessary:

"Ldap": {

"Path": "LDAP://localhost:10389/DC=example,DC=com",

"UserDomainName": "example"

},

As the Search method is invoked, an LDAP query is dynamically composed, and a filter is concatenated with the value entered in the search textbox:

searcher.Filter = "(&(" + UserNameAttribute + "=" + userName + "))";

The userName parameter is not sanitized or validated, and a bad actor can exploit this by injecting suspicious filters that could retrieve sensitive information from the LDAP directory server.

To mitigate this risk, we used Regex's IsMatch method to add a whitelist validation approach. The conditional expression will only be equivalent to true if any of the characters in userName are alphanumeric:

public User Search(string userName)
{
    if (Regex.IsMatch(userName, "^[a-zA-Z][a-zA-Z0-9]*$")){
        using (DirectoryEntry entry = new             DirectoryEntry(config.Path))
        {
// code removed for brevity

Include as part of the overall secure coding strategy the implementation of a whitelist input validation to check user-controlled inputs, safeguarding your ASP.NET Core web application from LDAP injection attacks.

 

Fixing XPath injection

Data-driven ASP.NET Core web applications can use XML databases as a means to store information and records. These data types are in XML format, and one way of navigating through the nodes of XML is by XPath.

Developers can, by mistake, dynamically construct XPath queries with untrusted data. This neglect can result in an arbitrary query execution or the retrieval of sensitive data from the XML database.

In this recipe, we will fix the XPath injection vulnerability in code.

Getting ready

Using Visual Studio Code, open the sample Online Banking app folder at \Chapter02\xpath-injection\before\OnlineBankingApp\.

This example uses the following XML data:

<?xml version="1.0" encoding="utf-8"?>
<knowledgebase>
    <knowledge>
        <topic lang="en">Types of Transfers</topic>
        <description lang="en">
            Make transfers from checking and savings to:
            Checking and savings
            Make transfers from line of credit to:
            Checking and savings
        </description>
        <tags>transfers, transferring funds</tags>
        <sensitivity>Public</sensitivity>
    </knowledge>
    <knowledge>
        <topic lang="en">Expedited Withdrawals</topic>
        <description lang="en">
        Expedited withdrawals are available to our         executive account holders.
        You may reach out to Stanley Jobson at         [email protected]
        </description>
      <tags>withdrawals, expedited withdrawals</tags>
      <sensitivity>Confidential</sensitivity>
    </knowledge>
</knowledgebase>

How to do it…

Let's take a look at the steps for this recipe:

  1. Launch Visual Studio Code and open the starting exercise folder by typing the following command:
    code .
  2. Navigate to Terminal | New Terminal in the menu or simply press Ctrl + Shift + ' in Visual Studio Code.
  3. Type the following command in the terminal to build the sample app to confirm that there are no compilation errors:
    dotnet build
  4. Open the Services/KnowledgebaseService.cs file and locate the vulnerable part of the code in the Search method:
    public List<Knowledge> Search(string input)
    {
        List<Knowledge> searchResult = new         List<Knowledge>(); 
        var webRoot = _env.WebRootPath;
        var file = System.IO.Path.Combine(webRoot,        "Knowledgebase.xml");
        
        XmlDocument XmlDoc = new XmlDocument();
        XmlDoc.Load(file);
        
        XPathNavigator nav = XmlDoc.CreateNavigator();
        XPathExpression expr =        nav.Compile(@"//knowledge[tags[contains(text()            ,'" + input + "')] and sensitivity/text()                ='Public']");
        var matchedNodes = nav.Select(expr);
    // code removed for brevity

    An XPath expression is dynamically created by concatenating the user-controlled input. Without any validation or sanitization done on the input parameter, a malicious actor can manipulate the XPath query by injecting malicious string, changing the intent of the whole expression.

  5. To fix this security bug, let's refactor the code and implement input sanitization based on the whitelisting technique. To start, add a reference to both the System and System.Linq namespaces:
    using System;
    using System.Linq;
  6. Add a new method to the KnowledgebaseService class and name it Sanitize:
    private string Sanitize(string input)
    {
        if (string.IsNullOrEmpty(input)) {
            throw new ArgumentNullException("input",             "input cannot be null");
        }
        HashSet<char> whitelist = new HashSet<char>        (@"1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ             abcdefghijklmnopqrstuvwxyz ");
        return string.Concat(input.Where(i =>        whitelist.Contains(i))); ;
    }
  7. Call the new Sanitize method, passing the input parameter to it as an argument. Assign the result to the sanitizedInput variable:
    public List<Knowledge> Search(string input)
    {
        string sanitizedInput = Sanitize(input);
        List<Knowledge> searchResult = new         List<Knowledge>(); 
        var webRoot = _env.WebRootPath;
        var file = System.IO.Path.Combine(webRoot,        "Knowledgebase.xml");
        
        XmlDocument XmlDoc = new XmlDocument();
        XmlDoc.Load(file);
        
        XPathNavigator nav = XmlDoc.CreateNavigator();
        XPathExpression expr =         nav.Compile(@"//knowledge[tags[contains(text()            ,'" + sanitizedInput + "')] and                 sensitivity/text()='Public']");
    // code removed for brevity

The custom Sanitize method will now remove unnecessary and possibly dangerous characters in the input string. The output is now passed into a sanitizedInput variable, making the XPath expression safe from exploitation.

How it works…

As we have learned in Chapter 1, Secure Coding Fundamentals, in the Input sanitization section, input sanitization is a defensive technique that can be practiced to remove suspicious characters in a user-supplied input. This approach will prevent the application from processing unwanted XPath injected into the query.

We have created the new Sanitize method that will serve as our sanitizer. Inside this method is a whitelist of defined characters and a Lambda invoked to remove the characters rejected from userName:

HashSet<char> whitelist = new HashSet<char>(@"1234567890ABCDEFGHI JKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ");
return string.Concat(input.Where(i =>    whitelist.Contains(i))); ;

Searching for a help article with an unacceptable character will not throw an exception, and our sample Online Banking web application will also not process the string:

Figure 2.9 – Searching the knowledge base

Figure 2.9 – Searching the knowledge base

There's more…

An alternative fix is to parameterize the XPath query. We will define a variable that will serve as a placeholder for an argument. This technique allows the data to be separated from code:

XmlDocument XmlDoc = new XmlDocument();
XmlDoc.Load(file);
XPathNavigator nav = XmlDoc.CreateNavigator();
XPathExpression expr =    nav.Compile(@"//knowledge[tags[contains(text(),$input)]        and sensitivity/text()='Public']");
XsltArgumentList varList = new XsltArgumentList();
varList.AddParam("input", string.Empty, input);
CustomContext context = new CustomContext(new NameTable(),    varList);
expr.SetContext(context);
var matchedNodes = nav.Select(expr);
foreach (XPathNavigator node in matchedNodes)
{ 
    searchResult.Add(new Knowledge() {Topic =      node.SelectSingleNode(nav.Compile("topic"))        .Value,Description = node.SelectSingleNode           (nav.Compile("description")).Value}); 
}

In the preceding code, the XPath expression is modified, and the $input variable is now a placeholder for the previously concatenated input value. We also used the XsltArgumentList object to create a list of arguments to include input before passing into the XpathExpression expression's custom context. In this way, the XPath query is parameterized and protected from malicious injection upon execution.

Note

This mitigation requires the creation of a user-defined custom context class that derives from XsltContext. There are other classes required to make this XPath parameterization possible. The class files are included in the sample solution, namely; Services\XPathExtensionFunctions.cs, Services\XPathExtensionVariable.cs, and Services\CustomContext.cs. The whole guide and source for these classes are also available online at the .NET official documentation: https://docs.microsoft.com/en-us/dotnet/standard/data/xml/user-defined-functions-and-variables.

About the Author

  • Roman Canlas

    Roman Canlas is a Senior Application Security Engineer working at a Fortune 500 company where he successfully established its global Application Security program from the ground up. His years of experience as a developer-led him to be an expert in Secure Code reviews and Static Application Security testing, focusing on web technologies.

    Roman held multiple certifications; the GIAC Web Application Penetration Tester (GWAPT), ISC2’s Certified Secure Software Lifecycle Professional (CSSLP), and EC-Council’s Certified Application Security Engineer in .NET (CASE.NET).

    Roman also has a Master’s degree in Information Systems and a Bachelors in Computer Science.

    Browse publications by this author
ASP.NET Core 5 Secure Coding Cookbook
Unlock this book and the full library for $5 a month*
Start now