http://www.perlmonks.org?node_id=306556

dvergin has asked for the wisdom of the Perl Monks concerning the following question:

I have been asked to render an opinion as to the security of a CGI script that appears to be cobbled together from some fairly old bits (witness the use of cgi-lib.pl).

I am poor at sniffing out vulnerabilities in code written in ways I would not have chosen in the first place so I have two questions of those more practiced in this: 1)Is the following material vulnerable from a security perspective? 2)What is some sample input that would demonstrate that vulnerability? (The boss-man will want to know.)

The whole script these fragments come from has a variety of issues (e.g.: the possibility of simultaneous writes to the data file <Update> and the simplistic invocation of the sendmail pipe </Update>) but my specific question here is regarding any security issues.

The original script lacks '-T' taint checking (this will be rectified <Update> which will, of course, require some sort of untainting of greater or lesser degree </Update>). So I guess my question boils down to whether there is a problem with printing potentially tainted form data to a file and to the sendmail pipe -- and how can I quickly demonstrate any vulnerabilities <Update> and code appropriate untainting </Update>.

<Update> The script does no sanity checks on the data in the '%in' hash </Update> and I am assuming that cgi-lib.pl does nothing to untaint the values it passes from the html form.

#!/usr/bin/perl require "cgi-lib.pl"; &ReadParse; $mailprog = '/usr/sbin/sendmail'; # ...omitted cruft open(FILE, ">>somefile.txt") || die "Can't find thedatabase\n"; print FILE "$in{'itemName'}|$in{'itemDate'}|etc..etc..\n"; close(FILE); # ...more code passes open (MAIL, "|$mailprog -t") || die "Can't open mail program\n"; print MAIL "misc hard-coded email header stuff here\n"; # ...etc, etc. print MAIL "Name: $in{'myName'}\n"; print MAIL "Contact Information: $in{'contact'}\n"; # ...more of same close(MAIL); # ...

------------------------------------------------------------
"Perl is a mess and that's good because the
problem space is also a mess.
" - Larry Wall

Replies are listed 'Best First'.
Re: Vetting a CGI script
by hardburn (Abbot) on Nov 12, 2003 at 17:37 UTC

    It should reset $ENV{PATH} (which taint mode will force you to do anyway).

    You will need to escape any pipe characters in the data from the client before it is printed to the file.

    I suggest always using the three-element form of open that was made available in perl 5.6.0, though in this case it's not a big deal.

    If you happen to be printing any user input in the e-mail headers, be sure to be very strict about what is allowed into them. About a month ago, we caught a spammer using one of our CGIs. The trick used was to put a new line in the to field followed by To: anyaddress@example.com, which was interpolated into the e-mail header. This would have allowed any address to be spammed, but the CGI appended our own domain name to the to field before sending, so all that happend was a lot of bounces. This made our e-mail admin very grumpy until we ran more strict validation on the fields, but it could have been worse. There were other fields that the spammer could have used that were also placed directly into the headers that didn't have anything appended to them. We are fortunate that the spammer wasn't quite that smart.

    ----
    I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
    -- Schemer

    : () { :|:& };:

    Note: All code is untested, unless otherwise stated

      In this case all the email header data is hard-coded in the script. Can anything bad be done with external data that is piped to sendmail for the body (after the "\n\n" that follows the header)?

      ------------------------------------------------------------
      "Perl is a mess and that's good because the
      problem space is also a mess.
      " - Larry Wall

Re: Vetting a CGI script
by idsfa (Vicar) on Nov 12, 2003 at 18:02 UTC

    For arbitrary input, consider that you are offering to set up a spam relay:

    $in{myName} = "\n.\nMAIL FROM fake@dev.null\n" . "RCPT TO poor@target.domain\n" . "DATA\n$spam_message_goes_here\n\.\n" . "MAIL FROM junk@throwaway\nRCPT TO nobody@nowhere\n" . "DATA\n\nJust junk to avoid throwing an error"

    ... or anything else someone might want to do with access to your SMTP server. (Moral of story: Net::SMTP ... but I assume you are doing this as justification for a rewrite anyway.)


    My parents just came back from a planet where the dominant life form had no
    bilateral symmetry, and all I got was this stupid F-Shirt.
      Perfect! That's exactly the kind of thing I was looking for.

      So I would recommend the use of the sendmail '-i' option. Given that and the fact that all the email header data is hard-coded, is there any way to case grief with user data only going into the email body?

      ------------------------------------------------------------
      "Perl is a mess and that's good because the
      problem space is also a mess.
      " - Larry Wall

        I'd really recommend not doing that either. For one, the syntax for that call looks like:

        $message = "From: blah\nTo: blah\nSubject: blah\n\nmessage\n"; open (SENDMAIL,"|sendmail -i); print SENDMAIL $message; close(SENDMAIL);

        Updated:
        (Yes, I know it could be done with multiple print's, but I hate dribbling information through a pipe ...)

        Which is a bigger rewrite than moving to Net::SMTP:

        use Net::SMTP; $smtp = Net::SMTP->new('mailhost'); $smtp->mail($ENV{USER}); # print MAIL "MAIL FROM ..." $smtp->to('postmaster'); # print MAIL "RCPT TO ..." $smtp->data(); # print MAIL "DATA\n"; $smtp->datasend("line 1\n"); # print MAIL ... $smtp->datasend("line 2\n"); # print MAIL ... $smtp->datasend("line 3\n"); # print MAIL ... $smtp->dataend(); $smtp->quit;

        Updated: (duh ... typing "first" w/o a "second")
        Second, invoking a whole 'nother app (sendmail) when you've already got perl running is just a bunch more overhead on your server. You then also have any security holes in 'sendmail -i' to remember to look for.


        My parents just came back from a planet where the dominant life form had no
        bilateral symmetry, and all I got was this stupid F-Shirt.
      That attack isn't a problem unless he was talking directly to the receiving mail server over SMTP. sendmail will encode the period and unless the receiving mail server is completely broken, the message will just have some SMTP commands in it.

      Update: I forgot about the -i flag to sendmail to prevent the rogue period from ending the message. The SMTP commands shouldn't be interpreted by sendmail but the period can be used to shorten the message sent.

Re: Vetting a CGI script
by calin (Deacon) on Nov 12, 2003 at 17:35 UTC
    I don't know anything about cgi-lib.pl. However, if I were to code this fascist-style, these thoughts would cross my mind:

    • Use the three-or-more-argument version of open. It's safer.
    • Turn $mailprog into a lexically scoped variable or constant (use constant ...). Messing with it is unlikely, but I'm following my fascist mindset.
    • Passing improperly untainted data to sendmail screams SPAM GATEWAY!
Re: Vetting a CGI script
by hmerrill (Friar) on Nov 12, 2003 at 17:12 UTC

    I've been using Perl for the last 6+ years, and I don't even remember cgi-lib.pl :)

    I don't see a problem printing possibly tainted data to a file, but it really depends on what that file will be used for. I suppose you could say that untainting that data would be the responsibility of the program that *reads* that file. But my inclination would be to untaint the data before writing it to the file. I don't have much experience with -T taint mode, but I believe that if you intend to add the -T flag, that you'll have to untaint all external data (like form data) coming in first before using it anyway - so it's kind of a mute point.

    As far as piping tainted data to sendmail, I thought I had read something somewhere about the flags to sendmail having something to do with security precautions, but I can't seem to find that. Read the perldocs on "How do I send mail?" by doing

    perldoc -q mail
    at a command prompt and search (using the forward slash "/") for "sendmail" - you'll find it. There are some slight sendmail flag differences between your code and what they suggest - I'm not sure if those differences are significant.

    HTH.
Re: Vetting a CGI script
by Zed_Lopez (Chaplain) on Nov 12, 2003 at 18:08 UTC
    Security is really hard to impose after the fact. Ditch it, start over, use FormMail from the nms script archive.
Re: Vetting a CGI script
by dvergin (Monsignor) on Nov 12, 2003 at 20:17 UTC
    In addition to the helpful responses posted here, I appreciate Limbic~Region's reminder in the ChatterBox that we can't rely on the HTML form to impose length and content restrictions. It is easy to download a local copy of the page and modify it (or even to manually construct a posting URL).

    In particular, Limbic~Region mentions the possibility of potential modification of the $CGI::DISABLE_UPLOADS and $CGI::POST_MAX values.

    For me, this possibility places a greater burden on the receiving script. A)Nothing dangerous (like a malevolent cgi script) should be uploaded to a place where it could be invoked from the web and B)Massive return values from the form fields should probably be simply discarded rather than attempting to process/forward/store them.

    In the current case, as best I can tell, uploads are not an issue. I assume someone could construct and post a response containing a dangerous or large upload. But without intervention by the receiving script, I presume it would simply languish in a tmp directory. A large, ininvited upload might slow the server down a bit or threaten the harddisk capacity, but there are other places (e.g. httpd.conf) to deal with that.

    On the other hand, over-large form data needs to be anticipated and handled appropriately within the receiving script.

    ------------------------------------------------------------
    "Perl is a mess and that's good because the
    problem space is also a mess.
    " - Larry Wall

      Contemplate this, a one-time colleague wrote a CGI that had once had an upload feature in it, the code was taken out, but his temp directory was in fact the cgi-bin directory. Someone who recognised the cgi (it was foolishly placed on a CGI download site) knew that he could upload a script to replace one that was already in the directory - but no longer used. Can you see where I am going! Somebody did just that, they uploaded a script, then they uploaded another, called them innocuously from the browser and siezed control of the machine, looked in the code for the original CGI, got the info for the database, downloaded his entire database (about 2 million records) and then proceeded to delete the database and then force a backup (another of the CGI's functions) that cleared his backup of data as well. By the time he got to the office the next day it was all over. The company went belly up and he along with it.

      PROTECT YOURSELF FROM UPLOADS!

      I learned a lot about CGI security from that experience.
      jdtoronto

        Great story. I'm bookmarking that.

        And consider this: even if you don't have a million records to protect, you have computer resources to protect. If someone breaks into your machine, they can use it as a staging platform against someone else (or a warez site), diverting the blame (at least temporarily) toward you.

        -- Randal L. Schwartz, Perl hacker
        Be sure to read my standard disclaimer if this is a reply.

        Ok, I see how they were able to take over the machine in that example, but assuming you *have* to offer file upload, how can you protect against that? It's been a long time since I've worked with CGI's that offer file upload capability, and I think when we did, we handled it with CGI.pm. I don't recall specifying a "temp" directory. Is the solution to the problem you presented fixed by specifying a temp directory that is *NOT* the cgi-bin directory?

        I just read some in 'perldoc CGI' - there's a section titled "-private_tempfiles" that describes security issues with file uploads and the snifibility of info being written to those temp files.

        I guess I'm wondering what the *right* way to program file uploads - is using CGI.pm to do that a good way?

        TIA.