Apache DevCenter
oreilly.comSafari Books Online.Conferences.

advertisement


Apache::CodeRed
Pages: 1, 2, 3

Let's review the module's logic, looking at some of the code that I used to make Apache::CodeRed work:



  1. Get the IP address of the incoming HTTP request.

    This is easy to do in mod_perl. $r offers us a wealth of information about the current HTTP transaction, including the IP address from which the request originated:

    my $remote_ip_address = $r->get_remote_host();

  2. Try to look up the DNS name associated with this number.

    Once I had the remote computer's IP address, I needed to retrieve its host name. This "reverse DNS" lookup is normally quite easy to do; from the Linux command line, I can simply say

    nslookup 192.168.1.1

    and my local DNS server will respond with the name for that number. But I wanted to do this from within a Perl program, without having to use "fork" or "system", which can cause problems when working with mod_perl.

    So I decided to use the Net::DNS module, written by Michael Fuhr and available on CPAN. I first created a new instance of a DNS resolver object:

    my $res = new Net::DNS::Resolver;

    I then double-checked that $remote_ip_address really contained an address, rather than a host name (which $r->get_remote_host() can sometimes return):

    if ($remote_ip_address =~ /^[\d.]+$/)

    Finally, I looked up the host name, either putting it in $remote_hostname or letting the user know that we didn't manage to find one:

    my $dns_query_response = $res->search($remote_ip_address);

    if ($dns_query_response)
    {
    foreach my $rr ($dns_query_response->answer)
    {
    next unless $rr->type eq "PTR";
    $remote_hostname = $rr->rdatastr;
    }
    }
    else
    {
    my $dns_error = $res->errorstring;
    $r->warn("CodeRed: Failed DNS lookup of " .
    "'$remote_ip_address' ('$dns_error')");
    }

    Notice how we get a list of responses to our query, only some of which (the "PTR" records) have the reverse DNS information we're looking for. We thus loop through the result code until we find one of type PTR, which will give us the host name associated with $remote_ip_address.

  3. Send e-mail to SecurityFocus with the host name or IP address.

    We now have enough information to notify SecurityFocus of the Code Red 2 infection. In fact, we're only missing one thing: the name of the local time zone. SecurityFocus explicitly asks people submitting reports to include a time zone; luckily, the Time::Zone module has a tz_name() function that returns the local time zone name:

    my $time_zone_name = tz_name();

    Armed with this information, we composed an e-mail message to SecurityFocus, and sent it to them with Milivoj Ivkovic's Mail::Sendmail module from CPAN. Sending mail couldn't be easier than Mail::Sendmail makes it; while it doesn't include all of the bells and whistles of Mail::Sender, it's more than adequate for sending simple text messages. (And it works just fine with qmail, despite the slightly misleading name.)

    Here's how we'll send the e-mail:

    my %sf_mail =
    ( To => $security_focus_address,
    CC => $cc_address,
    From => $from_address,
    Subject =>
    "CodeRed infection on '$remote_hostname': Automatic report",
    Message => $sf_message);

    my $sf_sendmail_success = sendmail(%sf_mail);

    If we have problems sending the e-mail, we send a warning to the Apache error log with $r->log, and return DECLINED to Apache. Returning DECLINED means our handler didn't do anything, and another handler (including Apache's default handler) may opt to do something with this HTTP request.

  4. Get the MX (mail exchanger) host associated with this domain.

    In an ideal world, we would be able to send e-mail to the host that originated the HTTP request. Unfortunately, life is more complicated than that: Not all hosts run SMTP servers, and not all e-mail servers are configured to accept e-mail for their domain. To send e-mail to the proper authorities, we must use DNS to find the MX host for a particular domain. Note that domains have MX hosts, but regular hosts do not. This means that we need to find the domain for a host, which can be a tricky business.

    Apache::CodeRed implemented a quick and dirty solution: We begin with $remote_hostname, and remove successive parts until we finally hit on an MX host. So we first try to get the MX for www.example.com (the infected host), and then try to get the MX for example.com (the domain). Here is how I implemented the algorithm in Perl:

    my @mx = ();
    my @hostname_components = split /\./, $remote_hostname;
    my $starting_index = 0;

    while ($starting_index < @hostname_components)
    {
    my $host_for_mx_lookup =
    join '.',
    @hostname_components[$starting_index ..
    $#hostname_components];

    @mx = mx($res, $host_for_mx_lookup);

    if (@mx)
    {
    last;
    }
    else
    {
    $starting_index++;
    }
    }

    if (! @mx)
    {
    my $dns_error = $res->errorstring;

    $r->warn("CodeRed: No MX records for '$remote_hostname':" .
    "'$dns_error'. Exiting.");
    return FORBIDDEN;
    }

  5. Finally, send e-mail to a number of administrative e-mail addresses at the domain's MX host, warning the system administrators that one of their computers has been infected by Code Red 2.

    Now that we know the MX address, we can send e-mail to the postmaster, webmaster, and administrator responsible for the site. We compose another message with Mail::Sendmail, returning FORBIDDEN to Apache. Apache, upon receiving this return code from our handler, sends an appropriate response to the user's browser.

Pages: 1, 2, 3

Next Pagearrow





Sponsored by: