Secure Programming

v1.00

 

 

Purpose

 

The purpose of this document is to educate the reader on secure programming practices.  The goal is to provide the user with techniques that can be applied immediately to software development projects that they are a part of.  Goals of this document are to cover the processes involved in designing a secure application, common mistakes and pitfalls that a developer can encounter, and solutions that can be utilized to alleviate those mistakes.

 

There are some basic principles, that, when applied, can alleviate exposure to the majority of security problems.  Vulnerabilities occur most often as the result of programs operating in a different manner, or being coerced to operate in a different manner than their author intended.  There are usually patterns that these flaws follow, and our hope is to demonstrate how to recognize these.  In many cases, it’s as simple as consistently thinking ‘what if?’, and emulating the mindset of an attacker.

 

This document is currently maintained by Oliver Friedrichs of@securityfocus.com.  This document is a work in progress and is expanded upon by discussions that have taken place on the SECPROG mailing list at http://www.securityfocus.com/forums/secprog/.

 

 

1.0      Common.. 3

1.1       Buffer Overflows. 4

1.2       Validating User Data. 7

1.3       Validating DNS names. 8

1.4       Return Values. 10

2.0      UNIX Operating Systems. 11

2.1       Insecure Use of Temporary Files. 12

2.1.1.       tmpfile() 13

2.1.2.       mkstemp() 14

2.1.3.       mktemp() 15

2.1.4.       Additional Solutions. 15

2.2       File Race Conditions. 16

2.3       Generating Random Numbers. 18

2.3.1        Linux. 18

2.3.2        OpenBSD.. 18

2.4       Privileged Programs. 19

2.5       Invoking Child Processes. 20

2.6       Real UID, effective UID, and saved UID.. 20

2.7       Core files. 20

3.1       Access Control Lists. 23

3.2       Registry Keys. 25

3.3       Files. 26

3.4       Generating Random Numbers. 27

4.0      References and Resources. 28

4.1      Contributors. 28

 

 


 

1.0     Common

 

This section discusses secure programming techniques that apply to both UNIX and Windows operating systems.  This area covers topics that are applicable to a varying number of programming languages and operating system.  They are programming conventions or concepts that can be applied to computing in general.

 


1.1     Buffer Overflows

 

By far one of the most common security vulnerabilities focused on in applications today is known as a “buffer overflow”.   The frequency with which this type of problem is being exploited has grown significantly within the past 10 years, with this type of attack being rare in the past – not because the problem wasn’t there, but because attackers were not skilled at exploiting it.

 

A buffer overflow occurs when a piece of data is copied into a location in memory that is not large enough to hold the piece of data.  The copying succeeds, however, memory outside of the boundary of the target memory is also written over.

 

Lets look at an example of a buffer overflow.

 

void function(char *str) {

char buffer[16];

 

strcpy(buffer,str);

}

 

void main() {

char large_string[256];

int i;

 

for( i = 0; i < 255; i++)

large_string[i] = 'A';

 

function(large_string);

}

 

The above program provides a simple example of a buffer overflow vulnerability.  The function copies a supplied string without checking the size, by using strcpy().  If you run this program you will get a segmentation violation.  Lets suppose that the passed in string, *str, instead of consisting of the letter ‘A’, actually contained machine code and correct address information to compose an exploit.  It would be possible for the machine code to be executed when the data is copied into and over the buffer[] variable, and onto the program’s stack.

 

By using the strncpy() function instead, this function could be fixed to safely copy the string, only copying as much data as the target buffer can contain.

 

Many functions that are prone to buffer overflows have safe counterparts, that can be passed in a size parameter to restrict how much data is called.

 

strcpy()                                   Use strncpy() to restrict the length of data copied.  Ensure you NULL terminate the string, since NULL termination will not occur if the source buffer is larger than or equal to the target buffer.

 

                                                                strncpy(target, source, size);

                                                                target[sizeof(target) – 1] = “\0”;

 

strcat()                                    Use strncat() to restrict the length of data copied.  The strncat() function only appends at most the length specified, NULL terminating the resulting string.

 

                                                                strncat(target, source, size);

 

sprintf()                                   Use snprintf() to restrict the length of data printed into the buffer.  The snprintf() function returns the total number of characters that are required to store the passed in data.  If this is larger than the passed in size value, then nothing is written to the buffer.  The user should check the return value of snprintf() to ensure that the buffer was printed to.

 

                                                                if (snprintf(target, sizeof(target) –1, “%s”, string) > sizeof(target) - 1)

                                                                                /* overflow occurred */

 

gets()                                       gets() is probably one of the most unsafe functions available.  It’s only parameter is a buffer:

 

char *gets(char *s);

 

It will read from standard input until a newline or EOF is received, filling the specified buffer.  No length checking is ever performed, making overflowing the buffer trivial.

 

As an alternative to gets(), use fgets():

 

char *fgets(char *s, int n, FILE *stream);

 

This function will read at most ‘n - 1’ characters from the specified file stream.  It will read from the file stream until a newline or EOF is received, or until ‘n – 1’ characters have been read.

 

scanf()                                     Care needs to be taken when using these functions to read

sscanf()                                   strings into fixed size buffers.  Ensure that you specify a

fscanf()                                    maximum length to be read into a specific buffer.  This can be best described by demonstrating a vulnerable and safe version:

 

                                                Vulnerable

 

                                                                char buffer[256];

 

                                                                num = fscanf(stdin, “%s”, buffer);

 

Safe

 

                                                                char buffer[256];

 

                                                                num = fscanf(stdin, “%255s”, buffer);

 

                                                In the second example above, only a maximum of 255 characters can be read into the specified buffer, while in the vulnerable example, an unlimited number can be read in, overwriting the stack in the function.

 

 

memcpy()                                There have been a few instances where overflows have occurred due to unsafe usage of the memcpy() function.  This may occur when the length specified to the memcpy() function can be manipulated by an outside source.  It is important to ensure that the length is not larger than the memory structure being copied into.  A good example of how an overflow like this can occur is illustrated as follows:

 

                                                                unsigned long copyaddress(struct hosten *hp) {

                unsigned long address;

 

memcpy(address, hp->h_addr_list[0], hp->h_length);

                                                                }

 

The above example is taken from an actual vulnerability that was present in the BIND (Berkeley Internet Name Daemon) distribution, and resulted in a number of vulnerabilities in various programs.  It has been simplified for the purpose of this example.  The above function copies hp->h_length number of bytes into the ‘address’ variable (which is 4 bytes).  Under normal circumstances,

hp->h_length will always be 4, since that is the size of an internet address.  If, however, an attacker can manipulate the h_length variable, which he can, if he can spoof a fake DNS reply, he can make the length larger, and pass in more data via the hp->h_addr_list variable.  This will cause more than 4 bytes to be copied into the ‘address’ variable, overflowing the variable and copying data into the stack.

 

Always ensure that you check the length before performing such an action.  For example:

 

                                                                unsigned long copyaddress(struct hosten *hp) {

                unsigned long address;

 

                                                                                if (hp->h_length > sizeof(address))

                return 0;

 

memcpy(address, hp->h_addr_list[0], hp->h_length);

 

return address;

                                                                }

 

In reality, this vulnerability was eventually fixed in the BIND system itself, protecting users from this specific mistake.  Since Internet addresses cannot be anything but 4 bytes, it was ensured that the hp->h_length variable was always 4 bytes when received from the network.


1.2     Validating User Data

 

In some cases it is necessary to validate user input, and remove characters or data that are illegitimate.  A good example may be reading in a username for authentication.  It is possible to strip out all invalid characters that we know of, such as high-bit characters, spaces, or numbers.  The better way however, is to simply strip out everything except that which we want to allow.  So, instead of guessing which characters may be dangerous and stripping them out, only allow those that we know are safe.  This is usually a common mistake that is made when passing data to a second program, using a shell command.

 

One of the most prominent examples of this problem occurred in web based CGI application known as phf, which shipped with NCSA and Apache web servers by default.  phf was one of the leading causes of internet break-ins a number of years ago.  The phf program stripped out known bad characters, before passing the data to a program which was called via popen().  As it happens, it missed one character, the new-line (\n) character, represented as %0a in the HTML query.  By using this character in the data that was passed to the program, an attacker could execute arbitrary commands on the target host.  When parsed by the shell interpreter on the remote host, the new-line characters acted as a command separator, treating the string before the new-line as one command, and the string after the new-line as a new command.  By asking for the following URL, it was possible to execute the command “cat /etc/passwd” on the target host, and view the password file in the web server’s response.

 
/cgi-bin/phf?Qalias=hell%0acat%20/etc/passwd%0a

 

After this vulnerability was found, a fix was made to a common library function that was responsible for cleaning the input.  The newline character was added to a list of characters that were removed from the input.  This was fine an dandy for a period of time, until someone found out that bash, (Bourne Again Shell), which is a common unix shell interpreter, also allowed the ASCII character 255 as a command seperator.  This opened up the same attack again, for any operating system that had bash as their default shell (Linux).  If the fix had instead only allowed known good characters, this problem would never have reoccurred.

 

 

Incorrect

Correct

 

#define BAD "/ ;[]<>&\t"

 

char *query()

{

char *user_data, *cp;

 

/* Get the data */

user_data = getenv("QUERY_STRING");

 

/* Remove bad characters */

for (cp = user_data; *(cp += strcspn(cp, BAD)); )

*cp = '_';

 

                return user_data;

}

 

#define OK              "abcdefghijklmnopqrstuvwxyz\

                                 BCDEFGHIJKLMNOPQRSTUVWXYZ\

                                 1234567890_-.@";

 

char *query()

{

char *user_data, *cp;

 

/* Get the data */

user_data = getenv("QUERY_STRING");

 

/* Remove all but good characters */

for (cp = user_data; *(cp += strspn(cp, OK));)

*cp = '_';

 

                return user_data;

}

 

 

In the correct example, only known bad characters are removed from the query string.  This leaves in place any unknown dangerous characters.  A safer way is to perform this would be as follows, which removes everything except known good characters.

 


1.3     Validating DNS names

 

It is recommended that you never use DNS names for any type of authentication.  The only secure authentication mechanism is one that is cryptographically secure.  If you must use DNS however, it is important to understand the correct way in which to verify a hostname.

 

Lets assume that you wish to restrict the usage of a particular network service you’re writing, to a single authorized, hard-coded, hostname.  When a connection is received, we obtain the connecting address from the accept() call.  With this address, we can now determine the DNS hostname of the connecting host.  We pass the address into a validation function.

 

Incorrect

Correct

 

int validate(u_int32_t ipaddr, char *hostname)

{

      struct inaddr ia;

      struct hostent *he;

 

      memset(&ia, 0, sizeof(ia));

      inaddr.s_addr = ipaddr;

 

      he = gethostbyaddr(&ia, sizeof(ia), AF_INET);

      if (!he)

            return 0;

 

      if (!he->h_name)

            return 0;

 

      if (!strcmp(he->h_name, hostname))

            return 1;

 

      return 0;

}

int validate(u_int32_t ipaddr, char *hostname)

{

      struct inaddr ia;

      struct hostent *he;

      int count;

 

      memset(&ia, 0, sizeof(ia));

      inaddr.s_addr = ipaddr;

 

      he = gethostbyaddr(&ia, sizeof(ia), AF_INET);

      if (!he)

            return 0;

 

      if (!he->h_name)

            return 0;

 

      if (strcmp(he->h_name, hostname))

            return 0;

 

      he = gethostbyname(hostname);

      if (!he)

            return 0;

 

      for (count = 0; he->h_addr_list[count]; count++)

            if (!memcmp(&ipaddr, he->h_addr_list[count], 4)

                  return 1;

                               

      return 0;

}

 

 

 

 

In the “Incorrect” example, a reverse lookup is performed on the passed in IP address.  Once the reverse lookup is complete, the hostname that was looked up is compared to our “allowed” hostname.  If they match, the authentication succeeds.  This introduces a security vulnerability that makes it fairly trivial for an attacker to circumvent this security mechanism.  If a valid user connects, the following will occur:

 

 

An attacker can exploit this via the following:

 

 

The “Correct” scenario solves this problem since it then performs a forward lookup on the hostname to ensure that the hostname resolves to the connecting IP address.  Since a hostname can have multiple IP addresses, all of them are checked.  This check makes it much more difficult for an attacker to perform the above attack.  The attacker would now also need to control the DNS server that serves the allowed.com domain, that is not on their own network.  They would have to compromise the remote host’s network to control the DNS server, making this attack much more difficult and almost infeasible.


1.4     Return Values

 

Checking the return value of functions may seem logical, but in some cases, it is simply overlooked!  In any security critical application it is very important that the return values of all functions are checked.  Let’s look at an example of how this can impact security:

 

char maketemp() {

                char *name = “/tmp/tempfile”;

 

                creat(name, 0644);

chown(name, 0, 0);

return name;

}

 

The above scenario has much more wrong with it than simply not checking the return value of functions, however lets look at the scenario related to return values.  If an attacker creates a symbolic link, pointing /tmp/tempfile to a non-existant location, he can cause the creat() function to fail.  Since the return value of creat() is never checked, the function will happily continue.  A race condition now exists.  Lets say the attacker has previously copied /bin/sh into /tmp, made it setuid (to himself).  Now, after the failed creat() call, and before the chown() call, the attacker wins the race, and links /tmp/tempfile to /tmp/sh.  The /tmp/sh file will now be owned by root and will now also be setuid root!  Some operating systems may prevent this, by resetting the setuid bit when a chown is performed, however this was simply use for demonstration purposes.

 

Always make sure you check the return value for failure or success.


2.0     UNIX Operating Systems

 

This section discusses secure programming techniques that apply to the UNIX operating systems.


2.1     Insecure Use of Temporary Files


Many vulnerabilities occur as the result of a program, not necessarily privileged, accessing a well-known or predictable file on the file system.  A program containing this problem may open a file in the system temporary directory, blindly writing data to the file.  By utilizing symbolic links, an attacker can often redirect this data to other files.  There are 2 scenarios under which this type of attack is commonly launched:

 

  1. A privileged system program is executed by the attacker.  The privileged program executes as the superuser on the system.  The program opens up a file in the system temporary directory:

 

/tmp/program.temp

 

By creating a symbolic link prior to execution, an attacker can point this file to other files that are owned by the privileged user, which the program is executing as. 

 

ln –s /etc/passwd /tmp/program.temp

 

When the program writes to this file, the symbolic link is followed, and the file written to is actually /etc/passwd.

 

  1. The attacker expects another user on the system to execute a known non-privileged program with a temporary file handling issue.  Being very patient, the attacker creates a symbolic link.

 

ln –s /etc/passwd /tmp/program.temp

 

The attacker then expects another system user to execute the program, appending to the password file, assuming the user executing the program has this privilege.

 

The primary difference between these 2 scenarios is that in the first example, the program being executed is setuid root, and when executed runs as the super-user, while in the second scenario, the program is only executed with the executing user’s permissions.

 

There are a number of solutions to these problems:

 

  1. Don’t create temporary files in /tmp.
  2. Create temporary files names that are random and not possible to predict.
  3. Utilize a system provided interface for creating temporary files (mkstemp()).

 

There are a number of system provided functions that can be used to provide temporary files, some of which need to be used correctly to avoid security consequences.

 

 


2.1.1.   tmpfile()

 

The tmpfile() function creates a temporary file, and returns an open handle to the file stream.  tmpfile() avoids the race condition between the generation of a temporary filename, and the creation of the file.  tmpfile() is defined as follows:

 

FILE *tmpfile(void);

 

            On many operating systems, tmpfile() will use the mkstemp() function to obtain and create a temporary filename, then unlink() the file, and fdopen() the file descriptor to return a stream handle.

 

Benefits

 

 

Restrictions

 

 


2.1.2.   mkstemp()

 

The mkstemp() function creates a temporary file, given a template, and returns an open file descriptor to the file.  mkstemp() avoids the race condition between the generation of a temporary filename, and the creation of the file.  mkstemp() is defined as follows:

 

int mkstemp(char *template);

 

A template is passed into the mkstemp() function which gives the path to the temporary file.  In the template are a series of X’s, which are filled in by the function with random values to create the random temporary file name.  An example usage would be:

 

fd = mkstemp(“/tmp/tempfileXXXXXX”);

 

When using this function, make sure that you specified at least six trailing X’s.  Some operating systems support more than six X’s to increase the randomness of the filename.

 

Benefits

 

 

Restrictions

 

 


2.1.3.   mktemp()

 

The mktemp() function is used to generate a unique filename, without actually creating the file.  mktemp() is defined as follows:

 

char *mktemp(char *template);

 

This function takes a template parameter in the same fashion as the mkstemp() function above.  In the template are a series of X’s, which are filled in by the function with random values (sometimes) to create the random temporary file name.  An example usage would be:

 

filename = mktemp(“/tmp/tempfileXXXXXX”);

 

Benefits

 

 

Restrictions

 

 

When using mktemp() it is necessary to open the temporary file once the filename has been generated.  Be careful when opening the file.

 

open(filename, O_WRONLY | O_CREAT, 0644);

 

The above call will create the file, succeeding even if the file already exists.  This is dangerous, and can be used to overwrite existing files if a race condition exists.

 

open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644);

 

The above is a safer way to create the file, since the call will fail, if the file already exists.

 

2.1.4.   Additional Solutions

 

  1. Make an additional directory in /tmp to create your temporary files in.  For example, you could use mktemp() to generate a temporary filename, however use this as a directory name, and then create your temporary files within that directory.  Make sure the directory is created with safe permissions however.  Do this by ensuring your umask() is set to a safe value.

 


2.2     File Race Conditions

 

There are a large number of different situations in which a race condition can occur, giving an attacker the ability to subvert access checks or file creations.  There is a common pattern, which when identified, can be alleviated to secure the operation.

 

  1. A permission check or status check is performed utilizing a filename
  2. An file operation is performed, performing an operation on the same filename

 

The problem that arises here is that in between the first and second operations, an attacker can manipulate the file, causing the permissions or status check to succeed, and the file operation to reference a different file.

 

This type of attack commonly utilizes symbolic links to take advantage of a program’s insecurity.  Lets look at an example source code fragment, which could be present in an insecure setuid root program:

 

int unsafeopen(char *filename)

{

struct stat st;

int fd;

 

/* obtain the files status information */

if (stat(filename, &st) != 0)

return –1;

 

                /*  make sure that the file is owned by root – uid 0*/

                if (st.st_uid != 0)

                                return –1;

 

                fd = open(filename, O_RDWR, 0);

                if (fd < 0)

                                return –1;

 

                return fd;

}

Essentially the above function does the following:

 

  1. Check to see whether filename exists and make sure it is owned by root (uid 0)
  2. Open the file

 

Since these are 2 separate system calls, there is no atomicity between them, leaving a time delay between the 2 specific operations.  Within this time delay, it is possible for file and system characteristics to change.  An attacker can exploit this in the following fashion:

 

  1. He can create a symbolic link pointing /tmp/filename to a root owned file, for example /etc/passwd.
  2. The stat() call will follow the symbolic link, and return information for /etc/passwd, which is owned by the root user (uid 0).
  3. The attacker removes the symbolic link, and points it to a file he owns.
  4. The program now happily opens /tmp/filename, which points to his file for reading, and reads in his data, instead of the data from a file prepared by another root owned process.

 

A safe version of this function would do the following:

 

int safeopen(char *filename)

{

struct stat st, st2;

int fd;

 

/* obtain the file’s status information */

if (lstat(filename, &st) != 0)

return –1;

 

                /* make sure the file is a regular file */

if (!S_ISREG(st.st_mode))

       return –1;

 

                /*  make sure that the file is owned by root – uid 0*/

                if (st.st_uid != 0)

                                return –1;

 
                /* open the file */

                fd = open(filename, O_RDWR, 0);

                if (fd < 0)

                                return –1;

 

                /* now we fstat() the file, to make sure it’s the same file still! */

                if (fstat(fd, &st2) != 0) {

                                close(fd);

                                return –1;

                }

 

                /*

 * here we make sure the inode and device numbers are the

 * same in the file we actually opened, compared to the file

 * we performed the initial lstat() call on.

 */

                if (st.st_ino != st2.st_ino || st.st_dev != st2.st_dev) {

                                close(fd);

                                return –1;

                }

 

                return fd;

}

The above function uses lstat() instead of stat().  This returns the status of the link, if the specified filename happens to be a symbolic link.  It then opens the file, and obtains the status of the open file descriptor.  The inode and device numbers of the status information are compared (they are unique between files), and if they are not found to be identical, the function is aborted.


2.3     Generating Random Numbers

 

Random number generation has been an issue without an easy solution ever since random numbers were needed.  Most operating systems provide a pseudo-random number generator library call, which is appropriate for some purposes, however remember the word “pseudo” in the name.  Some operating systems offer built-in random number generators, usually via a device driver that provides random data.  This is most often accomplished via a kernel driver that mixes and hashes various events and variables on the system.  We will cover some well known methods for obtaining random data in various operating systems.

 

2.3.1    Linux

 

Current Linux operating systems provide the /dev/random and /dev/urandom devices.  These devices provide random numbers based on various system states, that are collected and then hashed to produce a random number.

 

It is claimed that both /dev/random and /dev/urandom are secure enough to use in generating cryptographic keys, challenges, and other applications where secure random numbers are requisite. It should not be possible to predict the next random number from these sources.

 

The difference between the two is that /dev/random can run out of random bytes and the reader must wait for more to become available.  This can occur if not enough activity is present on the system to allow generation of additional random data, and can sometimes take a long time for new data to become available. 

 

/dev/random is high quality entropy, generated from measuring the inter-interrupt times etc. It blocks until enough bits of random data are available.

 

/dev/urandom is similar, but when the store of entropy is running low, it'll return a cryptographically strong hash of what there is.  This isn't as secure, but it's enough for most applications.

 

To use these devices, simply open the device name and perform a read call on the device for the desired number of bytes.

 

2.3.2    OpenBSD

 

The OpenBSD kernel uses the mouse interrupt timing, network data interrupt latency, inter-keypress timing and disk IO information to fill an entropy pool. Random numbers are available for kernel routines and are exported via devices to userland programs.  OpenBSD exposes the device /dev/random to userland programs requiring random numbers.

 

The following is taken from the OpenBSD manual page:

 

 

The various random devices produce random output data with different random qualities.  Entropy data is collected from system activity (like disk and network device interrupts and such), and then run through various hash or message digest functions to generate the output.

 

/dev/random         This device is reserved for future support of hardware random generators.

 

/dev/srandom       Strong random data.  This device returns reliable random data.  If sufficient entropy is not currently available (i.e., the entropy pool quality starts to run low), the driver pauses while more of such data is collected.  The entropy pool data is converted into output data using MD5.

 

/dev/urandom       Same as above, but does not guarantee the data to be strong.  The entropy pool data is converted into output data using MD5.  When the entropy pool quality runs low, the driver will continue to output data.

 

/dev/prandom       Simple pseudo-random generator.

 

/dev/arandom       As required, entropy pool data re-seeds an ARC4 generator, which then generates high-quality pseudo-random output data.  The arc4random(3) function in userland libraries seeds itself from this device, providing a second level of ARC4 hashed data.

 


2.4     Privileged Programs

 

When writing a privileged program, it is important to take into consideration everything  that is discussed in this course, however there are some basic strategies that can help you to improve the security of your project.

 

  1. Keep privileged portions of your program as simple and small as possible
  2. Have a well defined API.  This should cover all functions used internally in the program, as well as the interface exposed to the user.  Look for possible security consequences in the exposed API, this is a common route an attacker takes to exploit a privileged program.
  3. Do not trust any external inputs.  Ensure that you have sufficiently validated and cleaned the data before using it.

 


2.5     Invoking Child Processes

 

On many occasions it is necessary for one program to execute another.  If the parent program is a privileged program, for example a setuid program, this can introduce many interesting security consequences.

 

There a number of precautions which you should take when spawning a child program:

 

  1. Ensure that all file descriptors are closed, to prevent the child process from inheriting access to important files.
  2. Ensure that the full path to the program is specified, to prevent an alternate or Trojan version of the program from being executed.
  3. Drop privileges before execution, so that the child process does not inherit the privileges of the parent.
  4. Ensure environment variables that are passed onto the child are cleansed.  Pass a minimal environment, only whats needed.  Environment variables can be defined multiple times, having security consequences.  Just throw it all out and create a new environment.

 

2.6     Real UID, effective UID, and saved UID

 

Processes in UNIX must run under the privilege of a particular system user.  Operating systems vary, but in general all UNIX operating system processes have at least the following privileges associated with them:

 

uid                   The user ID the process is running as.  A process can only change the uid which they are running as if the process is running as the super-user, or if the user ID they wish to change to is the same as the effective user ID (see below).

 

gid                   The group ID the process is running as.  A process can only change the gid which they are running as if the process is running as the super-user, or if the group ID they wish to change to is the same as the effective group ID (see below).

 

euid                 Each process has an effective user ID, which under normal circumstances will be set to the same value as the real user ID initially.  When running a setuid file, however, the effective user ID is to the owner of the file.

 

egid                 Each process has an effective group ID, which under normal circumstances will be set to the same value as the real group ID initially.  When running a setgid file, however, the effective group ID is set to the group of the file.

 

 

 

2.7     Core files

 

Core files are generated by UNIX programs when an exception occurs.  These exceptions normally occur when the program memory or stack is corrupted, or invalid memory or misaligned structures are accessed.  These exceptions occur as the result of a bug in the program itself.  When the exception occurs, the operating system writes the memory of the currently executing program to a disk file, usually called ‘core’ or ‘program.core’, where program is the name of the program that was executing.  The core file can normally be used for analyzing the state of the program when it crashed, and assists in determining where the problem occurred.  Since all memory contents of the program are written to this file, it is possible that important, or security critical information (passwords) was in the programs memory at the time, and was written to this file.

 

If your program will contain security critical information in memory, it is wise to disable the creation of core files upon an exception.  You can use the setrlimit() function call to accomplish this.

 

int setrlimit(int resource, const struct rlimit *rlp);

 

By using this function, with a resource type of RLIMIT_CORE, you can set the size of the core file that is created (in bytes).  If you specify a size of 0, this prevents a core file from being created.

 

int nocore() {

struct rlimit rlp;

 

rlp->rlim_cur = 0;

rlp->rlim_max = 0;

return(setrlimit(RLIMIT_CORE, &rlp));

}

 


3.0      Windows Operating System

 

This section discusses secure programming techniques that are specific to Windows operating systems.  We focus primarily on Windows NT, however many of the topics also apply to other version of Windows, such as Windows 95 and Windows 98.  All topics also apply to Windows 2000.

 


3.1     Access Control Lists

 

Much of Windows NT security is based on Access Control Lists (ACL’s for short).  ACL’s can be applied to many different types of objects, used in various parts of the system.  Some examples of these objects include:

 

ACL’s define who the owner of an object is, and who can access the object.  In many cases, when creating a new object, the ACL’s applied to the object are not safe.  It is up to the implementer to ensure that the secure attributes are correctly set.  There are generally 4 different types of security information that apply to an object.  When setting or retrieving the security information on an object, it is important to be familiar with these.

 

 

OWNER_SECURITY_INFORMATION

 

This option specifies the owner of an object.  In many cases, this will default to the user who initially created the object.

 

GROUP_SECURITY_INFORMATION

 

This option indicates the group permissions applied to the object.  This defines which groups can access the object.

 

DACL_SECURITY_INFORMATION

 

This option sets the “discretionary” access control list on the specified registry key.

 

SACL_SECURITY_INFORMATION

 

This option sets the “system” access control list on the specified registry key.

 

Windows NT provides a function called SetSecurityInfo() that can be used to apply security descriptors to any of the objects shown above.  This function is used as follows:

 

DWORD SetSecurityInfo(

  HANDLE handle,                                                              // handle to the object

  SE_OBJECT_TYPE ObjectType,                  // type of object

  SECURITY_INFORMATION SecurityInfo, // type of security information to set

  PSID psidOwner,                                                               // pointer to the new owner SID

  PSID psidGroup,                                                               // pointer to the new primary group SID

  PACL pDacl,                                                                      // pointer to the new DACL

  PACL pSacl                                                                       // pointer to the new SACL

);

 

The SetSecurityInfo() function will only set one of the ACLs (owner, group, DACL, or SACL), not all of them, and you must specify in the SecurityInfo variable, which ACL you wish to set.  You must also specify in the ObjectType variable, which type of object you have specified a handle to (registry key, file, etc).

 

In addition to this function, there are also a number of object specific functions that can be used to apply security descriptors to their respective types of objects.  The registry for example, has it’s own set of functions that can be used to set ACLs on registry key, essentially duplicating the purpose of the SetSecurityInfo() function.

 

One of the biggest problems present is that when most objects are created, they possess insecure ACLs, usually granting “Everyone” access to the object.  It is important that the application developer is aware of this, and takes appropriate precautions to restrict access.

 


3.2     Registry Keys

 

It is quite common to use the Windows registry to store vital configuration information, that is required for normal operation of your program.  Sometimes vital security information is stored in the registry as well.  More often than not, however, registry keys are not sufficiently protected to prevent an attacker from reading or modifying the data stored in the registry.  Instead of using the SetSecurityInfo() functions, there are also specific functions for managing ACLs on registry keys.  There are 2 functions present within Windows to support the viewing and setting of registry key permissions.

 

LONG RegGetKeySecurity(

  HKEY hKey,                                                                                        // open handle of key to set

  SECURITY_INFORMATION SecurityInformation, // descriptor contents

  PSECURITY_DESCRIPTOR pSecurityDescriptor,   // address of descriptor for key

  LPDWORD lpcbSecurityDescriptor                                             // address of size of buffer and descriptor

);

 

LONG RegSetKeySecurity(

  HKEY hKey,                                                                                        // open handle of key to set

  SECURITY_INFORMATION SecurityInformation, // descriptor contents

  PSECURITY_DESCRIPTOR pSecurityDescriptor    // address of descriptor for key

);

 

 


3.3     Files

 

It is very important to ensure that you adequately protect your program’s files with ACLs.  There are two primary scenarios that are common in Windows NT application development.

 

1.      Application is a system wide application, available to all users.

 

-         Cannot limit access to the application, and it’s directories, since all users require the ability to access them.

-         Limit access to all application generated files – those that are created for each user.  This will ensure that only the user who created the files will have access to them.  Ensure that only the user has access to read and write to these files.

 

2.      Application is a user application, only used by the Administrator.

 

-         Limit access to the application, and it’s directories.  No other users require access to the application.  These ACLs can usually be applied during the installation process.

-         Limit access to all application generated files – those that are created for each user.  This will ensure that only the user who created the files will have access to them.  Ensure that only the user has access to read and write to these files.

 

There are 2 functions available in Windows NT to support the viewing and setting of file permissions.

 

BOOL GetFileSecurity(

  LPCTSTR lpFileName,                                                                   // address of string for file name

  SECURITY_INFORMATION RequestedInformation,              // requested information

  PSECURITY_DESCRIPTOR pSecurityDescriptor,   // address of security descriptor

  DWORD nLength,                                                                            // size of security descriptor buffer

  LPDWORD lpnLengthNeeded                                                      // address of required size of buffer

);

 

BOOL SetFileSecurity(

  LPCTSTR lpFileName,                                                                   // address of string for filename

  SECURITY_INFORMATION SecurityInformation,  // type of information to set

  PSECURITY_DESCRIPTOR pSecurityDescriptor    // address of security descriptor

);

 


3.4     Generating Random Numbers

 

The Windows NT CryptoAPI library provides functions that can be used to obtain random data.  The CryptoAPI exports a function called CryptGenRandom() that will fill a specified buffer with the required number of bytes of random data.

 

BOOL WINAPI CryptGenRandom(HCRYPTPROV hProv, DWORD dwLen, BYTE pbBuffer);

 

The following is quoted from the Microsoft MSDN and is Copyright 2000, Microsoft Corporation.

 

“The data produced by this function is cryptographically random. It is far more random than the data generated by the typical random number generator such as the one shipped with your C compiler.

This function is often used to generate random initialization vectors and salt values.

 

All software random number generators work in fundamentally the same way. They start with a random number, known as the seed, and then use an algorithm to generate a pseudo-random sequence of bits based on it. The most difficult part of this process is to get a seed that is truly random. This is usually based on user input latency, or the jitter from one or more hardware components.

 

If an application has access to a good random source, it can fill the pbBuffer buffer with some random data before calling CryptGenRandom. The CSP then uses this data to further randomize its internal seed. It is acceptable to omit the step of initialize the pbBuffer buffer before calling CryptGenRandom.”

 


 

4.0     References and Resources

 

The Unix Secure Programming FAQ

Tips on security design principles, programming methods, and testing

By Peter Galvin

 

http://www.sunworld.com/sunworldonline/swol-08-1998/swol-08-security.html

 
Smashing The Stack For Fun And Profit
by Aleph One
aleph1@securityfocus.com

 

http://www.securityfocus.com/data/library/P49-14.txt

 

4.1     Contributors

 

The following individuals have contributed to this document by taking part in the discussions on the SECPROG mailing list at http://www.securityfocus.com: