http://qs1969.pair.com?node_id=142949

The vast majority of scripts I write are CGI-related in some way. Naturally, I've found that placing a script online, where anyone can take a shot at it, can be a little unnerving at times. Luckily Perl eliminates many security problems (buffer overflows for one :), but there are still many areas of concern. I've attempted to list the essential security practices that, when followed, enable one to write more secure CGI scripts.

Taint Mode - I use this in every CGI script I write. It has helped me catch many mistakes that I would have otherwise overlooked. Sometimes though it gets me feeling a little too secure, as perlsec says "The tainting mechanism is intended to prevent stupid mistakes, not to remove the need for thought."

User Input - As a CGI Course I recently read stated "always trust your users. Never trust their input." Legitimate users can send unexpected data, malicious users will send everything you can think of - and more. This includes manipulating hidden form field values. Don't depend on the referrer (or "referer" ;) either, for it can be easily spoofed as well.

Also worthy of noting, when filtering user input don't try to think up every possible symbol/combination to filter out. Instead, simply state the form of the data your script will except and return an error for everything else.

Error Messages - Don't give out more information than necessary. If a user supplies an incorrect password for a legitimate user name, print out 'Incorrect Login' not 'Incorrect Password.' Also make sure to get all those use CGI::Carp qw/fatalsToBrowser/; out of production code.

Make %ENV safer - From perlsec "... set $ENV{'PATH'} to a known value, and each directory in the path must be non-writable by others than its owner and group." It also goes on to suggest that including something along the lines of delete %ENV{qw(IFS CDPATH ENV BATH_ENV)}; would be a good idea.

Encryption - Essential for any sensitive data. There are a whole bunch of modules on cpan dealing with encryption but currently I have very limited experience with them. Any advice from the more experienced monks on this subject would be appreciated.

Those are the essential security practices that I currently employ. Inevitably I have overlooked something so please reply with any suggestions for improving this node. Thanks.

Replies are listed 'Best First'.
Re: Essential CGI Security Practices
by BazB (Priest) on Feb 02, 2002 at 19:34 UTC

    use CGI or die; - Don't try and reinvent the CGI module - it works, and it's been well tested.
    You should avoid attempting to roll your own module - it's unlikely to be any better, and if it is, then you've spent too much time on it :-)

    Think beyond Taint and warnings - Although taint mode, warnings and so forth should not be overlooked - make sure the rest of the code is written in a sensible/secure manner.

    Don't use a script if you don't know what it's doing - Probably more for newbie Perl users who think that Matt's Perl scripts are good.
    Spend time reading through a script if you didn't write it yourself and don't know the quality of the author's other work.

    I think that's about all I can think of for the moment.

    BazB.

Re: Essential CGI Security Practices
by dws (Chancellor) on Feb 02, 2002 at 21:11 UTC
    Good list, to which I would add:

    Peer Review - Apply several pairs of competent eyeballs to the code. A skilled colleague, reading the code with a "how would I break this" hat on is a great way to uncover subtle problems.

    Data Security - Keep sensitive, missions critical data off of the web server box, especially if you're dealing with credit cards. Encryption isn't always enough.

      I'd like to add Subsection 1 to Peer Review. This section would be called QA.

      QA - Put your code into a replication of your production environment and get a dedicated QA person to go thru' your application as if it was live on the web. A skilled QA person is a seriously good weapon to have in your arsonal.

      While youre there you may as well set up a dedicated UAT to test your application as well. Keep in mind you shouldnt tell your QA 'guy' about how or what your app does as this may influence the nature of their testing.

        As much as I hate working with QA; partially because where I worked they often served as HCI/UI/HF, (not so) clearly when something is in testing is not the best time to redesign it; I'll have to ++.

        --
        perl -pe "s/\b;([st])/'\1/mg"

Re: Essential CGI Security Practices
by pjf (Curate) on Feb 03, 2002 at 02:07 UTC
    I'm occasionally employed to do security audits for programs both written in Perl and in other languages, and there's one mistake that consistantly pops up and earns me my wage. I've had to give the same piece of advice many many times:

    If you're dealing with a database, quote your values - properly.

    If you're using DBI and placeholders, then you should never have this problem. Alas, I've seen far too much code that will bomb unexpectedly when Ms O'Donnell enters her surname, or will cost thousands when someone requests a username of '; rollback; begin; drop table customers; commit;. (Note the leading tick.)

    If you're doing database work and don't know about the DBI module and placeholders, then go and read up on them now. They're worth it. Really.

    Cheers,

    Paul Fenwick
    Perl Training Australia

Re: Essential CGI Security Practices
by footpad (Abbot) on Feb 03, 2002 at 06:20 UTC

    And I would add:

    • Don't accept more than is absolutely necessary. Example: MSA's formmail.pl can be used as a spam relay because it accepts recipient as a CGI parameter. Don't do that.

      Only accept the minimum number of parameters you need.

    • Don't trust anything from the client--period. I've seen cases where CGI scripts claim to be secure because they check REFERER, which is trivial to spoof.

    • Don't ever use a script that tells you to chmod a publically accessible file to 777 (Example: here).

    • Be highly suspicious of code that's been updated one or fewer times in the last several years. (Theory: No one was defending against DoS attacks in 1996.)

    Not that I'm picking on anyone, of course. However, I have been helping a buddy run down various spammers that are playing on his system. Guess where the currently identified problems have come from?

    --f

Re: Essential CGI Security Practices
by gellyfish (Monsignor) on Feb 04, 2002 at 12:02 UTC

    As far as the CGI::Carp qw/fatalsToBrowser/ goes I would suggest an alternative to removing it altogether. CGI::Carp has had the facility to alter the output message for a quite a while - you can supply a coderef to a subroutine that will be called with the error message and which should print the text of the message to be output - you can set a $DEBUGGING variable to determine whether the actual error message gets output:

    use CGI::Carp qw(fatalsToBrowser set_message); use vars qw($DEBUGGING); BEGIN { $DEBUGGING = 1; my $error_handler = sub { my $message = shift; print "<h1>Oooh I got an error</h1>"; print $message if $DEBUGGING; } set_message($error_handler); }

    This allows you to easily switch on or off the detailed error messages and means you don't have to take the 'or die' out of potentially hundreds of lines of code.

    /J\

Re: Essential CGI Security Practices
by belg4mit (Prior) on Feb 02, 2002 at 22:34 UTC
    I'm not sure about that Error Messages, or at least the example. I know I always find it incredibly frustrating to get meaningless fluff back as error messages from a website. If the error has some meat to it I might be able to remedy the thing myself by altering the form data or hand-parsing the URL.

    Simply saying "Invalid Login" doesn't buy you any securty if you're sending in the clear over the wire, so why make it harder on the user? There are other ways of handling brute-force attacks that you are expressing worry about here (think escalating delays for failures).

    On the other hand, if you are truly paranoid minimizing the information you betray about your system is probably a good thing e.g. paths, server information (HTTP headers, etc.) (unfortunately?) that means no "Powered by Apache" feathers either ;-)

    As for %ENV I stand by "Re: perlsec question".

    --
    perl -pe "s/\b;([st])/'\1/mg"

      I think you're confusing input validation with error messages here. A script should produce meaningless fluff for errors a visitor cannot fix anyway - like could not open /path/to/config/file. That stuff belongs in the server error log and nowhere else. Of course it is useful to tell the user what went wrong if his input was rejected for some reason, but that's not quite the same as an error message. As Ovid wrote in his tutorial, your script may break at any - even unforseen - point, so you have no control about what information a visitor may see if you indiscriminately set fatalsToBrowser.

      Makeshifts last the longest.

        Are you replying to the right node? If so, I say nothing of fatalsToBrowser. And Invalid password/Invalid login is something the user can fix, and it is not really input validation as you cannot (usually) do it programmatically i.e. verify that the user has in fact authenticated himself. Also my discussion of paths (your point about open) was seperate, following "on the other hand", therefore we are in accordance.

        --
        perl -pe "s/\b;([st])/'\1/mg"

      I wouldn't go so far as to say that "Invalid login" fails to buy any security - it prevents users from trivially determining whether a username is valid or not, thus significantly increasing the search space for a brute-force attack. Not a silver bullet by any means (not even a very shiny one, really), but still enough to be significant in many cases.

      (Yeah, escalating delays are good, too, but a little trickier to implement in an environment, such as CGI, where you can't reliably maintain state.)

      I know I always find it incredibly frustrating to get meaningless fluff back as error messages from a website.

      Would it be less frustrating to get, "Error committing to table: incorrect number of fields"?

      For errors the use can maybe fix (e.g., username and password don't match), I try to give a meaningful and complete explanation of the problem. (At least, in theory that's what I intend to try to do.) For internal errors, I like to do something like the following:

      mydie("Error Condition One in Section Gimmel");

      My mydie subroutine starts out by explaining that there is some internal problem, that it is probably not the user's fault, that it's something the webmaster needs to fix, and that he might be able to do so more easily given the technical error code below. Then I give contact info for whoever maintains the script, and print out the string that was passed by the caller, labelled as a Technical Error Code. (The important thing about this string is that it is unique, and I can grep for it to find exactly where the problem was encountered.) All of this can be nicely formatted in a <div class=\"error\">...</div> so that the site CSS can easily style it as desired. This is MUCH nicer to the user than "Internal Server Error", but it doesn't give a potential malicious user a great deal of information, other than how to contact the admin. (Then you just have to make sure this person is not susceptible to social engineering... but you have to do that anyway)


      $;=sub{$/};@;=map{my($a,$b)=($_,$;);$;=sub{$a.$b->()}} split//,".rekcah lreP rehtona tsuJ";$\=$ ;->();print$/