As a Linux user, you'll eventually dabble in Web development. That's a given. And Linux offers copious tools and opportunities in this area. However, when you're writing your own Web tools, you must ensure that you don't inadvertently open security holes on your otherwise secure host. This chapter will quickly examine secure Web development techniques.
On every Web development project, you'll face three chief risks that are manifested in logical sequence, from your project's bare beginnings to its ultimate completion:
Faulty tools—You must keep up with the times and obtain the latest tools. Languages and libraries are carefully scrutinized but security issues within them surface periodically. If your tools are flawed, even your best efforts will fail.
Flawed code—Even if you have flawless tools, you must know how to properly use them. Some programming languages enforce strict guidelines, while others don't (C as opposed to Perl, for example). But most employ only cursory security checks on your code—if they do any at all. That means that you, and not the compiler or interpreter, are ultimately responsible for ensuring that your code enhances system security (or at worst, does not impede or degrade it).
Environment—Even if you use flawless tools and employ them properly, unexpected contingencies can arise. The environment is a good example. Attackers or even coworkers can either maliciously or unwittingly alter the environment and alter your program's execution and performance.
The best advice, therefore, is to choose one language, learn it well, and stay current on all security issues relevant to it. Beyond that, this chapter covers some common programming errors, means of avoiding them, and tools to help you in that regard.
Several functions in C, C++, and Perl spawn shells or otherwise execute programs insecurely:
system()
popen()
open()
eval
exec
Wherever possible, you should avoid these functions. The following sections illustrate why.
Here are two very risky practices:
Constructing internal command lines using user input.
Executing shell commands from within C or Perl.
Programmers often perform these tasks using the system()
function.
system()
is available via the standard library (stdlib.h
) and provides a mechanism to execute a shell command from a C or C++ program. As explained on the system
(3)
man page:
system()
executes a command specified in string by calling /bin/sh -c string
, and returns after the command has been completed.
Do not use system()
in
Publicly accessible programs or scripts on your Web host
SGID programs or scripts
SUID programs or scripts
Here's why: Attackers can execute shell commands riding on the system()
function, either by manipulating environment variables or pushing metacharacters or additional commands onto the argument list. In particular, you should always avoid giving attackers an opportunity to pass metacharacters to any function that calls a shell.
Table 16.1 lists commonly used metacharacters in various shells (bash
, csh
, ksh
).
Table 16.1. Various Shell Metacharacters in bash
, csh
, and ksh
Purpose | bash | csh | ksh |
---|---|---|---|
Append output to a file | >> | >> | >> |
Append STDERR and STDOUT | >>& | >& | |
Command separator | ; | ; | ; |
Command substitution | '…' | ' | '…' |
Execute in background | & | & | & |
Group commands | ( ) | ( ) | ( ) |
History substitution | ![ job # ] | %[ job # ] | |
Home directory symbol | /~ | /~ | ~ |
Literal (but not $ or / ) | "…" | "…" | "…" |
Literal quote | '…' | '…' | '…' |
Logical AND | && | && | && |
Logical OR | || | || | || |
Match multiple characters | * | * | * |
Match a single character | ? | ? | ? |
Match multiple characters | […] | […] | […] |
Path break symbol | / | / | / |
Pipe | | | | | | |
Redirect input to a line | << | << | << |
Redirect input | < | < | > |
Redirect output | > | > | > |
Redirect STDERR and STDOUT | 2> | >& | |
Variable substitution | ${…} | $ | ${…} |
To appreciate the danger of using system()
, consider this C++ code, which allows a user to execute a shell command:
int main() { char usercommand[20]; cout << "Please enter a command:"; cin >> usercommand; cout << "You entered" << usercommand << " "; system(usercommand); }
No one would actually write such a program, but it's useful for demonstration purposes. The code grabs a user command and executes it:
$testsystem
Please enter a command: ls
total 456
-rwxrwxrwx 1 9053 9000 530 Jun 9 1995 Makefile
-rwxrwxrwx 1 9053 9000 2799 Jun 14 1995 README
-rwxrwxrwx 1 9053 9000 1001 Jun 9 1995 arp.c
-rwxrwxrwx 1 9053 9000 6988 Jun 9 1995 dnit.c
-rwxrwxrwx 1 9053 9000 1047 May 13 1995 dnit.h
-rwxrwxrwx 1 9053 9000 0 Jun 9 1995 errlist
-rwxrwxrwx 1 9053 9000 1621 Jun 9 1995 ether.c
-rwxrwxrwx 1 mikal user 6798 Jun 22 07:11 ipspoof.c
This doesn't seem threatening. But suppose that the user entered a different command instead:
$testsystem
Please enter a command: ls;finger
total 456
-rwxrwxrwx 1 9053 9000 530 Jun 9 1995 Makefile
-rwxrwxrwx 1 9053 9000 2799 Jun 14 1995 README
-rwxrwxrwx 1 9053 9000 1001 Jun 9 1995 arp.c
-rwxrwxrwx 1 9053 9000 6988 Jun 9 1995 dnit.c
-rwxrwxrwx 1 9053 9000 1047 May 13 1995 dnit.h
-rwxrwxrwx 1 9053 9000 0 Jun 9 1995 errlist
-rwxrwxrwx 1 9053 9000 1621 Jun 9 1995 ether.c
-rwxrwxrwx 1 mikal user 6798 Jun 22 07:11 ipspoof.c
Login Name TTY Idle When Office
root Big Bad-Ass q0 Thu 15:15
mikal Chief Developer *ftp Thu 22:37 Room 200
The code allows users to execute additional commands by adding the command separator metacharacter (;
). True, attackers are restricted to appending commands without whitespace (they cannot successfully execute ls;
command1
argument
;
command2
argument
, for example), but nevertheless, this opens a serious hole.
system()
can be attacked in other ways, too. On some systems, local attackers can alter the Input Field Separator shell variable to break up paths in your system()
function into separate commands. For example, suppose you did this:
system("/bin/mydate");
If the attacker can reset the IFS variable to " "
, the shell will now parse your system call like this:
bin date
This will run a program named bin
in the current directory.
In Perl, system()
is even more dangerous. Consider a program that performs a function identical to the preceding C++ example:
#!/bin/perl print "Please enter a command:"; $command=<STDIN>; system($command);
Here, Perl slurps up multiple additional commands, whether separated by whitespace or not:
$testsystem.pl
Please enter a command: ls –l;cat /etc/passwd
total 8
-rw-r--r-- 1 root sys 0 Jun 25 00:26 perltest.txt
-rwxr-xr-x 1 root sys 102 Jun 25 00:25 testsystem.pl
root:s1rwxYeA1tqjM:0:0:Big Bad-Ass:/:/bin/csh
shutdown:*:0:0:shutdown,,,,,,:/shutdown:/bin/csh
sysadm:*:0:0:System V Administration:/usr/admin:/bin/sh
diag:*:0:996:Hardware Diagnostics:/usr/diags:/bin/csh
daemon:*:1:1:daemons:/:/dev/null
bin:*:2:2:System Tools Owner:/bin:/dev/null
uucp:*:3:5:UUCP Owner:/usr/lib/uucp:/bin/csh
sys:*:4:0:System Activity Owner:/var/adm:/bin/sh
adm:*:5:3:Accounting Files Owner:/var/adm:/bin/sh
lp:WCI1iUWKqUqDM:9:9:Print Spooler Owner:/var/spool/lp:/bin/sh
nuucp:*:10:10:Remote UUCP User:/var/spool/uucppublic:/usr/lib/uucp/uucico
auditor:*:11:0:Audit Activity Owner:/auditor:/bin/sh
dbadmin:*:12:0:Security Database Owner:/dbadmin:/bin/sh
rfindd:WCI1iUWKqUqDM:66:1:Rfind Daemon and Fsdump:/var/rfindd:/bin/sh
EZsetup:*:992:998:System Setup,,,,,,,:/var/sysadmdesktop/EZsetup:/bin/csh
demos:*:993:997:Demonstration User:/usr/demos:/bin/csh
OutOfBox:*:995:997:Out of Box Experience,,,,,,,:/usr/people/tour:/bin/csh
guest:WCI1iUWKqUqDM:998:998:Guest Account:/usr/people/guest:/bin/csh
4Dgifts:*:999:998:4Dgifts Account,,,,,,,:/usr/people/4Dgifts:/bin/csh
nobody:*:60001:60001:SVR4 nobody uid:/dev/null:/dev/null
noaccess:*:60002:60002:uid no access:/dev/null:/dev/null
nobody:*:-2:-2:original nobody uid:/dev/null:/dev/null
mikal:RFkVtMV5Aj0o6:1110:20:Michael:/usr/people/mikal:/bin/csh
hapless:UhmpfxFtbBGeI:1117:20:Hapless Linux User: /usr/people/hapless:/bin/csh
Hence, you should never build a command line with user input for handling by system()
.
This is true even if you think you've found a solution to control what gets read into STDIN. For example, some Webmasters present the user with check boxes, radio lists, or other read-only clickable elements that have predefined values. This isn't safe either. Nothing prevents a cracker from downloading the HTML source, altering the predefined values, and submitting the form. However, if you insist on doing things this way, at least verify form content:
popen()
is available via the standard I/O library (stdio.h
) and provides a mechanism to execute a shell command from a C or C++ program. As explained on the popen
(3)
man page:
The popen
function opens a process by creating a pipe, forking, and invoking the shell. Since a pipe is by definition unidirectional, the type argument may specify only reading or writing, not both; The resulting stream is correspondingly read-only or write-only. The command argument is a pointer to a null-terminated string containing a shell command line. This command is passed to /bin/sh
using the -c
flag; Interpretation, if any, is performed by the shell.
Do not use popen()
in
Publicly accessible programs or scripts on your Web host
SGID programs or scripts
SUID programs or scripts
popen()
invites various attacks, the most serious of which is the use of metacharacters to trick popen()
into invoking alternate commands. This problem crops up more often than you'd think, even in professionally developed applications. For example, in October 1998, the RSI Advise team reported an IRIX vulnerability to BUGTRAQ about autofsd
:
autofsd
is an RPC server which answers file system mount
and umount
requests from the autofs
file system. It uses local files or name service maps to locate file systems to be mounted. Upon receiving a map argument from a client, the server will attempt to verify if it is executable or not. If autofsd
determines the map has an executable flag, the server will append the client's key and attempt to execute it. By sending a map name that is executable on the server, and a key beginning with a semicolon or a newline followed by a command, unprivileged users can execute arbitrary commands as the superuser. The problem occurs when the server appends the key to the map and attempts to execute it by calling popen. Since popen executes the map and key you specify by invoking a shell, it is possible to force it into executing commands that were not meant to be executed.
(RSI.0010.10-21-98.IRIX.AUTOFSD, http://geek-girl.com/bugtraq/1998_4/0142.html.)
Also, like system()
, popen()
is vulnerable to environment variable attacks. Local attackers may be able to pass commands to the shell or launch malicious programs by altering the Input Field Separator and the $HOME
and $PATH
environment variables.
To foil such attacks, you can access, manipulate, and hard-code shell environment variables from C with the following functions, all available from the standard library (stdlib.h
):
getenv()
—Use this to get an environment variable.
putenv()
—Use this to either change or add an environment variable.
setenv()
—Use this to either change or add an environment variable.
Just how hardcore an approach to take on the environment is debatable, but remember that your C program inherits its environment variables from the shell by which it was executed. If you don't specify sensitive variables, you can inadvertently allow attackers to materially affect your program's execution. (Spafford and Garkfinkel recommend cleaning the environment completely and explicitly creating a new one.)
Table 16.2 describes important shell variables and what they represent.
Table 16.2. bash
Environment Variables and What They Mean
From C, you can access the total environment (all variables currently set) using environ
. As explained on the environ
(5)
man page:
An array of strings called the 'environment' is made available by exec(2)
when a process begins. By convention these strings have the form 'name=value'
.
In the UNIX Programming FAQ, Andrew Gierth offers an example program that grabs all currently set environment variables and prints them out (similar to printenv
and env
) using environ
:
#include <stdio.h> extern char **environ; int main() { char **ep = environ; char *p; while ((p = *ep++)) printf("%s ", p); return 0; }
In Perl, hard-code your environment variables at the top, before processing data, like this:
Failure to specify environment variables or check their length can result in C/C++ buffer overflows. xdat
on AIX 4 didn't check the length of $TZ
, for example, and the resulting overflow bought attackers root access. In a similar vein, in a bug discussed below, setuid
utilities in KDE failed to check the length of $HOME
.
open()
is a native Perl function that opens files. As explained in the Perl perlfunc
documentation, it…
…opens the file whose filename is given by EXPR
, and associates it with FILEHANDLE
. If FILEHANDLE
is an expression, its value is used as the name of the real filehandle wanted.
However, you can also use open()
to open a process (a command):
If you open a pipe on the command "-"
, i.e. either "|-"
or "-|"
, then there is an implicit fork done, and the return value of open is the PID
of the child within the parent process, and 0 within the child process.
Here's an example of using open()
to open a file for processing:
open(DATABASE, "mydatabase.txt"); while(<DATABASE>) { if(/$contents{'search_term'}/gi) { $count++; @fields=split('!:!', $_); print "$fields[1] $fields[2] $fields[3] "; } } close(DATABASE);
Here's an example of using open()
to open a process:
open(PS, "ps|") || die "Cannot open PS
$!";
while (<PS>) {
if(/pppd/) {
$count++;
@my_ppp = split(' ', $_);
kill 1 $my_ppp[0];
print "Your PPP process [PID $my_ppp[0]] has been terminated!
"
}
}
close(PS);
if($count==0) {
print "There is no PPP process running right now
";
}
To open a process using open()
without invoking the shell, try doing this instead:
open(PS, "|-") || exec("ps", "-a"); while (<PS>) { if(/pppd/) { $count++; @my_ppp = split(' ', $_); kill 1 $my_ppp[0]; print "Your PPP process [PID $my_ppp[0]] has been terminated! " } } close(PS); if($count==0) { print "There is no PPP process running right now "; }
Note that problems inherent in invoking the shell are not limited to C and Perl. You should exercise care when performing these tasks in any language. (For example, in Python, if you fail to apply adequate controls, you'll see equally negative results with os.system()
and os.popen()
.)
eval
is a function available in shells and Perl (typically invoked as eval
expression
). As explained in the Perl documentation:
EXPR [
expression
]
is parsed and executed as if it were a little Perl program. It is executed in the context of the current Perl program, so that any variable settings, subroutine or format definitions remain afterwards. The value returned is the value of the last expression evaluated, or a return statement may be used, just as with subroutines.
eval
will execute commands, all arguments passed to such commands, and even additional, sequential, or piped commands. Using eval
is therefore quite risky and offers attackers an opportunity to try a wide range of attacks.
The exec()
function allows you to execute external commands. As explained in the perlfunc
documentation:
The exec()
function executes a system command AND NEVER RETURNS. Use the system()
function if you want it to return. If there is more than one argument in LIST
, or if LIST
is an array with more than one value, calls execvp(3)
with the arguments in LIST
. If there is only one scalar argument, the argument is checked for shell metacharacters. If there are any, the entire argument is passed to /bin/sh -c
for parsing.
This is risky. exec
will execute the command, all arguments passed to it, and even additional, sequential, or piped commands. For this reason, if you use exec
(not recommended), enclose each individual argument in quotes like this:
exec 'external_program', 'arg1', 'arg2'
This will prevent attackers from passing arguments (or commands) onto the list.
Buffer overruns are still another example of how user input can materially alter your program's execution and performance. When you write C programs, be sure to use routines that provide buffer boundary checking. If you don't, attackers may be able to overrun the buffer, causing your program to fault. This can offer attackers an opportunity to execute malicious code.
For example, consider gets()
, which is available via the standard I/O library (stdio.h
) and provides a mechanism to read a line of user input. As explained on the fgetc
man page:
gets()
reads a line from stdin
into the buffer pointed to by s
until either a terminating newline or EOF
, which it replaces with ' '
. No check for buffer overrun is performed.
Here's an example of gets()
in use when the character buffer is set to 20:
/* gets_exa,ple.c – Why not to use gets() */ #include <stdio.h> void main() { char username[20]; printf("Please enter your username:"); gets(username); printf("%s ", username); }
When run, gets_example
reads in username
and spits it back out:
linux6$ gets_example Please enter your username: anonymous anonymous linux6$
But what if the user doesn't enter 20 characters or less? What if he floods gets_example
with garbage like this:
linux6$ gets_example Please enter your username: anonymousaaaaaaaaaaaaaaaa555555555555555555 5555555555555555555555anonymousaaaaaaaaaaaaaaaa555555555555555555555555 5555555555555555 Bus error (core dumped) linux6$
Or even this:
linux6$ gets_example Please enter your username: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Segmentation fault (core dumped) linux6$
In both cases, gets_example
core dumps because, as explained on the gets()
man page…
…it is impossible to tell without knowing the data in advance how many characters gets()
will read and… gets()
will continue to store characters past the end of the buffer.
Attackers search high and low for such holes to exploit so they can run malicious code in unintended memory space.
In addition to gets()
, avoid using any of the following routines:
fscanf()
—Reads input from the stream pointer stream
. In many instances, you can use fgets()
instead.
realpath()
—Expands all symbolic links and resolves references to '/./'
, '/../'
, and extra '/'
characters in the null-terminated string named by path
.
scanf()
—Reads input from the standard input stream stdin
. Try using fgets()
first to get the string and then use sscanf()
on it.
sprintf()
—Writes to the character string str
, but does not check the string's length. Try snprintf()
instead.
strcat()
—Concatenates two strings, and appends the src
string to the dest
string, but does not check string length. Use strncat()
instead.
strcpy()
—Copies a string pointed to be src
to the array pointed to by dest
, but does not check string length. Use strncpy()
instead.
A sobering example of how buffer overruns can jeopardize your system is the sperl5.003
bug, evident on Red Hat Linux 4.2. suidperl
is a tool for securely running setuid
Perl scripts. In May 1997, CERT reported that…
…due to insufficient bounds checking on arguments which are supplied by users, it is possible to overwrite the internal stack space of suidperl
while it is executing. By supplying a carefully designed argument to suidperl
, intruders may be able to force suidperl
to execute arbitrary commands. As suidperl
is setuid
root, this may allow intruders to run arbitrary commands with root privileges.
The problem arose in a function using sprintf()
. To see a detailed analysis of that hole, and to test attack code that demonstrates how attackers exploit buffer overruns, go to http://www.ryanspc.com/exploits/perl.txt.
Other interesting examples include
Netscape Communicator 4.07-4.5 Buffer Overrun—Dan Brumleve found a buffer overrun in specified Communicator versions. When Communicator receives an unknown MIME type, it generates a dialog box that offers you various options. The function that creates the dialog box message uses sprintf()
with a 1KB buffer. Remote Webmasters can use the exploit to execute arbitrary commands on your box. The attack turns Communicator into an interactive shell for remote attackers. To experiment with this exploit, get the source at http://www.shout.net/nothing/buffer-overflow-1/view-buffer-overflow-1.cgi.
rpc.mountd
—rpc.mountd
is a Remote Procedure Call (RPC) server that answers a client request to mount a file system (part of NFS). In August 1998, independent researchers found a buffer overrun in rpc.mountd
that allowed remote attackers to gain privileged access to the target. Check out the explanation and source code at http://pulhas.org/exploits/Linux/mountd4.html.
kde
(K Desktop Environment)—Catalin Mitrofan found an overflow/environment weakness in kde
on Debian. By overloading the HOME
and X
environment variables, attackers can get high enough access to read /etc/shadow
. Get the code at http://hysteria.sk/lists/bugtraq/msg00481.html.
Check the following links to learn more about buffer overflows:
Attack Class:Buffer Overflows, Evan Thomas, University of British Columbia (http://helloworld.ca/1999/04-apr/attack_class.html).
Smashing the Stack for Fun and Profit, Aleph One, excerpted from Phrack 49 (http://aurora.phys.utk.edu/~swb/perlZ/pearls/smash.html).
How to Write Buffer Overflows, by Mudge of L0pht Heavy Industries (http://l0pht.com/advisories/bufero.html).
Buffer Overruns, What's the Real Story?, by Lefty at [email protected]
(http://reality.sgi.com/nate/machines/security/stack.nfo.txt).
Stack Smashing Vulnerabilities in the Unix Operating System, Nathan P. Smith, Computer Science Department, Southern Connecticut State University (http://reality.sgi.com/nate/machines/security/buffer-alt.ps).
Finding and Exploiting Programs with Buffer Overflows, by prym at [email protected]
(http://reality.sgi.com/nate/machines/security/buffer.txt).
Compromised - Buffer - Overflows, from Intel to SPARC Version 8, Mudge from L0pht (http://l0pht.com/advisories/buf.ps).
An Empirical Study in the Reliability of UNIX Utilities, Baron P. Miller, David Koski, Ravi Murthy, Cjin Pheow Lee, Vivekananda, Ajitkumar Natarajan, Jeff Steidl, Computer Science Department, University of Wisconsin (ftp://grilled.cs.wisc.edu/technical_papers/fuzz-revisited.ps.Z).
Try as you might, you can never anticipate every possible combination of characters in a user's input. Most users will input appropriate strings, or those they think are appropriate. But crackers will try exotic combinations, looking for weaknesses in your program. To guard against such attacks, take the following steps:
Ensure that your code uses only those routines that check for buffer length. If it contains routines that don't, insert additional code that does.
Ensure that you explicitly specify environment variables, initial directories, and paths.
Subject your code to rigorous testing. Try overflowing the stack, pushing additional commands onto the argument list, and so on. Essentially, try to crack your own program.
In Perl scripts, screen out metacharacters and validate all user input by enforcing rules that allow only words, as in ~ tr/^[w ]//g
. Note: Many tutorials suggest that you explicitly define forbidden characters (that which is not expressly denied is permitted). Try to avoid doing this. The favored approach is to explicitly define approved characters instead (that which is not expressly permitted is denied). This method is more reliable.
Allowing variable interpolation is very dangerous. Therefore, use single quotes whenever possible. (Any named variable used in a double-quote string is interpolated.)
Also, use taintperl
, which forbids the passing of variables to system functions. taintperl
can be invoked in Perl 4 by calling /usr/bin/taintperl
, and in Perl 5 by using the -T
option when invoking Perl (as in #!/usr/bin/perl -T
).
When you're writing CGI programs, always specify absolute paths. This will prevent attackers from tricking your script into executing an alternate program with the same name.
For example, never do anything like this:
# set up a directory variable $DIR='pwd'; chop($DIR); # and then later on… sub some_function { open(EXTERNAL_SCRIPT, "$DIR/myprogram.pl|); }
Never use relative paths, either. Relative paths point to locations relative to the current directory. Consider this script:
open(DATABASE, "search/data/clients.dat|");
while(<DATABASE>) {
if(/$contents{'search_term'}/gi) {
$count++;
print "$fields[5] $fields[6] $fields[7]<br>
";
}
}
close(DATABASE);
if($count < 1) {
print "No matches!
";
}
This doesn't identify a hard path. If you moved this script, the path leading to clients.dat
would change:
In /var/http
, the script points to /var/http/search/data/clients.dat
.
In /etc/http
, the script points to /etc/http/search/data/clients.dat
.
Instead, point to the absolute path, like this:
open(DATABASE, "/var/http/ourcompany.net/search/data/clients.dat");
while(<DATABASE>) {
if(/$contents{'search_term'}/gi) {
$count++;
print "$fields[5] $fields[6] $fields[7]<br>
";
}
}
close(DATABASE);
if($count < 1) {
print "No matches!
";
}
This way, there's no ambiguity. The script points to one file only: /var/http/ourcompany.net/search/data/clients.dat
.
Never deviate from this rule, even when launching simple programs. For example, suppose you did this:
system("date");
Or even this:
$mydate='date';
If an attacker can alter $PATH
and point to an alternate date
, your script will execute it. If you're dead set on executing programs in this manner, try this instead:
system("/bin/date");
Or this:
$mydate='/bin/date';
Also, consider hard-coding your initial working directory at startup. For this, use chdir
.
chdir()
, available in C from unistd.h
and also a native Perl function, changes the current directory. It can return many errors that might alert you to problems, like whether the target actually exists. As an additional measure, consider following your chdir()
with an lstat()
. This will verify that the target is actually a directory as opposed to a symbolic link.
If your CGI programs create or open files, observe these rules:
Always include error-handling code to warn you if the file isn't actually a file, cannot be created or opened, already exists, doesn't exist, requires different permissions, and so on.
Watch which directories you use to create or open files. Never write a file to a world-writeable or world-readable directory.
Always explicitly set the file's UMASK
.
Set file permissions as restrictively as possible. If the file is a dump of user input, such as a visitor list, the file should be readable only by the processes that will engage that file.
Ensure that the file's name does not have metacharacters in it, and if the file is generated on-the-fly, include a screening process to weed out such characters.
Finally, Table 16.3 lists some interesting tools that can help you test your work.
Table 16.3. Interesting Programming and Testing Tools
Variable | Purpose |
---|---|
lclint | A lint -like checker for ANSI C that checks risky data sharing, ignored return values, null values, memory management errors, and much, much more. For a description of lclint , go to http://www.doc.ic.ac.uk/lab/cplus/lclint/guide.html. To get lclint , go to ftp://ftp.sds.lcs.mit.edu/pub/lclint/guide.tar.gz. |
mem_test | A library for finding memory leaks in C programs. Get it at http://members.iquest.net/~jbuchana/mem_test.html. |
C Inside | A source code viewer that lets you selectively examine the results of preprocessing to determine what macros really expand to. Get it at http://www.thinkage.on.ca/shareware/. |
GNU Nana | A free library providing improved support for assertion checking and logging in C and C++. Learn more at http://www.cs.ntu.edu.au/homepages/pjm/nana-home/. |
Plumber | A tool for identifying memory leaks in C programs. Learn more at http://home.earthlink.net/~owenomalley/plumber.html. |
ObjectManual | Generates HTML documentation for your C++ programs on-the-fly, (especially useful if you're doing professional development). http://www.obsoft.com/Product/ObjMan.html. |
DOC++ | A tool for generating HTML documentation for your C/C++/Java programs on-the-fly (especially useful if you're doing professional development or when you're accountable for the docs). |
cgihtml | A library for writing HTML out from C programs (useful when you don't want to bother coding HTML parsing routines yourself). To get it, go to http://www.eekim.com/software/cgihtml/. |
MIME++ | A C++ class library for parsing, creating, and editing messages in MIME format. Also, it can streamline your work in many instances. Get it at http://www.hunnysoft.com/mimepp/. |
Latro | Scans remote Windows hosts for insecure Perl installations (useful for when you establish a heterogeneous intranet). Get Latro at http://language.perl.com/news/latro-announce.html. |
SCAT | A tool and Application Programming Interface (API) to maintain client state. It is possible to integrate DES (and perhaps PGP or even RSAREF) into SCAT routines. Check out SCAT at http://www.btg.com/scat/scat.html. |
msystem (by Matt Bishop) | Offers secure versions of system(3) , popen(3) , and pclose(3) . Check out msystem at ftp://coast.cs.purdue.edu/pub/tools/unix/msystem.tar.Z. |
crashme | A tool for testing your operating environment software's robustness. In certain cases, it can reveal weaknesses in your programs. Check out crashme at ftp://coast.cs.purdue.edu/pub/tools/unix/crashme/. |
showid | A shell script that records and reports the UID and GID of program while it is executing. Check out showid at ftp://coast.cs.purdue.edu/pub/tools/unix/show_effective_uid. |
worm-src | The source code to the Internet Worm, an excellent example of how buffer overruns (and other attacks) operate. Get it at ftp://coast.cs.purdue.edu/pub/tools/unix/worm-src.tar.gz. |
PAM | Pluggable Authentication Modules allow you to alter how Linux applications perform authentication without actually rewriting and compiling them. Learn more at http://www.interweft.com.au/other/pam/pam.html. |
CGIWrap | A gateway program that allows general users to use CGI scripts and HTML forms without compromising the security of the http server. Scripts run with the permissions of the user who owns the script. Check out CGIWrap at ftp://concert.cert.dfn.de/pub/tools/net/cgiwrap/. |
In addition to the preceding information, there are many online documents that offer excellent secure programming advice. Here are a few:
CGI Security Tutorial, Michael Van Biesbrouck (http://www.csclub.uwaterloo.ca/u/mlvanbie/cgisec/).
How to Write a Setuid Program, Matt Bishop (http://www.cs.ucdavis.edu/~bishop/scriv/1986-loginv12n1.ps).
Robust Programming, Matt Bishop, Department of Computer Science, University of California at Davis (http://www.cs.ucdavis.edu/~bishop/classes/ecs153-98-winter/robust.html).
Security Code Review Guidelines, Adam Shostack (http://www.homeport.org/~adam/review.html).
Shifting the Odds: Writing (More) Secure Software, Steve Bellovin, AT&T Research Murray Hill, NJ (http://www.research.att.com/~smb/talks/odds.ps).
The Unofficial Web Hack FAQ, Simple Nomad (http://www.nmrc.org/faqs/www/index.html).
The World Wide Web Security FAQ, Lincoln D. Stein (http://www.w3.org/Security/Faq/www-security-faq.html).
UNIX Security: Security in Programming, Matt Bishop, SANS '96 (http://www.cs.ucdavis.edu/~bishop/scriv/1996-sans-tut.ps).
Writing Safe Privileged Programs, M. Bishop, Network Security, 1997 (http://www.cs.ucdavis.edu/~bishop/scriv/1997-ns97.ps).
Your main aim is to anticipate every possible contingency that can result from your program's use. Approach your code as a cracker would. Visit cracker sites and study how similar programs have been broken in the past. Apply these principles to your own program and see what happens. This is really the only way to be sure.
3.145.166.149