Catch vulnerability on your own: user annotations for C# code

Unicorn Developer
6 min readOct 25, 2024

--

We think everyone knows that vulnerabilities can negatively affect a project. There are a number of ways to tackle vulnerabilities, from manual searches to customized tools. The article will discuss one such tool in more detail.

Foreword

Let’s explore the user annotation system in the PVS-Studio static analyzer and how it can tackle vulnerabilities. Adding user markup to the analyzer isn’t a new idea. We’ve already implemented it into the PVS-Studio C++ analyzer. You can find out more about C++ annotations and their features here.

The C# analyzer team decided to keep up with their peers and started to implement user annotations for the C# analyzer.

We’ve already mentioned the term “user annotations” a few times. If there’s no confusion about the “user”, let’s focus on the “annotation”. The annotation is a markup that allows the analyzer to get extra information about source code. For example, while marking up the method, you can indicate to the analyzer that the method always returns unverified external data. If you don’t handle such data properly, it can reach certain parts of a program and create potential vulnerabilities.

What is a potential vulnerability? Briefly, it’s a defect in the program code that attackers can exploit in certain scenarios. You can learn more about potential vulnerabilities here.

How can we protect our code from potential vulnerabilities? One way to address the issue can be taint analysis. It allows you to track potentially unsafe data through the program.

Taint analysis in PVS-Studio

In PVS-Studio, taint analysis is implemented through data-flow analysis, it tracks the sources and sinks of tainted data. During the analysis, the analyzer determines the paths of spreading tainted data. If such data enters the sink unchecked, the analyzer will issue a warning about a potential safety weakness. This is a summary of how taint analysis works in PVS-Studio, but you can read the full version too :)

Now we’ve introduced 16 diagnostic rules for taint analysis; each is designed for a specific security weakness:

  • V5608 — SQL injection;
  • V5609 — Path traversal vulnerability;
  • V5610 — XSS vulnerability;
  • V5611 — Insecure deserialization vulnerability;
  • V5614 — XXE vulnerability;
  • V5615 — XEE vulnerability;
  • V5616 — Command injection;
  • V5618 — Server-side request forgery;
  • V5619 — Log injection;
  • V5620 — LDAP injection;
  • V5622 — XPath injection;
  • V5623 — Open redirect vulnerability;
  • V5624 — Configuration vulnerability;
  • V5626 — ReDoS vulnerability;
  • V5627 — NoSQL injection;
  • V5628 — Zip Slip vulnerability.

Let’s take a peek at an example of the potential vulnerability the analyzer detects:

void ProcessRequest(HttpRequest request) 
{
string name = request.Form["name"];

string sql = $"SELECT * FROM Users WHERE name='{name}'";
ExecuteReaderCommand(sql);
....
}

void ExecuteReaderCommand(string sql)
{
using (var command = new SqlCommand(sql, _connection))
{
using (var reader = command.ExecuteReader()) { .... }
}
....
}

The name variable will get the username from an external source. Later, the variable value becomes a part of the SQL query.

The code is vulnerable to SQL injection because it uses unverified external data to create the database query.

For example, the name parameter might contain the following value:

'; DELETE FROM Users WHERE name != '

When the string is inserted into the query template, the resulting SQL command would be:

SELECT * FROM Users WHERE name='';
DELETE FROM Users WHERE name != ''

Executing this query would delete all entries from the Users table (if each user has a value in the name column).

PVS-Studio detects this kind of potential vulnerability.

User annotations

Note: at the time of writing, the C# analyzer supports user annotations only for taint analysis. However, we plan to expand the capabilities of C# user annotations in the future.

We’ve already talked about annotations, but I repeat just in case. The annotation is a markup that gives the analyzer extra information about the source code.

Starting with the version 7.33, PVS-Studio has introduced a system that enables users to mark up sources, transmitter, and sinks of tainted data for the C# projects. This way, the analyzer can more accurately detect potential vulnerabilities in a particular project.

The analyzer already has a markup for the most popular library methods, constructors, and properties. Out of the box, the analyzer knows that SQL injection may occur when the System.Console.ReadLine method result is passed to the System.Data.SqlClient.SqlCommand constructor.

It works because the analyzer has annotations indicating that System.Console.ReadLine is a tainted data source, and System.Data.SqlClient.SqlCommand is a data sink. If tainted data are passed to the sink, an SQL injection vulnerability can occur.

Let’s return to user annotations. To clarify things, look at a real-life example.

Suppose you have the following class in the project:

namespace MyNamespace
{
public class MyClass
{
public string GetUserInput()
{
....
}
public string ModifyCommand(string command, string commandAddition)
{
....
}
    public void ExecuteCommand(string command)
{
....
}
}
}

The class methods operate as follows:

  • GetUserInput returns a certain user input;
  • ModifyCommand adds a string to an existing SQL command;
  • ExecuteCommand executes an SQL command.

Let’s also look at how you could use these methods:

public static void ProcessUserInput(MyClass test)
{
string userInput = test.GetUserInput();
string modifiedCommand = test.ModifyCommand("I'm a sql command",
userInput);
test.ExecuteCommand(modifiedCommand);
}

In this case, the unverified user input is passed to the method that executes the SQL command. As mentioned above, this can lead to SQL injection.

To enable the analyzer to track such cases, add a few method annotations. The content of the annotation file will look as follows:

{
"version": 1,
"language": "csharp",
"annotations": [
{
"type": "method",
"namespace_name": "MyNamespace",
"type_name": "MyClass",
"method_name": "GetUserInput",
"returns": {
"attributes": [
"always_taint"
],
"namespace_name": "System",
"type_name": "String"
}
},
{
"type": "method",
"namespace_name": "MyNamespace",
"type_name": "MyClass",
"method_name": "ModifyCommand",
"params": [
{
"namespace_name": "System",
"type_name": "String"
},
{
"namespace_name": "System",
"type_name": "String",
"attributes": [
"transfer_annotation_to_return_value"
]
}
]
},
{
"type": "method",
"namespace_name": "MyNamespace",
"type_name": "MyClass",
"method_name": "ExecuteCommand",
"params": [
{
"namespace_name": "System",
"type_name": "String",
"attributes": [
"sql_injection_target"
]
}
]
}
]
}

Let’s go over the purpose of each annotation:

  • The GetUserInput method annotation indicates that the method returns tainted data.
  • The ModifyCommand method annotation states that if the second argument includes tainted data, the method also returns taint.
  • The ExecuteCommand method annotation alerts the analyzer that SQL injection may occur if tainted data is passed as the first argument.

Thus, the analyzer understands that tainted data from GetUserInput is passed to ModifyCommand. After that, ModifyCommand also returns tainted. This value is passed to the ExecuteCommand method, which can lead to SQL injection.

If you add annotations to a project that contains ProcessUserInput and then analyze this project, you get the following warning:

V5608 [OWASP-5.3.4, OWASP-5.3.5] Possible SQL injection. Potentially tainted data in the ‘modifiedCommand’ variable is used to create SQL command.

It’s worth noting that you can add the $schema field to the annotation file. Using this field, modern text editors and IDEs can perform validation and suggest possible values while developers edit a file. You can find out what values the $schema field accepts in the documentation.

If you want to know more about what user C# annotations can do, just check out this link.

Conclusion

That’s it! As you can see, annotations can significantly aid in identifying potential vulnerabilities. If you’re interested in annotating your project and searching for potential vulnerabilities, we suggest you try PVS-Studio.

If you prefer not to create annotations manually, you can always rely on the PVS‑Studio developers. As mentioned above, many annotations of library methods, constructors, and properties are available right after installation. All that’s left is to analyze your project :)

--

--

Unicorn Developer
Unicorn Developer

Written by Unicorn Developer

The developer, the debugger, the unicorn. I know all about static analysis and how to find bugs and errors in C, C++, C#, and Java source code.