http://www.lst.de/~okir/blackhats/
以下是整理该书中我认为比较重要的东西。
Note that strncpy does not copy the terminating NUL byte if the destination buffer is too small. The proper way of using it goes like this:
char buffer[BUFSZ];Admittedly, this is something every book on software design tells you on page three. But in a security context, it nevertheless bears repeating because if you have the code once, you will be able to go over it with a fine tooth comb, looking for problems such as buffer overflows. If you have the same code scattered all over your project, in several copies, the motivation to do so (and the concentration with which you perform this) may be less, resulting in less secure code.
Usually, it's best to close all open file descriptors except the standard ones (standard input, output and error) attached to descriptors 0, 1 and 2, respectively. The following code tries to do that in a portable way:
int fd;The close-on-exec flag tells the kernel to close the file when executing another program. The purpose of this flag is exactly to prevent the kind of information leak described above. Assume you've opened a file using fopen, then you can set the flag like this:
void set_cloexec(FILE *fp)The child process, on the other hand, closes the reading end of the pipe, and attaches the writing side to its standard output and standard error (file descriptors 1 and 2) using the dup2 system call. It then proceeds to drop privileges as described above, and execute the external program.
int fds[2], pid;It is therefore important that whenever you execute another program from a setuid application, you should clean up the environment. Be sure that you take a whitelist approach that passes only known harmless variables instead of blacklisting variables that are known to be dangerous - you might miss one that turns out to open a huge hole.
The following code runs the slip-up script with a predefined environment, except for the LANG variable which is copied from the caller's environment. The crucial part here is the use of execve rather than execv or execl. The latter invoke the sub-process with the current environment (which might have been poisoned by the user), while execve takes an array of name=value pairs and passes that as the sub-commands environment.
You should therefore make it a habit to ensure that file descriptors 0, 1, and 2 are valid, right at the start of your main procedure and before you do anything else. One common approach is:
while (1) {The best cure I know is to check the file's owner using lstat. Consider the following code:
snprintf(filename, sizeof(filename),At first glance, this looks like the typical race condition I was ranting about above. Of course, many things can happen between the lstat call and the open call. But in this case, the attacker will not be able to replace the file with something else because of the sticky bit on /tmp! If the information returned by lstat tells us the file is owned by us, we know we're safe because the attacker cannot remove whatever we just looked at.
Note that the code snippet above works for everything except setuid applications; in a setuid application getuid will return the user ID of the user who invoked the program, i.e. the (potential) attacker.To make a long story short: the most watertight way of enforcing that certain requests can only be sent by local processes is to use a second socket (often called control socket because it's dedicated to special control tasks), and restrict who can connect to it. With UDP and TCP sockets, you can do this by binding the socket to address 127.0.0.1 and port xyz. Then, a remote attacker can send packets to port xyz on your machine as much as he might - they won't be delivered to your service because the control socket will only accept packets with a destination address of 127.0.0.1.
Therefore, it is a good idea to always make sure that the address information returned in a struct hostent is actually what you expect it is by doing something like this:
If you don't care about supplementary groups, you can clear the list of groups altogether by using setgroups(0, NULL).
If your server is also supposed to change its root directory, the call to chroot must happen after initgroups() and before setuid().
There are several defenses against these attacks. One is administrative; which is to configure the firewall to drop all packets sent to the broadcast address of some (sub-)network. Which is admittedly tedious, because the firewall admin needs to be involved every time an existing network is split up into two smaller subnets (because this creates a new broadcast address). However, this is the only defense against the smurf attack. Today, there are even several ``research'' projects on the net that regularly scan large portions of the Internet for these ``open multiplier'' networks, and notify the administrative contacts of those found vulnerable.
An application should protect itself against the UDP echo attack by ignoring any UDP packets from ports below 512. Applications normally do not send packets from such a low port unless they explicitly bind to it. However, ports in this range are reserved for network servers. And coincidentally, all known services that are prone to such ping-pong effects live in that range. This is how inetd's builtin services such as echo, chargen and time defend themselves against the echo attack today. They simply drop any UDP packets from port numbers less than 512.
Does this make any difference? Yes, there's a small but important one. Assume you've made it setuid, and there's a bug in it that gives the attacker uid uucp. The program he attacked is owned by uucp as well, so he can replace it with a modified version that behaves just like the original one,11.2 but ``steals'' the uids of all users who invoke the program. Who knows, maybe one day the super user will come along and run this utility?
This can be avoided by making the program setgid uucp and giving it mode 555 permissions. An attacker cracking this program will not be able to modify the binary because all he gets out of cracking it is group uucp privilege.
Capability | Description |
CAP_CHOWN | Change owner of any file |
CAP_DAC_OVERRIDE | Override any file permissions (DAC = Discretionary Access Control) |
CAP_KILL | Send signals to any process |
CAP_NET_BIND_SERVICE | Bind to any UDP/TCP port below 1024 |
CAP_NET_RAW | Open raw and packet sockets |
CAP_IPC_LOCK | Lock shared memory segments |
CAP_SYS_RAWIO | Allow ioperm and iopl (used to access e.g. hardware registers) |
CAP_SYS_CHROOT | Allow use of chroot |
CAP_SYS_TIME |
Allow setting the system clock
|