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/.
2.1 Insecure Use of Temporary Files
2.6 Real UID, effective UID, and saved UID
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.
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);
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.
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.
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.
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.
This section discusses secure programming techniques that apply to the UNIX operating systems.
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:
/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.
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:
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.
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
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
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.
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.
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:
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:
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.
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.
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.
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.
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.
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:
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.
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.
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.
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
);
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
);
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.”
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
The
following individuals have contributed to this document by taking part in the
discussions on the SECPROG mailing list at http://www.securityfocus.com: