Fortify Static Code Analyzer

HP Fortify Static Code Analyzer (SCA) is a set of software security analyzers that search for violations of security-specific coding rules and guidelines in a variety of languages. The rich data provided by SCA language technology enables the analyzers to pinpoint and prioritize violations so that fixes can be fast and accurate. The analysis information produced by SCA helps you deliver more secure software, as well as making security code reviews more efficient, consistent, and complete. This is especially advantageous when large code bases are involved. The modular architecture of SCA allows you to quickly upload new third-party and customer-specific security rules.

The way of working

Use fortify for a “big existing visual solution” is not easy.  And in my case, the visual solution is composed as more than 200 visual projects ! So, we have to manage several points :

Project Type and Implications

We have three project types, each of them having their implications regarding fixing project issues:

  • Application projects have probably the lesser implications, as their build does not affect other projects (their runtime might, of course).
  • DLL projects could be released independently under the assumption that the interface doesn’t change, and that proper unit testing is in place (to ensure that any modification doesn’t break the interface contract).
  • Static Library projects are the trickier ones, as fixing them will necessarily require the build.

Dependency Analysis

The following analysis will be about the spread of issues across projects, while considering the dependency graph.

This dependency analysis is done based on the dependency information as defined in the visual studio solution by the user. There are other dependencies that are not recorded (such as COM+ dependencies for our COM+ projects, or even P/Invoked DLLs in C# code). The report still provides sufficient details in order to define some initial plans of action.

The current results focus on the issues with Critical, High and Medium severities.

Naming Used in the Analysis

There are some terms that will be used in the remainder of the documentation:

  • “Requires” dependency: relationship between two projects where the subject depends on the object.
  • “Used By” dependency: relationship between two projects where the object depends on the object.
  • Direct dependency: when the relationship between two projects is direct (as opposed to having an intermediate project that creates the dependency).
  • Recursive dependencies: all direct dependencies of a project and all dependencies of those dependencies (recursively).

Overview

We have found more than 16000 Critical issues and some specific issues detected are very hard to fix, need some change in architecture, way of working,… E.g. for the issues present in the DLLs, these projects could be released independently if done carefully:

  • We would need to create careful unit testing before doing any changes, to make sure we can regress our fixes.
  • We would have to still do end-to-end testing, although eventually in a lighter or less exhaustive activity.
  • “Deprecrate message” should be inserted in the current DLLs entry point to notify the creation of the new DLL version and to make a progressive transition of usage of these new entries points.

Possible Preliminary Actions

Any decided plan of action should take into account:

  • The operational cost involved in fixing bugs (do the actual fixes, do the testing – unit and e2e): which projects should we proactively fix, which ones should we wait for an IM/PM (and therefore leave it towards the end) to also fix the issues reported by Fortify.
  • The operational cost of releasing common projects (DLLs in particular) in very early stage vs deferring them for a reasonable period, in case an IM/PM shows that requires a functional fix (this might optimize testing/releasing, since we avoid a release for fixing Fortify specifically).
  • The functional impact of releasing each project: which other projects have a dependency in code or in the architecture.
  • The security impact of each project: in terms of what issues represent bigger threats, what applications are more security-sensitive, whether it’s better to fix one project with a big count of issues or many others with small count.

Deferring the Fixing of DLL Issues

  • A hard requirement that all DLL dependencies need to be fixed when an application is fixed will probably hinder the team. The reason for this is that such requirement will force to test (E2E) all other applications that also use the same dependency, which is a cost that we might not be able to afford without proper planning and time-boxing.There will be significant amount of unit test development to be done around DLLs, which we’ll also need to plan for.

Fortify scan

We use a batch to launch the fortify scan for a specific project or for all. Its separated from common build chain because its take too much time to make a scan every time. A security scan should be done at the end of development after the testing and before releasing application.

Launching Fortify can be done by using a specific menu installed in Visual Studio.

Or via a specific command line using several parameters.

sourceanalyzer -b MyBuild -machine-output devenv All-projects.sln /BUILD debug /project "..\projects\%$_MyProject%\%$_MyProject%.vcxproj" /ProjectConfig %$_Bits%

The result files from the analyzer is a “.fpr” file type and can be opened using the audit workbench tools.

With this audit tools, we can have a look at all security risk.

For this huge “project” (composed of many small/medium projects), we have start with a two pass approach.

  • First step called (QuickWins) is used to detect and fix the simple issues.
  • Second step is used to fix more complex issues.

To improve the way of working, we have done a fortify Library composed with fortify functions used to fix issues detected.

Also, some security issues can be detected only in runtime, means that a specific functions can return some “alarm” when secutiry issues are detected…

A Dashboard sample used during the project development, overview of all projects issues fixed, pending,…

Sample of Fortify Library Helper

FORTIFY_API int fortifyPathManipulation(const char *originalPath, int size);

FORTIFY_API int  fortifyReverseDnsCheck(struct hostent *resolvedHost);

FORTIFY_API void fortifyEraseAndCleanMemory(void *ptr, int size);

Typical Security Issues detected by Fortify Scan

Potential command injection with Process.Start

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

Vulnerable Code

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

Risk

If a malicious user is able to controlled either the command 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 whitelist of characters.

Solution

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

Buffer Overflow Attacks

It gets worse when an attacker comes to know about a buffer over flow in your program and he/she exploits it. Consider this example :

#include <stdio.h> #include <string.h>

int main(void) {

char buff[15];     int pass = 0;

printf("\n Enter the password : \n");     gets(buff);

if (strcmp(buff, "thegeekstuff"))     {

printf ("\n Wrong Password \n");

}     else     {

printf ("\n Correct Password \n");         pass = 1;

}

if (pass)     {

/* Now Give root or admin rights to user*/

printf ("\n Root privileges given to the user \n");

}

return 0;

}


The program above simulates scenario where a program expects a password from user and if the password is correct then it grants root privileges to the user.

Let’s the run the program with correct password ie ‘thegeekstuff’ :

$ ./bfrovrflw

Enter the password : thegeekstuff

Correct Password

Root privileges given to the user

This works as expected. The passwords match and root privileges are given.

But do you know that there is a possibility of buffer overflow in this program. The gets() function does not check the array bounds and can even write string of length greater than the size of the buffer to which the string is written. Now, can you even imagine what can an attacker do with this kind of a loophole?

Here is an example :

$ ./bfrovrflw
Enter the password : hhhhhhhhhhhhhhhhhhhh
Wrong Password
Root privileges given to the user.

In the above example, even after entering a wrong password, the program worked as if you gave the correct password.

There is a logic behind the output above. What attacker did was, he/she supplied an input of length greater than what buffer can hold and at a particular length of input the buffer overflow so took place that it overwrote the memory of integer ‘pass’. So despite of a wrong password, the value of ‘pass’ became non zero and hence root privileges were granted to an attacker.

There are several other advanced techniques (like code injection and execution) through which buffer over flow attacks can be done but it is always important to first know about the basics of buffer, it’s overflow and why it is harmful.

Common vulnerabilities guide

Most vulnerabilities in C are related to buffer overflows and string manipulation. In most cases, this would result in a segmentation fault, but specially crafted malicious input values, adapted to the architecture and environment could yield to arbitrary code execution. You will find below a list of the most common errors and suggested fixes/solutions.

gets

The stdio gets() function does not check for buffer length and always results in a vulnerability.

Vulnerable code
#include <stdio.h>
int main () {
char username[8];
int allow = 0;
printf ("Enter your username, please: ");
gets(username); // user inputs "malicious"
if (grantAccess(username)) {
allow = 1;
}
if (allow != 0) { // has been overwritten by the overflow of the username.
privilegedAction();
}
return 0;
}

Prefer using fgets (and dynamically allocated memory!):

#include <stdio.h>
#include <stdlib.h>
#define LENGTH 8
int main () {
char* username, *nlptr;
int allow = 0;

username = malloc(LENGTH * sizeof(*username));
if (!username) return EXIT_FAILURE;
printf ("Enter your username, please: ");
fgets(username,LENGTH, stdin);
// fgets stops after LENGTH-1 characters or at a newline character, which ever comes first.
// but it considers \n a valid character, so you might want to remove it:
nlptr = strchr(username, '\n');
if (nlptr) *nlptr = '\0';

if (grantAccess(username)) {
allow = 1;
}
if (allow != 0) {
priviledgedAction();
}

free(username);

return 0;
}

strcpy

The strcpy built-in function does not check buffer lengths and may very well overwrite memory zone contiguous to the intended destination. In fact, the whole family of functions is similarly vulnerable: strcpy, strcat and strcmp.

Vulnerable code

char str1[10];
char str2[]="abcdefghijklmn";
strcpy(str1,str2);

The best way to mitigate this issue is to use strlcpy if it is readily available (which is only the case on BSD systems). However, it is very simple to define it yourself, as shown below:

#include <stdio.h>

#ifndef strlcpy
#define strlcpy(dst,src,sz) snprintf((dst), (sz), "%s", (src))
#endif

enum { BUFFER_SIZE = 10 };

int main() {
char dst[BUFFER_SIZE];
char src[] = "abcdefghijk";

int buffer_length = strlcpy(dst, src, BUFFER_SIZE);

if (buffer_length >= BUFFER_SIZE) {
printf ("String too long: %d (%d expected)\n",
buffer_length, BUFFER_SIZE-1);
}

printf ("String copied: %s\n", dst);

return 0;
}

Another and may be slightly less convenient way is to use strncpy, which prevents buffer overflows, but does not guarantee ‘\0’-termination.

enum { BUFFER_SIZE = 10 };
char str1[BUFFER_SIZE];
char str2[]="abcdefghijklmn";
/* limit number of characters to be copied */
strncpy(str1,str2, BUFFER_SIZE);

// We need to set the limit to BUFFER_SIZE, so that all characters in the buffer
// are set to '\0'. If the source buffer is longer than BUFFER_SIZE, all the '\0'
// characters will be overwritten and the copy will be truncated.

if (str1[BUFFER_SIZE-1] != '\0') {
/* buffer was truncated, handle error? */
}

For the other functions in the family, the *n* variants exist as well: strncpm and strncat

sprintf

Just as the previous functions, sprintf does not check the buffer boundaries and is vulnerable to overflows.

Vulnerable code
#include <stdio.h>
#include <stdlib.h>

enum { BUFFER_SIZE = 10 };

int main() {
char buffer[BUFFER_SIZE];
int check = 0;

sprintf(buffer, "%s", "This string is too long!");

printf ("check: %d", check); /* This will not print 0! */

return EXIT_SUCCESS;
}

Prefer using snprintf, which has the double advantage of preventing buffers overflows and returning the minimal size of buffer needed to fit the whole formatted string.

#include <stdio.h>
#include <stdlib.h>

enum { BUFFER_SIZE = 10 };

int main() {
char buffer[BUFFER_SIZE];

int length = snprintf(buffer, BUFFER_SIZE, "%s%s", "long-name", "suffix");

if (length >= BUFFER_SIZE) {
/* handle string truncation! */
}

return EXIT_SUCCESS;
}

printf and friends

One other vulnerability category is concerned with string formatting attacks , those can cause information leakage, overwriting of memory, … This error can be exploited in any of the following functions: printf, fprintf, sprintf and snprintf, i.e. all functions that take a “format string” as argument.

It’s really simple: always hardcode the format string. At least, never let it come directly from any user’s input.

File opening

Much care must be taken when opening files, as many issues can arise. This is covered in much detail by Kupsch and Miller in this tutorial. They also provide libraries implementing their approach. Out of the many ways file handling can be attacked, we will only present two brief examples below.

Some of the basic pitfalls are described below.

Symbolic link attack

It is a good idea to check whether a file exists or not before creating it. However, a malicious user might create a file (or worse, a symbolic link to a critical system file) between your check and the moment you actually use the file.

Vulnerable code
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define MY_TMP_FILE "/tmp/file.tmp"

int main(int argc, char* argv[])
{
FILE * f;
if (!access(MY_TMP_FILE, F_OK)) {
printf ("File exists!\n");
return EXIT_FAILURE;
}
/* At this point the attacker creates a symlink from /tmp/file.tmp to /etc/passwd */
tmpFile = fopen(MY_TMP_FILE, "w");

if (tmpFile == NULL) {
return EXIT_FAILURE;
}

fputs("Some text...\n", tmpFile);

fclose(tmpFile);
/* You successfully overwrote /etc/passwd (at least if you ran this as root) */

return EXIT_SUCCESS;
}

Avoid the race condition by accessing directly the file, and don’t overwrite it if it already exists. So,

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>

#define MY_TMP_FILE "/tmp/file.tmp"

enum { FILE_MODE = 0600 };

int main(int argc, char* argv[])
{
int fd;
FILE* f;

/* Remove possible symlinks */
unlink(MY_TMP_FILE);

/* Open, but fail if someone raced us and restored the symlink (secure version of fopen(path, "w") */
fd = open(MY_TMP_FILE, O_WRONLY|O_CREAT|O_EXCL, FILE_MODE);
if (fd == -1) {
perror("Failed to open the file");
return EXIT_FAILURE;
}

/* Get a FILE*, as they are easier and more efficient than plan file descriptors */
f = fdopen(fd, "w");
if (f == NULL) {
perror("Failed to associate file descriptor with a stream");
return EXIT_FAILURE;
}
fprintf(f, "Hello, world\n");
fclose(f);

/* fd is already closed by fclose()!!! */
return EXIT_SUCCESS;
}

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’)