![]() CEG
499/699:
|
|
| 08/03/00 |
"The infrastructure of the Internet is fundamentally flawed in so many ways that it is a constant source of amazement that it does not collapse under its own weight. But for those of us who depend on the Internet for the competitive advantages it brings, there is little choice but to live with it and struggle to recover from the constant curves it throws us."Fred Cohen, Sandia National Laboratories
widely credited as the first one to scientifically describe computer virus
"All problems fall into one of two categories: Those that can and those that cannot be easily solved. For instance, some of the denial of service attacks ... are a result of the IP protocol's design. Short of implementing a new protocol ..., not much can be done beyond stopgap measures that make particular attacks less effective.
Other difficult problems include network sniffing and spoofing. These result from security-related information being sent in the clear over networks. Then there is the general authentication problem. The difficulty with authentication is that the lowest common denominator is user names and passwords, and that method is generally not sufficient.
Unfortunately, solving these problems requires new hardware, new software, and user training, ... Over the longer term, protocols like IPV6 and IPsec will resolve many of these issues. Of course they may create new ones. ...
The solvable problems are the result of poor planning, programming, and implementation. These can be solved by software vendors ... [by] improv[ing] their coding methodologies. ... "
"... Is code getting better? You could assume that the security holes in
operating systems are the result of poor coding way back when, and that new code
and coding methods do not have the same problem. You would be wrong. Consider
Windows NT and its sorry security state. Or look in our own back yard at
Solaris. Bugs in admintool, NIS+, the volume manager, procfs,
PPP, PAM, and the PCI bus drivers ... prove the point." [Peter Galvin,
Pete's Wicked World column, Sun World Magazine, 1998]
In June 2000, GSA Federal Chief Information Officers Council listed the "The Ten Most Critical Internet Security Threats":
nxt, qinv and in.named
allow immediate root compromise.rpc.ttdbserverd (ToolTalk),
rpc.cmsd (Calendar Manager), and rpc.statd that
allow immediate root compromisesadmind (remote administration access to
Solaris systems) and mountd (controls and arbitrates access to
NFS mounts on UNIX hosts) permit root compromise.The full report (available at http://www.itpolicy.gsa.gov/itleaders/toptenthreats.htm) is worth studying, and system administrators are urged to follow the procedures given there in securing their systems. For our immediate use here, observe how many of these are due to programming errors. Items 1-6, and 9 are programming errors. Items 7, 8, and 10 are configuration errors.
Law 1 (Law of Large Programs) Large programs are even
buggier than their size would indicate.
Corollary 1.1 A security-relevant program has security bugs.
Theorem 3 Exposed machines should run as few programs as possible; the ones that are run should be as small as possible.
Cryptologists and real-time programmers are familiar with doing things this way. Most other programmers are not.
Many programs are written with the assumption that enough resources will be available. So look to see what happens if there is not enough memory and some allocations fail, usually returning NULL from malloc or new. Is it possible for untrusted users to use up all the resources (which becomes a denial-of-service problem)? What happens if the program runs out of fds (and whether it is possible)? What happens if the program cannot fork(), or if its child dies during initialization due to resource starvation?
CGI scripts commonly execute other programs, passing them user data on their command lines. In order to avoid having this data interpreted by the shell (on a Unix system) as instructions to execute other programs, access other files, etc., the CGI script removes unusual characters -- things like '<', '|', '!', '"', etc. You can do this in a fail-open way by having a list of "bad characters" that get removed. Then, if you forgot one, it is a security hole. You can do it in a fail-closed way by having a list of "good characters" that do not get removed. Then, if you forgot one, it is an inconvenience.
Error handling and reporting is an essential part of any programming paradigm. Delicate handling of and recovery from error conditions is an absolute necessity, especially in a third party library.
assert(3) is a macro that accepts a single argument which it treats as a Boolean expression. If the expression evaluates to false, the assert macro prints an error message and terminates the program. Assertions are useful in the developmental stages of programs when verbose error handling is not in place or when a grievous error condition that normally should not happen occurs. One must use assertions, but exiting abruptly even after reporting an error is not acceptable. If a grievous error condition is detected, the code should return error codes to the caller, and let it decide what to do. Code should be able to handle grievous errors well enough to be able to exit gracefully from the top level (if possible). Also, never use structured exception handling as a substitute for writing solid code in the first place.
sprintf(), fscanf(), scanf(), vsprintf(),
realpath(), getopt(), getpass(), streadd(), strecpy(), strtrns(), gets(),
strcpy(), and strcat()
O_EXCL and O_CREAT
flags to assure that the file does not already exist.lstat() to make sure a file is not a link, if
appropriate.
chdir()) to an appropriate
directory at program start.tmpfile() or mktemp()
system calls to create them (although most mktemp() library
calls have problematic race conditions).
system() and popen() system calls.setuid or setgid shell scripts.
getservbyname()
instead.
lint.
gcc -pg -a causes the program to produce a bb.out file
that can be helpful in determining how effective your tests are at covering
all branches of the code.What was originally a minor annoyance, or sometimes even a convenience, can become a security hole when a program is run in a different context. For example, suppose you have a PostScript interpreter that was originally intended to let you preview your documents before printing them. This is not a security-sensitive role; the PostScript interpreter does not have any capabilities that you do not. But suppose you start using it to view documents from other people. Suddenly, the presence of PostScript's file access operators becomes a threat! Someone can send you a document which will delete all your files -- or possibly stash copies of your files someplace they can get at them.
There is a section on robust programming techniques that avoid the buffer overflow exploits in Prabhaker Mateti, "Buffer Overflow", Lectures on Internet Security, www.cs.wright.edu /~pmateti/ Courses/ 499/ Top/ lectures.html. In addition, the following are required reading.
Programs that Must be Robust: The OS kernel. All setuid
and setgid programs. All daemons that accept network connections.
If the kernel has security holes, no amount of checking of system programs is going to make the system secure from attack. However, relatively few kernel bugs are being found and exploited these days.
setuid root programs allow users to gain root privileges.
Daemons allow users to access the system without first getting authenticated. A
network daemon may answer a network request and process it under the daemon's
privileges, not a user's. Therefore, this is another way for users to increase
access, or even gain initial access, to the target system.
In terms of security, kernels are relatively bug-free because of the limited
interfaces available to attack. For instance, Solaris has 210+ system calls
(check /usr/include/sys/syscall.h), and Linux about 190. Compare
that to the thousands of points a hacker has available to attack: sockets,
files, devices, and programs.
Check for "rws----" permissions to see if an executable is setuid
root. Run find / -perm +4000 -print to locate all setuid files. Add
"-user root" in order to find just those that elevate to root.
SYNOPSIS
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
DESCRIPTION
setuid() sets the real-user-ID (ruid), effective-user-ID (euid),
and/or saved-user-ID (suid) of the calling process. The super-user's
euid is zero.
Under Linux, setuid is implemented like the POSIX version
with the _POSIX_SAVED_IDS feature. This allows a setuid
(other than root) program to drop all of its user privi
leges, do some un-privileged work, and then re-engage the
original effective user ID in a secure manner.
If the user is root or the program is setuid root, special
care must be taken. The setuid function checks the effec
tive uid of the caller and if it is the superuser, all
process related user ID's are set to uid. After this has
occurred, it is impossible for the program to regain root
privileges.
Thus, a setuid-root program wishing to temporarily drop
root privileges, assume the identity of a non-root user,
and then regain root privileges afterwards cannot use
setuid. You can accomplish this with the (non-POSIX, BSD)
call setuid.
The following conditions govern setuid's behavior:
o If the euid is zero, setuid() sets the ruid, euid, and suid to
uid.
o If the euid is not zero, but the argument uid is equal to the
ruid or the suid, setuid() sets the euid to uid; the ruid and
suid remain unchanged. (If a set-user-ID program is not
running as super-user, it can change its euid to match its
ruid and reset itself to the previous euid value.)
o If euid is not zero, but the argument uid is equal to the
euid, and the calling process is a member of a group that has
the PRIV_SETRUGID privilege (see privgrp(4)), setuid() sets
the ruid to uid; the euid and suid remain unchanged.
setgid() sets the real-group-ID (rgid), effective-group-ID (egid),
and/or saved-group-ID (sgid) of the calling process. The following
conditions govern setgid()'s behavior:
o If euid is zero, setgid() sets the rgid and egid to gid.
o If euid is not zero, but the argument gid is equal to the rgid
or the sgid, setgid() sets the egid to gid; the rgid and sgid
remain unchanged.
o If euid is not zero, but the argument gid is equal to the
egid, and the calling process is a member of a group that has
the PRIV_SETRUGID privilege (see privgrp(4)), setgid() sets
the rgid to gid; the egid and sgid remain unchanged.
RETURN VALUE
Upon successful completion, setuid() and setgid() returned 0.
On error, -1 is returned, and errno is set appropriately.
Like most man pages, the descriptions of this most famous syscall vary from Unix to Unix. Here is a typical entry. As you can see, it is quite unclear, and many programmers do not study other carefully written setuid programs.
Recommended Reading:
Simson Garfinkel, Gene Spafford Practical Unix and Internet Security, 2nd edition (April 1996), O'Reilly & Associates; ISBN: 1565921488. Errata: http://www.oreilly.com/catalog/puis/errata/ Chapter 23: Writing Secure SUID and Network Programs.
None. Work out at least a few of the exercises in Matt Bishop's article.
These lecture materials are gleaned from many sources. All are presented after careful reading. In some cases, I may have neglected proper attribution. I assure the reader it is not because I claim authorship. Indeed, in the lectures there is hardly any thing new that I have contributed. Suggestions for improvement are always welcome.
| 08/03/00 01:21:35 PM |
| Open Content Copyright © 2000 pmateti@cs.wright.edu | Other Internet Security Lectures by Mateti |