Cover V12, I09

Article

sep2003.tar

Computing Securely

Randal L. Schwartz

Security is everybody's business. You may ask yourself, "Why should I take security seriously? I don't have anything on my system that's worth exploiting." Well, that's exactly what the bad guys want you to believe.

Whether you think you have useful data or not, your box provides an identity tied to you, and can be used to mislead the people in pursuit of another exploit, or completely cover the tracks, leaving you holding the bag. And, your machine has resources, such as CPU, disk, and network interface, that can be abused by the bad guy to stage larger attacks, such as distributed denial-of-service attacks or monitoring traffic from a new vantage point.

Because security is everyone's business, let's look at the most common exploits, and what we need to do about them, focusing on the Perl aspects of those points:

Keep Your Software Current and Secure

The latest Perl releases almost always include bug fixes and code enhancements that improve the security of your system. This was especially true in the great transition from Perl 5.003 to Perl 5.004. If you aren't running at least 5.004, now would be a very good time to just abandon that old code.

Also note that many of the CPAN libraries get updated frequently, and some of those updates are also security related. If you aren't using CPAN.pm's r function on a regular basis, you may be leaving yourself vulnerable to the latest attack.

Write Code You Can Understand

If you don't know what your code is doing, how do you know your code isn't doing something exploitable or harmful? There's a lot of noise out there saying that "Perl is unmaintainable", but I will argue that "too many people write unmaintainable Perl needlessly". Well-written and documented Perl coded with sound engineering principles is actually quite easy to update and modify.

If you've inherited an ugly codebase, please make it a high priority to get your boss to let you rewrite your code.

Never Trust Input Data

Exploits generally happen because some part of the data was trusted to be within a certain range or certain shape, and a bad guy did something else instead. Don't trust your input data. Verify that it contains acceptable characters of the right length or right shape. Obviously, Perl's regular expressions help quite a bit here. Also look at things like Email::Valid and Regex::Common in the CPAN to help you with validation.

Additionally, Perl's taint mode can help you track when incoming data has somehow leaked all the way through to some output-affecting operation without being validated. But buggy (or worse, blind) validations can defeat these checks, so don't rely on taint checks to do your work for you.

Don't Turn Data into Arbitrary Filenames

Another common exploit occurs when input data (either unchecked or improperly checked) is used as part of a filename. When you do something like this, be extra cautious. For example, when you allow an input parameter to select a file within a directory, be sure the input filename doesn't look like: ../../../../../etc/passwd.

Keep Code and Data Separate

One security mantra I recite is "don't let code become data, or data become code". When your executable code can be accessed like data, the bad guys might be able to determine algorithms or locations of secrets. But worse, if the bad guys can get data they control to become code, you've lost the battle. Be sure you completely separate where your code is located, and where your data is stored. For example, don't put data files in your Web's CGI directory -- that's just asking for trouble.

Don't Let Buffers Overflow

While Perl has no known buffer overflow exploits, you might be using Perl to call programs that do have problems. Be careful to limit the size of data you pass to programs when you use system or backticks or qx//.

Beware the NUL Byte

Similarly, Perl is fine with a string that contains a NUL ("\0") byte. But many of the system calls and child programs are not. Again, be very careful that you don't permit such a character to be created (and it's trivial from CGI parameters, for example) and then passed on where it will be misinterpreted.

Don't Leave "debug-mode" Enabled

The famous Robert Morris "Internet Worm" exploited a mostly enabled "debug" mode in sendmail to leap from system to system. If you have a debug mode or testing mode, be sure it gets turned off when your system is in production. Don't simply exclaim "but I might need that if something breaks". Fine, turn it on when something breaks, but not before.

Don't Be Too Chatty in Error Messages

Certainly, the user wants to know when something breaks, but usually the user seeing the error message isn't the person who has to fix the code. They don't need to know the precise SQL that triggered the error, or the name of the database being accessed, and so on, because that stuff is all very useful information to a bad guy.

The most frequent violation I see of this principle is when use CGI::Carp qw(fatalsToBrowser) is enabled on production code. This is wrong, very wrong. The random user at the other end of the HTTP connection needs only to be told that "something went wrong" and "we're looking into it", and maybe a unique timestamp so they can report the error. Everything else should be captured in an error log somewhere, not sent to the user for exploit.

Watch for Timing Exploits

Most decent programmers grasp the execution of their program as a single thread. But it takes extra discipline and thinking to understand all the places where a program can break (accidentally and deliberately) when multiple instances of the program are executing.

While this topic could be an entire article in itself, the two main points here are to generate good temporary filenames (using File::Temp is a good start), and flock your shared data to prevent incompatible simultaneous updates (using the flock function).

Generate Cryptographically Strong Session IDs

Especially in a Web environment, you'll need to have session IDs to track that subsequent page hits are all related. Don't use guessable session IDs, because a bad guy might be able to hijack another user's session if the session ID is guessed, possibly accessing previously entered payment credentials or other secure information. Apache::Session contains an example of a decent session ID generator, but you might also consider Math::TrulyRandom and other similar modules to generate very, very hard-to-guess numbers.

Hide your Passwords

Don't put your passwords into your scripts! From time to time, someone will say, "hey, can I have the code for that cool thing?", and without thinking, I'll attach the code to my reply mail. Until I made a habit of putting passwords into a separate file, I revealed my access codes more than once. Putting the passwords into a separate file also permits the file to be lightly encrypted, although anyone with access to both the code and the file can obviously decrypt the data trivially.

Know the Protocols

Understand that HTTP basicauth security transmits passwords in the clear. Tools are readily available to sniff the network traffic on a segment and display these passwords. If you care about security, be sure you are using SSL (https:// URLs) for anything that needs to be authenticated securely. Even if you think you're on a secure wire in-house, consider the user at the Wi-Fi access point at the local coffee shop or bookstore, which is trivial to sniff.

Beware HTML Attacks

User-uploadable HTML can trigger "Cross-Site Scripting" attacks, permitting one bad guy to steal the credentials of other innocent victims visiting the same site. User-uploadable HTML can also execute arbitrary code if the page is server-side-include parsed.

If you have a Web application like a message system, chat room, or guestbook, escape the HTML (using HTML::Entities), or control the permitted HTML very carefully (using tools made from HTML::Parser, for example).

Know What You're Executing

Have a healthy distrust for scripts from amateur sources. The early Web days made heroes out of the early adoptors of Perl, and unfortunately, their legacy is usually a collection of poorly written Perl scripts using old and unaudited techniques. Look at the code, and if you can't understand it, or if you find it looks bad, then don't use it! If you must use it, ask for help.

Avoid the Shell

The multi-argument version of system or exec permits a child process to be launched without a shell being involved. This is good because nearly every non-alphanumeric character means something special to most shells, and can trigger command invocations that you didn't intend. For example:

system "gzip $somefile";
might do a lot more than invoke gzip on a file, if that filename contained backquotes, vertical bars, newlines, semicolons, and so on. So use this instead:

system "gzip", $somefile;
Now that a shell can't be involved, the invocation is much more secure.

Be Careful about your PATH

And just which gzip was being invoked in that last example? Think about your PATH, including where it was set, and how it might change. Watch out in particular for a trailing . (current directory) in your path, or worse, a leading .!

Be Careful about INC and PERL5LIB

The require operator (on which use is built) looks at @INC to determine the list of scanned directories. This list is built-in, but can be affected by arbitrary user code and the setting of environment variables such as PERL5LIB. If a bad guy can control the list, they can replace strict.pm with their own code, and that'd be a bad thing. Don't let that be an exploit!

Use Built-ins Rather than Calling a Subprocess

Far too often, I've seen a child process used when it wasn't necessary. For example:

chomp(my $now = 'date');
system "rm", $somefile;
These are bad from a performance perspective, but also from a security view, as they can both be done "in house":

my $now = localtime;
unlink $somefile;
thus avoiding an expensive launch of a program. Yes, but which program? The PATH exploit makes these both rather vulnerable to mistakes.

Know the Language

You should also continue to master Perl if you want to ensure security. For example, if alarm bells don't immediately go off in your head when you see something like this:

$input = /(\w+)/;
my $keyword = $1;
then you need to keep studying. The problem with this code is that when the match fails, $1 is left over from a previous match. This kind of code can be used as a security exploit, if the attacker can access the source code or have an idea that this is happening. It's code that "looks right" but definitely isn't.

Further Information

I've just barely scratched the surface in this article. Learning about security is an ongoing process, especially since for every countermeasure, someone is working right on a new exploit. For Perl security, start with the perlsec manpage included in the standard distribution.

If you've got the inclination, spend at least a month or two reading the BUGTRAQ, CERT, and RISKS mailing lists. If you thought that a virus or two a month was a lot, you might be surprised at the dozens of daily exploits listed on these mailing lists.

And finally, search the Web for things like "security FAQ" or "CGI security FAQ". You'll see a lot of common themes in these documents, but I generally find one or two new interesting items every time I look. We can't all be security experts, but we're all responsible for security. Until next time, enjoy!

Randal L. Schwartz is a two-decade veteran of the software industry -- skilled in software design, system administration, security, technical writing, and training. He has coauthored the "must-have" standards: Programming Perl, Learning Perl, Learning Perl for Win32 Systems, and Effective Perl Programming. He's also a frequent contributor to the Perl newsgroups, and has moderated comp.lang.perl.announce since its inception. Since 1985, Randal has owned and operated Stonehenge Consulting Services, Inc.