Static code analysis is a powerful tool for automated security testing of applications. The more an analysis is tailored to your programming language and individual code, the more efficient and accurate are the results. In the second part of our fine-tuning guide, we dive deeper into our analysis approach and how to fully customize it with 5 advanced settings.

RIPS performs language-specific code analysis. Each of our unique analysis engines is dedicated to a different programming language. You can get the maximum out of static analysis if you further fine-tune our language-specific engines to your specific code features. In the following, we guide you through the following 5 configuration options and best practices for advanced users:

Before we look into these settings, it helps to have a brief recap on how RIPS’ code analysis and issue detection works.

Concept of Taint Analysis

RIPS automates the process of identifying all possible user inputs in your application and checking if these attacker-controlled data are used in any security-sensitive feature. Depending on this vulnerable feature and its sensitivity for exploitation, a different issue type is reported. For example, unsanitized user input found in a SQL query poses the risk of a SQL injection vulnerability, user input used in a dynamic code evaluation results in a code injection vulnerability.

To detect these critical types of security violations, RIPS transforms your source code into a graph model. It then analyzes the data flow from user input (sources) towards security-sensitive features (sinks) throughout all possible code paths in the graph model. This process is called taint analysis. One data flow from a source to a sink can reach over hundreds of edges in the graph, e.g. different if/else branches, as well as over different files and functions. On its path, the data can also be modified (sanitized) or verified (validated) such that exploitation from attackers is prevented.

1. Reduce Analysis Depth

By default, RIPS’ taint analysis runs over the complete graph model of your source code. But in order to save performance, it can make sense to lower the level of analysis depth. Then, each data flow analysis between a source and a sink is limited to only a certain amount of graph edges. You can change the analysis depth level in the general settings of your analysis profile.

Best practices:

Very high analysis depth is the most thorough scan and enabled by default. This level is recommended for scanning major code changes and branch merges to detect even deeply nested vulnerabilities.

is the most thorough scan and enabled by default. This level is recommended for scanning major code changes and branch merges to detect even deeply nested vulnerabilities. Reduced analysis depth can be used depending on your code’s quality and modularity. The better your code is structured, the shorter are the code paths, and a lower analysis depth is sufficient. We recommend to experiment with different levels and to observe the impact on results and performance.

can be used depending on your code’s quality and modularity. The better your code is structured, the shorter are the code paths, and a lower analysis depth is sufficient. We recommend to experiment with different levels and to observe the impact on results and performance. Very low analysis depth follows user input only over a few graph edges and catches low hanging fruits. It can be used for IDE integrations and for scanning small code commits in order to get initial security feedback faster, before starting an in-depth scan.

2. Define Custom Sources

RIPS automatically identifies and follows all kinds of sources in your code that receive user input. This can be GET or POST parameters, cookies, HTTP headers, URLs, file names, etc. - everything that a malicious user could modify. During data flow analysis, RIPS also recognizes your custom functions that return any of those inputs and treats these functions as sources as well.

But sometimes code is incomplete. Let’s have a look at the following code. An $id is fetched from the unknown method User::getParameter() in line 3.

Example: Code fetches data from an unknown class User

1 2 3 4 5 6 7 class MyClass { public function process ( User $user ) { $id = $user -> getParameter ( 'id' ); $query = "SELECT * FROM users WHERE id = $id " ; $result = sqlQuery ( $query ); } }

Without any knowledge about the class User and its method getParameter() we don’t know if there is a SQL injection vulnerability in line 4, and neither does RIPS. Hence, we can add any knowledge about additional sources to our analysis profile.

Best practices:

Add User::getParameter() as a source and specify which type of user input is returned by this method. For example, it can return a $_REQUEST parameter. You can also specify the name of this request parameter, e.g. id , or specify that the parameter name is dynamically determined by the 1 argument of this method.

and specify which type of user input is returned by this method. For example, it can return a parameter. You can also specify the name of this request parameter, e.g. , or specify that the parameter name is dynamically determined by the argument of this method. Add $id as global source variable if you would like RIPS to treat any $id variable in your code as a tainted source. You can map this variable to a superglobal, like $_REQUEST . Optionally, specify the exact element id , otherwise your variable maps to the superglobal array.

3. Define Custom Sinks

A security-sensitive function of an application is called a sink. RIPS knows every security-sensitive feature of your programming language and tracks if user input reaches one of those features. Typical examples include functions to execute system commands ( Runtime.exec() in Java) or to evaluate code ( eval() in PHP). If a parameter of a custom function is used in a security-sensitive feature, then RIPS automatically treats this function and its parameter as a sink as well.

Although RIPS follows the data flow of user input throughout multiple layers of function calls automatically, you can also define your own set of sensitive functions. For example, our code snippet uses the custom function sqlQuery() to execute SQL queries.

Example: Code executes SQL query with an unknown function

1 2 3 4 5 6 7 class MyClass { public function process ( User $user ) { $id = $user -> getParameter ( 'id' ); $query = "SELECT * FROM users WHERE id = $id " ; $result = sqlQuery ( $query ); } }

Since the function sqlQuery() is not defined in the code, RIPS does not know what it does and if it is security-sensitive. We can solve this with a sink configuration in our analysis profile.

Best practices:

Add sqlQuery() as a sink and specify that the 1 parameter is susceptible to SQL injection. RIPS then inspects all calls and reports a security issue of the specified type when it detects that user input flows into the first parameter.

and specify that the parameter is susceptible to SQL injection. RIPS then inspects all calls and reports a security issue of the specified type when it detects that user input flows into the first parameter. Create your own security rules by assigning your custom functions with a specific issue type. List any additional function and its parameter number that leads to a security violation when it is tainted with user input.

4. Define Custom Sanitizer

A sanitizer transforms malicious characters of a source into a safe character set, such that the source can be safely used in a sensitive sink. When all malicious characters for a specific issue type are sanitized, then exploitation is prevented and RIPS does not report a vulnerability.

A typical example of input sanitization is escaping. In this process, all quotes ( ' and " ) are preceded with a backslash so that these are not interpreted as string delimiters anymore ( \' and \" ). The backslash character \ is escaped as well ( \\ ). Escaping is typically used to sanitize data in non-prepared SQL queries. However, this is not always safe.

Example: Code sanitizes data with an unknown function

1 2 3 4 5 6 7 8 class MyClass { public function process ( User $user ) { $id = $user -> getParameter ( 'id' ); $id = clean ( $id ); $query = "SELECT * FROM users WHERE id = $id " ; $result = sqlQuery ( $query ); } }

Our code example sanitizes the id with an unknown function clean() . If this function only escapes data, then an attacker could still inject SQL syntax to exploit this SQL injection. For example, the payload 1; DROP TABLE users-- - does not contain any quotes and is thus not affected by escaping quotes. In this case, a safe sanitizer would need to return only numerical values.

Hence, in RIPS you don’t specify what issue type a sanitizer addresses, but rather what characters it affects. RIPS then evaluates for what vulnerability type and in what context this is secure or not.

Best practices:

Specific characters can be entered that are sanitized in the return value of a specific function, for example "'<>./\(){}$ . Simply add the sanitized characters one by one and provide the number of the parameter that is returned in a sanitized way.

can be entered that are sanitized in the return value of a specific function, for example . Simply add the sanitized characters one by one and provide the number of the parameter that is returned in a sanitized way. ALL is a magic keyword that you can enter when all kinds of malicious characters are sanitized. Use this, for example, if your sanitizer returns only numbers and thus hinders attackers to form any malicious payload.

5. Define Custom Validator

A validator checks if the characters of a given source are within a safe character set. Otherwise, the data is rejected. This typically happens within a security check that bases on a true or false return of a function. Our previous code example could also be secured in the following way.

Example: Code validates data with an unknown function

1 2 3 4 5 6 7 8 9 class MyClass { public function process ( User $user ) { $id = $user -> getParameter ( 'id' ); if ( isClean ( $id )) { $query = "SELECT * FROM users WHERE id = $id " ; $result = sqlQuery ( $query ); } } }

The SQL query with the data $id is only executed if the validator function isClean() returns true. Within the function isClean() , the characters of $id are apparently checked for any malicious inputs. We have seen in the previous section that a check for quote characters would not be sufficient. Instead, the function should check if $id contains only numerical characters. The configuration of validators is similar to the one of sanitizers.

Best practices:

Specific characters, or the keyword all, can be used to specify which characters are checked for by the validator.

can be used to specify which characters are checked for by the validator. Return True or False indicates the behavior of the validator in case of a match. In our example, isClean() returns false when the specified characters are found. A counter-example would be a function called isNotClean() that returns true when a malicious character was found.

indicates the behavior of the validator in case of a match. In our example, returns false when the specified characters are found. A counter-example would be a function called that returns true when a malicious character was found. Exit can be defined as a special behavior when the application is stopped as soon as a malicious character is found in the validator’s parameter. Typically, these exit functions are not used within a constraint and don’t have any return value.

Summary

In the second part of our guide, we looked at 5 advanced options to fine-tune your static code analysis results. Static code analysis is a complex topic, and not all configurations may seem easy at the beginning. We recommend to start with the basic settings first and then to deep-dive into the advanced and code-specific settings later. Keep in mind that usually, no configuration of RIPS is required and that it detects and follows all of your sinks, sources, sanitizer, and validator across different files and functions automatically.