Comments on How To Log Emails Sent With PHP's mail() Function To Detect Form Spam

How To Log Emails Sent With PHP's mail() Function To Detect Form Spam If you are running a webserver you might have faced the problem already: somewhere on your server is a vulnerable contact form or CMS system written in PHP that gets abused by spammers to send emails trough your server. If you have more than a few websites, it is a pain to detect which of the sites is vulnerable and sends the spam emails. This tutorial explains the installation of a small wrapper script which logs email messages sent trough the PHP mail() function.

56 Comment(s)

Add comment

Please register in our forum first to comment.

Comments

By:

Thank you for the hint! I modified the tutorial to correct the problems.

By: George

The suggestion in this article is a malicious user's dream! The code contains several deficiencies which will readily grant a user shell access to the server!

 

By: jm

Please submit corrections instead of just cry "do not use this"

By: Anonymous

Thanks ;)

By: Anonymous

I can't find out the logs being generated in the file mail.form file .. But the mail reaches the mail id specified in the file.

By: hakanpolatkan

Hi Till,

First, thank you very much for sharing your howto document and it definetely seems that it'll make my work easier.

But i think it might need to be made some little corrections about the phpsendmail file. The code doesnt run unless the correction below is made.

Line 36: return shell_exec($cmd); -> return shell_exec($command);

 And;

I get the message below when the system runs the script.

sendmail: fatal: Recipient addresses must be specified on the command line or via the -t option

Line 20: $command = 'echo "'.$mail.'" | '.$sendmail_bin.' ';

But it runs when i change the line above as 

$command = 'echo "'.$mail.'" | '.$sendmail_bin.' -t';

 

 

best regards

By: George

Yes, this is a gaping security hole.

Read up on command line injection to understand why.

By: David F. Skoll

This script is one huge security hole.  Do not use it!  It invokes shell_exec() and system() on untrusted input without sanitization.

 

By:

is this really a security hole?

 anyways, I'm not getting anything logged in the logs. am I missing something?

By: Jm

Instead of criticize, you should better submit a patch...

 

By: Jack

Thanks for the feedback that this is a huge security hole.  Can you advise how one could log these mail items without having a huge security hole like the above?  I would readily appreciate both logging mail and not having a huge security hole.

By: Jason Clifford

Instead of trying to bolt the barn door after the horse has bolted it is far better to patch PHP to prevent anyone using it to inject implicit recipients (http://www.lancs.ac.uk/~steveb/patches/php-mail-reject-implicit-recipients-patch/)

 You can also patch mail() to specify in the email which website actually sent the message to make it easier to trace who is actually responsible for any spam -  http://www.lancs.ac.uk/~steveb/patches/php-mail-header-patch/

 I've used both these patches and they really help a lot.

By:

Thank you for sharing your script!

By: Brooks

The log, if you copied the script exactly, will be in /tmp/mail_php.log, also assuming that you set this to 777.  If you have a test mail script and it is not writing to the log, you will want to make sure the script is hitting your mail server as well, to ensure it is running through the entire script and not erroring out, a simple way to do this is to grep the email that you are sending to out of your mail.log, usually in /var/log/maillog, /var/log/mail.log, or if you have plesk, /usr/local/psa/var/log/maillog

 so in the case of plesk:

 tail /usr/local/psa/var/log/maillog -f | grep [email protected]

 Enter that, then submit the form, if you don't see the shell window start to show results, the mail is not going through, and you may need to start choppping the code a bit.

By:

I changed the logfile location from temp directory to log directory in the phpsendmail file.

//$logfile = '/tmp/mail_php.log';
$logfile = '/var/log/mail.form';

I got the log correctly in the mail.form file, but all the records are on the same line. How to add a line break at the end of the line? Logging the mail sent through PHP seems to be really a good idea.

By:

Change the line:

 $logline .= trim($line).' ';

 to:

 $logline .= trim($line)." \n";

By: Brooks

Hello,

    Excellent script, I've had to modify this for a couple of my older servers that are still running PHP4 (Plesk does not really enjoy when you update...anything).  And, I could think of nothing better to do than to share this.

 

Touch /usr/local/bin/phpsendmail

Touch /var/log/php_mail_log

chmod +x /usr/local/bin/phpsendmail

chmod 777 /var/log/php_mail_log

 

The content of /usr/local/bin/phpsendmail:

 

#!/usr/bin/php
<?php
$sendmail_bin='/usr/sbin/sendmailauth';
$logfile='/var/log/php_mail_log';

//* Get the email content
$logline = '';
$pointer = fopen('php://stdin', 'r');

//Open the log file
$handle = fopen($logfile,"a+");

//Prepare the date
$date = date('m-d-y :: h:i:s A --');

// get email content functionality
while ($line = fgets($pointer)) {
        if(preg_match('/^to:/i', $line) || preg_match('/^from:/i', $line)) {
                $logline .= trim($line).' ';
        }
        $mail .= $line;
}

//* compose the sendmail command
$command = 'echo ' . escapeshellarg($mail) . ' | '.$sendmail_bin.' -t -i';
for ($i = 1; $i < $_SERVER['argc']; $i++) {
        $command .= escapeshellarg($_SERVER['argv'][$i]).' ';
}

$content = "$date " . $_ENV["PWD"]  . " $logline " . "\n";
fwrite($handle,$content);
fclose($handle);


//* Execute the command
return shell_exec($command);

?>
 

   Please note the sendmail bin, yours will probably just be /usr/sbin/sendmail and not /usr/sbin/sendmailauth, this is a customized sendmail script for SMTP Authentication.   The reason for this  entry would be PHP4 does not have the ability to use file_put_contents, there are ways to add it to the code, but add a lot more lines of code when fopen and fwrite can do the same thing.  I have also varied the date command a little bit for ease of reading, and added a line break so that everything does not string into one long line, once again for ease of reading. Below is a sample of the output:

 09-11-20 :: 07:01:50 -- /var/www/vhosts/domain/ To: address From: address

   Thank you very much for the original code as it makes my job much easier, and I hope this contribution is well accepted.

    Brooks

     Nexpoint Web Hosting

     http://www.nexpoint.net

By: Anonymous

thank you very much, it worked perfectly, very useful article.

By:

Thanks for the modified script. I tested it and updated it in the tutorial.

By: David Goodwin

As mentioned above; the code is quite insecure.

I've hopefully fixed the problems with it, and include the code below.

thanks

 David.

 <code>

#!/usr/bin/php
<?php

/**
  This script is a sendmail wrapper for php to log calls of the php mail() function.
  Author: Till Brehm, www.ispconfig.org
  (Hopefully) secured by David Goodwin <david @ _palepurple_.co.uk>
*/

$sendmail_bin = '/usr/sbin/sendmail';
$logfile = '/tmp/mail_php.log';

//* Get the email content
$logline = '';
$pointer = fopen('php://stdin', 'r');

while ($line = fgets($pointer)) {
        if(preg_match('/^to:/i', $line) || preg_match('/^from:/i', $line)) {
                $logline .= trim($line).' ';
        }
        $mail .= $line;
}

//* compose the sendmail command
$command = 'echo ' . escapeshellarg($mail) . ' | '.$sendmail_bin.' -t -i';
for ($i = 1; $i < $_SERVER['argc']; $i++) {
        $command .= escapeshellarg($_SERVER['argv'][$i]).' ';
}



//* Write the log
file_put_contents($logfile, date('Y-m-d H:i:s') . ' ' . $_ENV['PWD'] . ' ' . $logline, FILE_APPEND);
//* Execute the command
return shell_exec($command);

</code>

(I've not yet been able to fully test this, so your mileage may vary....)

By: roben

Thanks for this, this is just what I needed!

 

If you also want to log some of the php environment variables from the request you need to put something like

in /etc/php5/set_php_headers.php. You can choose whatever variables you want. I added the "__" to them to prevent overriding. Now modify php.ini and set: auto_prepend_file = "/etc/php5/set_php_headers.php" This executes the above file for each request. You can use these variables in the /usr/local/bin/phpsendmail script for logging.

By: roben

Grrr... Something broke my reply.

What you have to add is:

<?php
        putenv("__PATH_INFO=". $_SERVER["PATH_INFO"]);
        putenv("__SCRIPT_NAME=". $_SERVER["SCRIPT_NAME"]);
        putenv("__SCRIPT_FILENAME=". $_SERVER["SCRIPT_FILENAME"]);
        putenv("__REMOTE_ADDR=". $_SERVER["REMOTE_ADDR"]);
        putenv("__HTTP_HOST=". $_SERVER["HTTP_HOST"]);
?>

By:

1. Login as root, then create script file: /usr/local/bin/logging_sendmail

#!/bin/sh
# Logging sendmail wrapper

SENDMAIL="/usr/sbin/sendmail"
LOGFILE="/var/log/sendmail.log"

DT=`date "+%Y-%m-%d %H:%M:%S"`
DTFN=`date "+%Y%m%d-%H%M%S"`
TMPFP=`tempfile --prefix=lsm_`

cat | tee "$TMPFP" | $SENDMAIL $*
RETVAL=$?

TO=`grep "To:" <"$TMPFP"`
rm -f "$TMPFP"

echo "$DT: $PWD sends e-mail $TO" >>$LOGFILE

exit $RETVAL

2. Make the script executable:

chmod +x /usr/local/bin/logging_sendmail

3. Create /var/log/sendmail.log:

touch /var/log/sendmail.log

chown root:adm  /var/log/sendmail.log

chmod 662  /var/log/sendmail.log

4. Add logrotate configuration as /etc/logrotate.d/logging_sendmail:

/var/log/sendmail.log {
    weekly
    rotate 4
    compress
    delaycompress
    missingok
    create 662 root adm
}

5. Configure the /usr/local/bin/logging_sendmail script instead of phpsendmail as the article describes above (php.ini modifications).

6. Restart Apache as the article describes

You can look at the logfile with the same command, but the filename passed has to be: /var/log/sendmail.log

It works perfectly on our server.

Good luck!

By: Debian Dude

This shell solution doesn't work at least on Debian. Logging works but mail was not sent. And yes i copy/paste the script and send mail location is correct.

By: guldi

yes it does. you have to adjust this line:

SENDMAIL="/usr/sbin/sendmail"

And add the -t:

SENDMAIL="/usr/sbin/sendmail -t"

this just worked great for me. yeah. :)

By: Sean Dempsey

The shell option works great for me too (for some reason the PHP version did not).  The only problem I'm having is that TO is not capturing the to field. Could capitalization of "To" be causing the problem? Any ideas, anyone?

By: Bretticus

Email did not send for me until I used the -t switch in the sendmail path also (I  am using CentOS release 5.9 with sendmail linked to exim.) Thanks for the awesome wrapper. We have so many old email forms on this particular server. What a relief to be able to find a troublespot (Wordpress comments currently!) and take quick measures.

By: Joao

Hey, this solution is very good thanks.

But the >> is not appending :/ probably permissions problem. I have tried chmod 777 but nothing.

 I removed the rm command and append >> to the temp file and it works.
But how can i get the output to go to my logfile?

this is what i have:

#!/bin/sh
# Logging sendmail wrapper

SENDMAIL="/usr/sbin/sendmail -t"
LOGFILE="/var/log/sendmail.log"

DT=`date "+%Y-%m-%d %H:%M:%S"`
DTFN=`date "+%Y%m%d-%H%M%S"`
TMPFP=`mktemp`

cat | tee "$TMPFP" | $SENDMAIL $*
RETVAL=$?

TO=`grep "To:" < "$TMPFP"`
#rm -f "$TMPFP"        (this should be uncommented ofc)
echo "$DT: $PWD sends e-mail $TO" >> $TMPFP      (this should be $LOGFILE)
exit $RETVAL


And the ls on the log file

-rwxrwxrwx 1 root         apache              8 2010-12-27 16:08 sendmail.log


Thanks for the help

By:

maketemp doesn't exist on Centos 5.  

Replace line

 TMPFP=`tempfile --prefix=lsm_`

 with

 TMPFP=`mktemp`

 

By: Mike

My server has been attacked by some guys using fishing and mail() function.

Your tutorial allow me not to secure my server, but to warn victims.

 Sure your tutorial is strongly useful.

By: itransition

Thanks. It helped me.

By: Jamen

I have tried both versions of the script, php and shell only versions and both work fine.

But NEITHER show the actual file of the script completing the mail function.  They only show the PWD aka Working directory of the script! 

How do you make the darn log file show the actual php script file itself running the mail function!  Not just the working directory!  There are thousands of php files in the directories listed.

Thank you!

By: Unai Rodriguez

Dear All,

We have had issues sending UTF-8 encoded emails when using this phpsendmail wrapper instead of the normal sendmail method. Basically the contents get all turned into unreadable characters.

 Has anyone experienced similar issues? Would there be a way of fixing this?

 Thank you so much for the excellent information.

With Best Wishes,

Unai Rodriguez

By: Anonymous

php function escapeshellarg() removes internetional characters. Simply quote string- do not use this function

By: Anonymous

Just add this on the beginning of the script:

 setlocale(LC_CTYPE"en_US.UTF-8");

By: Nick Lachey

Fantastic post. Here’s a tool that lets you build all types of web forms with email alerts fast and without coding. Just point and click http://www.caspio.com/online-database/features/web-forms-online.aspx

By: Ian Dunn

PHP 5.3 added two configuration directives to solve this problem.
 
mail.add_x_header - Adds an extra header to the e-mail showing which script made the call to mail()
mail.log - Sets up a log file for all mail() calls. Contains the originating script and To address

See http://php.net/manual/en/mail.configuration.php for more info.

By:

You are the best, thanks so much for this information ...With this new feature / support then the wrapper is not necessary for PHP 5.3 and above ...

By: Justin G.

THANK YOU.  This provides so much more information (including the full path to the calling script and even the line number of the call).  Only took a few seconds to hunt down the offender once I enabled this.

By: Aas

Thank you. Your comment should precede the article itself!

By: Jae

readable log :)

/* Write the log
file_put_contents($logfile, date('Y-m-d H:i:s') . ' ' . $_ENV['PWD'] . ' ' . $logline.PHP_EOL, FILE_APPEND);

By: crazeeEyez

blah blah blah security hole blah blah blah.  This is very useful to troubleshoot when you know there is mail being sent through your sendmail but can't track down the origin.  If you only use it when you need it and revert back when you're done, this can be really helpful.

 Thank you for the very useful tool!

By: Anonymous

THANK YOU!!!!!!

By: zayo

how can a get script filename path for write it to logfile?

$_SERVER["SCRIPT_FILENAME"]  return me /usr/local/bin/phpsendmail

thanx

By: FitServer

Great article and suggestion. It worked perfectly for my needs and helped me debugging a hacked website.

Thank you a lot!

By: Siegbert

very good to find out which script sends spam mails. But I have one problem with the script. If used with the mailtest.php all works fine. But when I send mail from a Joomla-contact-form I get the following error in mail-log:

plesk sendmail[10557]: Unknown option `--'.plesk sendmail[10557]: Unable to get list of recipients from command line args.

The $mail-variable looks like this without the lines, they are only for better formating:

---------------------------

To: [email protected]: =?utf-8?Q?B=C3=BCchereiverwaltung_f=C3=BCr_Schulen:_test_aus_dem_Kontaktf?=  =?utf-8?Q?ormular?=X-PHP-Originating-Script: 10002:class.phpmailer.phpDate: Fri, 20 Nov 2015 21:59:23 +0100From: =?utf-8?Q?B=C3=BCchereiverwaltung_f=C3=BCr_Schulen?= <[email protected]>Reply-To: Glodek <[email protected]>Message-ID: <[email protected]>X-Priority: 3X-Mailer: PHPMailer 5.2.9 (https://github.com/PHPMailer/PHPMailer/)MIME-Version: 1.0Content-Type: text/plain; charset=utf-8Content-Transfer-Encoding: 8bitDies ist eine Mailanfrage via https://www.glodek-edv.de/ von:Glodek <[email protected]>test

--------------------------------------

What ist the reason for it on UBUNTU 14.04 LTS with PLESK ?

Thank you

By: VLADKRAS

So log file has 777 permission and is publicly avaliable - it's very bad

By: till

When you run a web server that runs each website under a different user and group with suexec and you use php-fpm or php-fcgi, then the log file has to have 777 permissions as all php processes have to be able to write to it, there are no other options. If you run all sites under one user, then give the file e.g. 750 permissions and chown it to the user and greoup that your single php process is using.

By: Harald Reindl

"Instead of criticize, you should better submit a patch..." - There is nothing to patch, you write untrusted input in a logfile which ends in exploit the shell when using "cat" to view it, you wrap untrusted input in a shell command and execute that on a (wrong configured otherwise it would not allow shell-execution) webserver and so the whole "howto" is flawed in a way that it is a security nightmare from the first to the last line

By: Ruben

Why you should use this if we have a option in php.ini called mail.log

; The path to a log file that will log all mail() calls. Log entries include; the full path of the script, line number, To address and headers.mail.log = /var/log/phpmail.log

By: asc

i get nothin in my logs saved

By: James

It's worth noting there could be GDPR (EU privacy law) implications of doing this. By logging the contents of an email, you could be logging people's personal data too. You'd need to gain user consent for storing this up front to remain GDPR compliant.

By: till

The above log script does not log any mail contents, it just logs the name of the server script which has sent an email (and that's not personal data) plus the from and to address (which are logged in the systems mail.log file anyway).

By: jan

do you save the script as sendmail.php ?

By: phptechie

Nice tutorial! It is a very informative tutorial. It was extremely helpful.

By: Lapo Luchini

Hi! This funciton is handled internally by PHP itself, since PHP 5.3.0 release htis parameter can be easily configured in php.ini:

mail.log = /path/to/some/logfile