Table of Contents for
Practical UNIX and Internet Security, 3rd Edition

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Practical UNIX and Internet Security, 3rd Edition by Alan Schwartz Published by O'Reilly Media, Inc., 2003
  1. Cover
  2. Practical Unix & Internet Security, 3rd Edition
  3. A Note Regarding Supplemental Files
  4. Preface
  5. Unix “Security”?
  6. Scope of This Book
  7. Which Unix System?
  8. Conventions Used in This Book
  9. Comments and Questions
  10. Acknowledgments
  11. A Note to Would-Be Attackers
  12. I. Computer Security Basics
  13. 1. Introduction: Some Fundamental Questions
  14. What Is Computer Security?
  15. What Is an Operating System?
  16. What Is a Deployment Environment?
  17. Summary
  18. 2. Unix History and Lineage
  19. History of Unix
  20. Security and Unix
  21. Role of This Book
  22. Summary
  23. 3. Policies and Guidelines
  24. Planning Your Security Needs
  25. Risk Assessment
  26. Cost-Benefit Analysis and Best Practices
  27. Policy
  28. Compliance Audits
  29. Outsourcing Options
  30. The Problem with Security Through Obscurity
  31. Summary
  32. II. Security Building Blocks
  33. 4. Users, Passwords, and Authentication
  34. Logging in with Usernames and Passwords
  35. The Care and Feeding of Passwords
  36. How Unix Implements Passwords
  37. Network Account and Authorization Systems
  38. Pluggable Authentication Modules (PAM)
  39. Summary
  40. 5. Users, Groups, and the Superuser
  41. Users and Groups
  42. The Superuser (root)
  43. The su Command: Changing Who You Claim to Be
  44. Restrictions on the Superuser
  45. Summary
  46. 6. Filesystems and Security
  47. Understanding Filesystems
  48. File Attributes and Permissions
  49. chmod: Changing a File’s Permissions
  50. The umask
  51. SUID and SGID
  52. Device Files
  53. Changing a File’s Owner or Group
  54. Summary
  55. 7. Cryptography Basics
  56. Understanding Cryptography
  57. Symmetric Key Algorithms
  58. Public Key Algorithms
  59. Message Digest Functions
  60. Summary
  61. 8. Physical Security for Servers
  62. Planning for the Forgotten Threats
  63. Protecting Computer Hardware
  64. Preventing Theft
  65. Protecting Your Data
  66. Story: A Failed Site Inspection
  67. Summary
  68. 9. Personnel Security
  69. Background Checks
  70. On the Job
  71. Departure
  72. Other People
  73. Summary
  74. III. Network and Internet Security
  75. 10. Modems and Dialup Security
  76. Modems: Theory of Operation
  77. Modems and Security
  78. Modems and Unix
  79. Additional Security for Modems
  80. Summary
  81. 11. TCP/IP Networks
  82. Networking
  83. IP: The Internet Protocol
  84. IP Security
  85. Summary
  86. 12. Securing TCP and UDP Services
  87. Understanding Unix Internet Servers and Services
  88. Controlling Access to Servers
  89. Primary Unix Network Services
  90. Managing Services Securely
  91. Putting It All Together: An Example
  92. Summary
  93. 13. Sun RPC
  94. Remote Procedure Call (RPC)
  95. Secure RPC (AUTH_DES)
  96. Summary
  97. 14. Network-Based Authentication Systems
  98. Sun’s Network Information Service (NIS)
  99. Sun’s NIS+
  100. Kerberos
  101. LDAP
  102. Other Network Authentication Systems
  103. Summary
  104. 15. Network Filesystems
  105. Understanding NFS
  106. Server-Side NFS Security
  107. Client-Side NFS Security
  108. Improving NFS Security
  109. Some Last Comments on NFS
  110. Understanding SMB
  111. Summary
  112. 16. Secure Programming Techniques
  113. One Bug Can Ruin Your Whole Day . . .
  114. Tips on Avoiding Security-Related Bugs
  115. Tips on Writing Network Programs
  116. Tips on Writing SUID/SGID Programs
  117. Using chroot( )
  118. Tips on Using Passwords
  119. Tips on Generating Random Numbers
  120. Summary
  121. IV. Secure Operations
  122. 17. Keeping Up to Date
  123. Software Management Systems
  124. Updating System Software
  125. Summary
  126. 18. Backups
  127. Why Make Backups?
  128. Backing Up System Files
  129. Software for Backups
  130. Summary
  131. 19. Defending Accounts
  132. Dangerous Accounts
  133. Monitoring File Format
  134. Restricting Logins
  135. Managing Dormant Accounts
  136. Protecting the root Account
  137. One-Time Passwords
  138. Administrative Techniques for Conventional Passwords
  139. Intrusion Detection Systems
  140. Summary
  141. 20. Integrity Management
  142. The Need for Integrity
  143. Protecting Integrity
  144. Detecting Changes After the Fact
  145. Integrity-Checking Tools
  146. Summary
  147. 21. Auditing, Logging, and Forensics
  148. Unix Log File Utilities
  149. Process Accounting: The acct/pacct File
  150. Program-Specific Log Files
  151. Designing a Site-Wide Log Policy
  152. Handwritten Logs
  153. Managing Log Files
  154. Unix Forensics
  155. Summary
  156. V. Handling Security Incidents
  157. 22. Discovering a Break-in
  158. Prelude
  159. Discovering an Intruder
  160. Cleaning Up After the Intruder
  161. Case Studies
  162. Summary
  163. 23. Protecting Against Programmed Threats
  164. Programmed Threats: Definitions
  165. Damage
  166. Authors
  167. Entry
  168. Protecting Yourself
  169. Preventing Attacks
  170. Summary
  171. 24. Denial of Service Attacks and Solutions
  172. Types of Attacks
  173. Destructive Attacks
  174. Overload Attacks
  175. Network Denial of Service Attacks
  176. Summary
  177. 25. Computer Crime
  178. Your Legal Options After a Break-in
  179. Criminal Hazards
  180. Criminal Subject Matter
  181. Summary
  182. 26. Who Do You Trust?
  183. Can You Trust Your Computer?
  184. Can You Trust Your Suppliers?
  185. Can You Trust People?
  186. Summary
  187. VI. Appendixes
  188. A. Unix Security Checklist
  189. Preface
  190. Chapter 1: Introduction: Some Fundamental Questions
  191. Chapter 2: Unix History and Lineage
  192. Chapter 3: Policies and Guidelines
  193. Chapter 4: Users, Passwords, and Authentication
  194. Chapter 5: Users, Groups, and the Superuser
  195. Chapter 6: Filesystems and Security
  196. Chapter 7: Cryptography Basics
  197. Chapter 8: Physical Security for Servers
  198. Chapter 9: Personnel Security
  199. Chapter 10: Modems and Dialup Security
  200. Chapter 11: TCP/IP Networks
  201. Chapter 12: Securing TCP and UDP Services
  202. Chapter 13: Sun RPC
  203. Chapter 14: Network-Based Authentication Systems
  204. Chapter 15: Network Filesystems
  205. Chapter 16: Secure Programming Techniques
  206. Chapter 17: Keeping Up to Date
  207. Chapter 18: Backups
  208. Chapter 19: Defending Accounts
  209. Chapter 20: Integrity Management
  210. Chapter 21: Auditing, Logging, and Forensics
  211. Chapter 22: Discovering a Break-In
  212. Chapter 23: Protecting Against Programmed Threats
  213. Chapter 24: Denial of Service Attacks and Solutions
  214. Chapter 25: Computer Crime
  215. Chapter 26: Who Do You Trust?
  216. Appendix A: Unix Security Checklist
  217. Appendix B: Unix Processes
  218. Appendixes C, D, and E: Paper Sources, Electronic Sources, and Organizations
  219. B. Unix Processes
  220. About Processes
  221. Signals
  222. Controlling and Examining Processes
  223. Starting Up Unix and Logging In
  224. C. Paper Sources
  225. Unix Security References
  226. Other Computer References
  227. D. Electronic Resources
  228. Mailing Lists
  229. Web Sites
  230. Usenet Groups
  231. Software Resources
  232. E. Organizations
  233. Professional Organizations
  234. U.S. Government Organizations
  235. Emergency Response Organizations
  236. Index
  237. Index
  238. Index
  239. Index
  240. Index
  241. Index
  242. Index
  243. Index
  244. Index
  245. Index
  246. Index
  247. Index
  248. Index
  249. Index
  250. Index
  251. Index
  252. Index
  253. Index
  254. Index
  255. Index
  256. Index
  257. Index
  258. Index
  259. Index
  260. Index
  261. Index
  262. Index
  263. About the Authors
  264. Colophon
  265. Copyright

Tips on Avoiding Security-Related Bugs

Software engineers define errors as mistakes made by humans when designing and coding software. Faults are manifestations of errors in programs that may result in failures. Failures are deviations from program specifications. In common usage, faults are called bugs.

Why do we bother to explain these formal terms? For three reasons:

  1. To remind you that although bugs (faults) may be present in the code, they aren’t necessarily a problem until they trigger a failure. Testing is designed to trigger such a failure before the program becomes operational...and results in damage.

  2. Bugs don’t suddenly appear in code. They are there because some person made a mistake—from ignorance, from haste, from carelessness, or from some other reason. Ultimately, unintentional flaws that allow someone to compromise your system were caused by people who made errors.

  3. Almost every piece of Unix software (as well as software for several other widely used operating systems) has been developed without comprehensive specifications. As a result, you cannot easily tell when a program has actually failed. Indeed, what appears to be a bug to users of the program might be a feature that was intentionally planned by the program’s authors.[242]

When you write a program that will run as superuser or in some other critical context, you must try to make the program as bug-free as possible because a bug in a program that runs as superuser can leave your entire computer system wide open.

Even when your program will run as an unprivileged user, it’s important to design and implement it carefully, especially if it will be accessed by anonymous or untrusted others. Bugs become vulnerabilities through privilege escalation; an untrusted remote user exploits a bug in a network daemon to gain access as an ordinary local user, and then uses that access to exploit bugs that allow him to act as a privileged user, or even as the superuser.

Of course, no program can be guaranteed to be perfect. A library routine can be faulty, or a stray gamma ray may flip a bit in memory to cause your program to misbehave. Nevertheless, there are a variety of techniques that you can employ when writing programs that will tend to minimize the security implications of any bugs that may be present. You can also program defensively to try to counter any problems that you can’t anticipate now.

Here are some general rules to code by.

Design Principles

  1. Carefully design the program before you start. Be certain that you understand what you are trying to build. Carefully consider the environment in which it will run, the input and output behavior, files used, arguments recognized, signals caught, and other aspects of behavior. Try to list all of the errors that might occur, and how you will deal with them.

    Remember: you will need to design your program. Either you will design the program before you start writing it, or you will design it while you are writing it. You might as well design as much of the program before you write the code. That way, if you decide to change your design in the process, there will be less code to change.

  2. Document your program before you start writing the code. Write a theory-of-operation document for your code, describing what it will do and how it will do it. Outline the major modules. Most importantly, revise this document while you write your program. If you can’t or won’t do that, at least consider writing documentation that includes a complete manual page before you write any code. Doing so can serve as a valuable exercise to focus your thoughts on the code and its intended behavior.

  3. Make the critical portion of your program as small and as simple as possible.

  4. Resist adding new features simply because you can. Add features and options only when there is an identified need that cannot be met by combining programs (one of the strengths of Unix).[243] The less code you write, the less likely you are to introduce bugs, and the more likely you are to understand how the code actually works.

  5. Resist rewriting standard functions. Although bugs have been found in standard library functions and system calls, you are much more likely to introduce newer and more dangerous bugs in your versions than in the standard versions.

  6. Be aware of race conditions. These can manifest themselves as a deadlock, or as failure of two calls to execute in close sequence.

    Deadlock conditions

    Remember: more than one copy of your program may be running at the same time. Consider using file locking for any files that you modify. Provide a way to recover the locks in the event that the program crashes while a lock is held. Avoid deadlocks or “deadly embraces,” which can occur when one program attempts to lock file A and then file B, while another program already holds a lock for file B and then attempts to lock file A.

    Sequence conditions

    Be aware that your program does not execute atomically. That is, the program can be interrupted between any two operations to let another program run for a while—including one that is trying to abuse yours. Thus, check your code carefully for any pair of operations that might fail if arbitrary code is executed between them.

    In particular, when you are performing a series of operations on a file, such as changing its owner, stating the file, or changing its mode, first open the file and then use the fchown( ), fstat( ), or fchmod( ) system calls. Doing so will prevent the file from being replaced while your program is running (a possible race condition). Also avoid the use of the access( ) function to determine your ability to access a file: using the access( ) function followed by an open( ) is a race condition, and almost always a bug.

  7. Write for clarity and correctness before optimizing the code. Trying to write clever shortcuts may be a stimulating challenge, but it is a place where errors often creep in. In practice, most optimizations have little visible effect unless the code is executed in time-critical places (e.g., interrupt handling) or is invoked tens of thousands of times per day. Meanwhile, the penalties for writing dense, difficult-to-understand code can include longer testing time, increased maintenance effort, and more lurking bugs. Spending two days of hacking to save 100 instruction cycles per day is also a very poor return on investment.[244]

Coding Standards

  1. Check all of your input arguments. An astonishing number of security-related bugs arise because an attacker sends an unexpected argument or an argument with an unanticipated format to a program or a function within a program. A simple way to avoid these kinds of problems is by having your program always check all of its arguments. Argument checking will not noticeably slow down most programs, but it will make them less susceptible to hostile users. As an added benefit, argument checking and error reporting will make the process of catching non-security-related bugs easier.

  2. When you are checking arguments in your program, pay extra attention to the following:

    • Check arguments passed to your program on the command line. Check to make sure that each command-line argument is properly formed and bounded.

    • Check arguments that you pass to Unix system functions. Even though your program is calling the system function, you should check the arguments to be sure that they are what you expect them to be. For example, if you think that your program is opening a file in the current directory, you might want to use the index( ) function to see if the filename contains a slash character (/). If the filename contains the slash, and it shouldn’t, the program should not open the file.

    • Check arguments passed to your program via environment variables, including general environment variables (e.g., HOME) and such variables as the LESS argument.

    • Do bounds checking on every variable. If you only define an option as valid from 1 to 5, be sure that no one tries to set it to 0, 6, -1, 32767, or 32768. If string arguments are supposed to be 16 bytes or less, check the length before you copy them into a local buffer (and don’t forget the room required for the terminating null byte). If you are supposed to have three arguments, be sure you have three.

  3. Check all return codes from system calls. Practically every single Unix operating system call has a return code. Check them! Even system calls that you think cannot fail, such as write( ), chdir( ), and chown( ), can fail under exceptional circumstances and return appropriate return codes. When the calls fail, check the errno variable to determine why they failed. Have your program log the unexpected value and then cleanly terminate if the system call fails for any unexpected reason. This approach will be a great help in tracking down problems later on.

    If you think that a system call should not fail and it does, do something appropriate. If you can’t think of anything appropriate to do, then have your program delete all of its temporary files and exit.

  4. Have internal consistency-checking code. Use the assert macro if you are programming in C. If you have a variable that you know should be either a 1 or a 2, then your program should not be running if the variable is anything else.

  5. Include lots of logging. You are almost always better off having too much logging rather than too little. Report your log information into a dedicated log file. Or consider using the syslog facility so that logs can be redirected to users or files, piped to programs, and/or sent to other machines. And remember to do bounds checking on arguments passed to syslog( ) to avoid buffer overflows.

    Here is specific information that you might wish to log:

    • The time that the program was run.

    • The UID and effective UID of the process.

    • The GID and effective GID of the process.

    • The terminal from which it was run.

    • The process number (PID). If you log with syslog, including the LOG_PID option in the openlog( ) call will do this automatically.

    • Command-line arguments.

    • Invalid arguments, or failures in consistency checking.

    • The host from which the request came (in the case of network servers).

    • The result of an ident lookup on that remote host.

  6. Always use full pathnames for any filename argument, for both commands and data files.

  7. Check anything supplied by the user for shell metacharacters if the user-supplied input is passed on to another program, written into a file, or used as a filename. In general, checking for good characters is safer than checking for a set of “bad characters” and is not that restrictive in most situations.

  8. If you are expecting to create a new file with the open call, then use the O_EXCL | O_CREAT flags to cause the routine to fail if the file exists. If you expect the file to be there, be sure to omit the O_CREAT flag so that the routine will fail if the file is not there.[245]

  9. If you think that a file should be a file, use lstat( ) to make sure that it is not a link. However, remember that what you check may change before you can get around to opening it if it is in a public directory.

  10. If you need to create a temporary file, consider using the tmpfile( ) or mkstemp( ) functions. tmpfile( ) creates a temporary file, opens the file, deletes the file, and returns a file handle. The open file can be passed to a subprocess created with fork( ) and exec( ), but the contents of the file cannot be read by any other program on the system. The space associated with the file will automatically be returned to the operating system when your program exits. If possible, create the temporary file in a closed directory, such as /tmp/root/. mkstemp( ) does not delete the file and provides its name as well as its file handle, and thus is suitable for files that need more persistence.

Warning

Older versions of mkstemp( ) could create world-writable files. Make sure yours doesn’t. Never use the mktemp( ) or tmpnam( ) library calls if they exist on your system—they are not safe in programs running with extra privilege. The code as provided on most older versions of Unix had a race condition between a file test and a file open. This condition is a well-known problem and is relatively easy to exploit.

  • Make good use of available tools. If you are using C and have an ANSI C compiler available, use it, and use prototypes for calls. If you don’t have an ANSI C compiler, then be sure to use the -Wall option to your C compiler (if supported) or the lint program to check for common mistakes. Use bounds checkers, memory testers, and any other commercial tools to which you have access.

Things to Avoid

  1. Don’t use routines that fail to check buffer boundaries when manipulating strings of arbitrary length.

    In the C programming language in particular, note the following:

    Avoid

    Use instead

    gets( )

    fget( )

    strcpy( )

    strncpy( )

    strcat( )

    strncat( )

    sprintf( )

    snprintf( )

    vsprintf( )

    vsnprintf( )

    Use the following library calls with great care—they can overflow either a destination buffer or an internal, static buffer on some systems if the input is “cooked” to do so:[246] fscanf( ) , scanf( ), sscanf( ), realpath( ), getopt( ), getpass( ), streadd( ), strecpy( ), and strtrns( ). Check to make sure that you have the version of the syslog( ) library that checks the length of its arguments.

    There may be other routines in libraries on your system of which you should be somewhat cautious. Note carefully if a copy or transformation is performed into a string argument without benefit of a length parameter to delimit it. Also note if the documentation for a function says that the routine returns a pointer to a result in static storage (e.g., strtok( )). If an attacker can provide the necessary input to overflow these buffers, you may have a major problem.

  2. Don’t design your program to depend on Unix environment variables. The simplest way to write a secure program is to make absolutely no assumptions about your environment and to set everything explicitly (e.g., signals, umask, current directory, environment variables). A common way of attacking programs is to make changes in the runtime environment that the programmer did not anticipate.

    Thus, you should make certain that your program environment is in a known state. Here are some of the things you may want to do:

    • If you absolutely must pass information to the program in its environment, then have your program test for the necessary environment variables and then erase the environment completely.

    • Otherwise, wipe the environment clean of all but the most essential variables. On most systems, this is the TZ variable that specifies the local time zone, and possibly some variables to indicate locale. Cleaning the environment avoids any possible interactions between it and the Unix system libraries.

    • You might also consider constructing a new envp and passing it to exec( ), rather than using even a scrubbed original envp. Doing so is safer because you explicitly create the environment rather than try to clean it.

    • Make sure that the file descriptors that you expect to be open are open, and that the file descriptors you expect to be closed are closed. Consider what you’ll do if stdin, stdout, or stderr is closed when your program starts (a safe option is usually to connect them to /dev/null.) For example, components of Wietse Venema’s Postfix mailer often include this C snippet:

      for (fd = 0; fd < 3; fd++)
              if(fstat(fd, &st) == -1 && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
                      msg_fatal("open /dev/null: %m");
    • Ensure that your signals are set to a sensible state.

    • Set your umask appropriately.

    • Explicitly chdir ( ) to an appropriate directory when the program starts.

  3. Do not provide shell escapes in interactive programs (they are not needed).

  4. Never use system( ) or popen( ) calls. Both invoke the shell, and can have unexpected results when they are passed arguments with funny characters, or in cases where environment variables have peculiar definitions.

  5. Do not create files in world-writable directories.

  6. Don’t have your program dump core except during your testing. Core files can fill up a filesystem and contain confidential information. In some cases, an attacker can actually use the fact that a program dumps core to break into a system. Instead of dumping core, have your program log the appropriate problem and exit. Use the setrlimit( ) function or equivalent to limit the size of the core file to 0. While you’re at it, consider setting limits on the number of files and stack size to appropriate values if they might not be appropriate at the start of the program.

Before You Finish

  1. Read through your code. After you have written your program, think of how you might attack it yourself. What happens if the program gets unexpected input? What happens if you are able to delay the program between two system calls?

  2. Test it carefully for assumptions about the operating environments. For example:

    • If you assume that the program is always run by somebody who is not root, what happens if the program is run by (Many programs designed to be run as or can cause security problems when run as root, for instance.)

    • If you assume that the program will be run by root, what happens if it is not run as root?

    • If you assume that the program always runs in the /tmp or /tmp/root [247] directory, what happens if it is run somewhere else? What if /tmp/root is a symlink? What if it doesn’t exist?

  3. Test your program thoroughly. If you have a system based on SVR4, consider using (at the least) tcov, a statement-coverage tester (and if your system uses GNU tools, try gcov). Consider using commercial products, such as Centerline’s CodeCenter and Rational’s PurifyPlus (from personal experience, we can tell you that these programs are very useful). Remember that finding a bug in testing is better than letting some anonymous attacker find it for you!

  4. Have your code reviewed by another competent programmer (or two, or more). After she has reviewed it, “walk through” the code with her and explain what each part does. We have found that such reviews are a surefire way to discover logic errors. Trying to explain why something is done a certain way often results in an exclamation of “Wait a moment . . . why did I do that?”

    Tip

    Simply making your code available for download is not the same as having a focused review! The majority of code published on the Web and via FTP is not carefully examined by competent reviewers with training in security and code review. In most cases, the people who download your code are more interested in using it, or porting it to run on their toaster than they are in providing meaningful code review. Keep this in mind about code you download, too—especially if someone claims that the code must be correct because it has had thousands of downloads.

  5. If you need to use a shell as part of your program, don’t use the C shell. Many versions have known flaws that can be exploited, and nearly every version performs an implicit eval $TERM on startup, enabling all sorts of attacks.

    We recommend the use of ksh (used for some of the shell scripts in this book). It is well-designed, fast, powerful, and well-documented (see Appendix C). Alternatively, you could write your scripts in Perl, which has good security for many system-related tasks.

Remember: many security bugs are actually programming bugs, which is good news for programmers. When you make your program more secure, you simultaneously make it more reliable.



[242] “It’s not a bug, it’s a feature!”

[243] For some reason, people writing new software for Unix (and especially Linux) have forgotten this basic principle of Unix.

[244] Donald Knuth said: “Premature optimization is the root of all evil.” Although “all evil” may be a bit extreme, it does seem to be at the root of a great number of programming errors.

[245] Note that on some systems, if the pathname in the open( ) call refers to a symbolic link that names a file that does not exist, the call may not behave as you expect. This scenario should be tested on your system so you know what to expect.

[246] Not all of these are available under every version of Unix.

[247] We use /tmp/root with the understanding that you have a directory /tmp/root automatically created by your startup scripts, and that this directory has a mode of 0700. Your /tmp directory should have mode 1777, which prevents ordinary users from deleting the /tmp/root directory.