Far too many security flaws are still being written in code that needs a temporary file for some reason. These notes attempt to illustrate briefly how writing files into /tmp or similar untrustworthy areas is problematic, and should be avoided in favor of modules that handle the troublesome details in a more secure and transparent fashion.
Insecure temporary files allow malicious local users of a system to either delete files they otherwise should not have access to, or possibly run arbitrary code, depending on how the temporary file is used. The attacks are usually opportunistic, requiring the setup of trap that will be triggered when the insecure code is run by something else.
Temporary directories, if needed, can also be managed with File::Temp. Consider also the File::AtomicWrite module, which offers additional features around File::Temp.
Insecure Code
The following examples show various insecure file handling code, and comment on attacks possible against such poor code.
- Static file under /tmp.
- Arbitrary file overwrite.
- Possible arbitrary code execution.
- Process number or other random data in filename.
While not perl, the worst temporary file handling the author has ever seen was found in a useradd implementation for Mac OS X. The user addition script is run as the superuser account, which allows full access to the system. The script wrote out a perl program to crypt.pl under /tmp, then proceeded to run that program as the superuser, using code similar to the following.
# shell code that blindly writes to static location under /tmp
cat <<'EOF' >/tmp/crypt.pl
#!/usr/bin/perl -l
$password = shift;
@salt = ('.', '/', 0 .. 9, 'A' .. 'Z', 'a' .. 'z');
print crypt $password, join '', (@salt)[rand 64, rand 64];
EOF
# execution of code written above
chmod +x /tmp/crypt.pl
PWHASH=`/tmp/crypt.pl $PASSWORD`
There are numerous security problems here, and while use of mktemp(1) to write crypt.pl to a secure location, the script would be better written in a language that has the crypt function to being with.
Any file can be overwritten though the use of a symbolic link. The /tmp/crypt.pl is linked by an attacker to a particular target, for example /sbin/init to render the system inoperable once the flawed useradd is run.
$ ln -s /sbin/init /tmp/crypt.pl
In addition to the trivial file overwrite problem, there is also a race condition, whereby an active attacker may be able to slow the system and insert arbitrary code into /tmp/crypt.pl after the script writes the file but before the script can execute it.
Removing the /tmp/crypt.pl first worsens the situation, as there is now an arbitrary file delete problem (via a malicious symbolic link), in addition to the arbitrary code execution race condition.
To allow multiple instances of a script to run, or as a flawed attempt to avoid security problems, code will often write out a file to a filename based on the process identification number or other random data. These approaches also suffer from arbitrary file overwrite and possible code execution problems, as an attacker can simply create thousands of symbolic links based on what the process number or random data could be, that all point at the file to overwrite.
open TMPFILE ">/tmp/work.$$";
print TMPFILE …
Working against the above code, the attacker would create symbolic links using something like the following.
perl -e 'for (1..66666) { system qw(ln -sf /sbin/init), "/tmp/work.$_" }'
While large variation in the filenames may make an attack impractical or greatly reduce the chances of the attack working, the variation is no substitution for using code that opens a non-existent file for exclusive access, as done by the code listed in the solutions section, below.
Security Audit
New code can easily be searched for improper temporary file handling, either manually or via a custom program that has been coded to find such problems. Site policy should prohibit the use of such code, or require a rewrite using known secure techniques before the code is put into production. Be sure to also check for uses of the TMP or TMPDIR environment variables.
$ grep -rl /tmp *
…
Lock Files under /tmp
Never locate lock files under a system-wide temporary directory. These locations allow for needless local security problems: an attacker could create a denial of service by creating the lock file as a different user, or depending on the lock handling code and various race conditions, cause arbitrary file truncation or removal against the user that creates the lock file. Instead, locate lock files under /var/run or similar directories that offer the minimum permissions necessary to carry out the file-based locking.
Ensuring only one copy of a perl script is running at a time on PerlMonks lists various other options. Also consider better job schedulers, such as CFEngine, which include run-only-a-single-copy support as a native feature.
Solutions
For Perl code, there are several solutions that provide secure temporary file handling. General security advice for Perl can be found in the perlsec documentation.
- Keep the data in memory.
- Secure temporary file handling.
- Calling an external editor.
Use modules such as File::MMap or IO::String to keep the data in memory. This may not be an option in all cases, but should be faster than touching the filesystem, and will avoid any filesystem related security problems. In recent versions of Perl, the open function now has options to open files in memory or to anonymous files on the filesystem. For more information, see perlopentut and perlfunc.
Instead of writing the file under a /tmp directory, use a module that takes appropriate security measures to ensure the file handle obtained is safe, such as File::Temp. This module uses similar code to that found in the mktemp(1) command.
The following example outlines modifying a file, with preservation of the original Unix ownership and permissions. My File::AtomicWrite can perform these various steps via either a simple write_file call or an object oriented interface.
#!/usr/bin/perl -w
use strict;
use File::Temp qw(tempfile);
my $target_filename = shift || die "usage: $0 file-to-replace\n";
if ( !-f $target_filename ) {
die "error: not a file: name=$target_filename\n";
}
my ( $tmp_fh, $tmp_filename ) =
tempfile( "$target_filename.XXXXXX", UNLINK => 0 );
if ( !defined $tmp_fh ) {
die "error: no temporary file\n";
}
# TODO write to temp file, -z "is it empty" santiy checks, etc. here
# preserve permissions on target file by applying them to temp file
my ( $mode, $uid, $gid ) = ( stat($target_filename) )[ 2, 4, 5 ];
$mode = $mode & 07777;
chmod $mode, $tmp_filename;
chown $uid, $gid, $tmp_filename;
rename $tmp_filename, $target_filename;
Another option is to create a directory, either at runtime, or a known directory created when the software is installed, or by configuration management. This directory can either be a File::Temp module tmpdir under /tmp, or a known path, such as /var/run/… or the like. What additional sanity checks are necessary will depend on the software, whether multiple users must have access, and so forth.
If the temporary file is being written out so that an external editor can alter the data, use my Term::CallEditor Perl module, which in turn uses File::Temp to handle temporary files.
Deleting Open Files
While the deletion of an open file is not strictly related to temporary file creation, it bears mentioning. A file that is deleted or removed—that is, on Unix, that has its directory entry eliminated by the unlink(2) system call—while still held open by a filehandle in a process, can be written to by the process. This can lead to eventual filesystem exhaustion of all free space. Therefore, care must be taken to not remove files (usually logfiles, and usually by deficient logrotation schemes) that are being written to. Use tools such as lsof to discover these files, or on certain operating systems other tricks to recover the content of these files.