Quicksearch
Calendar
Syndicate This Blog
|
Wednesday, December 13. 2006mail() logging for PHP
One of the common problems faced by web hosting companies offering PHP is the abuse of the mail() function to send spam. This problem has became further exasperated lately due to use of automated tools that seek sites vulnerable to PHP code execution and use the security hole to inject mailer code that then proceeds to send tons of spam.
This of course causes a series problem for a web host such as increased server load, possibility of getting blacklisted and thus having all mail generated by the server rejected and even problems with an up-stream provider. One of the problems with solving the mail() abuse is figuring out who is doing it or perhaps what script was exploited to do it, since the mail() function does not offer any logging mechanism. The uid identifier is generally useless because when PHP is ran as an Apache modules all script share the web server's uid, which yet another reason to use FastCGI. To address this problem one my client's had asked me to write a mail() logging and tracking facility that can be used to quickly identify the offending scripts and was kind enough to permit me to share the code with with the community. The mailing logging patch, which you can download here, offers two features which can be controlled via INI settings for the entire PHP setup or on a per-virtual-host level. The first option, mail.add_x_header (boolean) allows you to enable the addition of the X-PHP-Originating-Script header to each mail sent by mail(), which will include the uid of the script and its name. The combination of the two should be sufficient to identify the user to whom the script belongs and via a simple find command locate the actual script. This option is intended primarily for instances where you have a bounced e-mail or a forwarded mail with a spam complaint, allowing you to quickly identify the offender. The second option, mail.log (takes a filename) allows you to enable logging of every single mail() call, each log line will include the fullpath of the file and the line where the mail() was called from in addition to the "To" address and any headers (to keep track of CC, BCC) that were part of the function call. To ensure that each log line is 1 line long, \r and \n are replaced with spaces. Trackbacks
Trackback specific URI for this entry
No Trackbacks
Comments
Display comments as
(Linear | Threaded)
Can I assume this patch will make it into the core? Great addition!
--Tony
It'll definitely not make it into PHP 4.4.X as this branch is closed for new features. It may make into PHP 5.2.2 (5.2.1 is already settled for new features), assuming other developers like the concept.
Why not just block outgoing SMTP connections and put the logging on your SMTP server? Clients can send mail without using mail(): see Swift Mailer for an example.
Because that breaks applications that are reliant on mail() function.
Not necessarily. Obviously, you wouldn't want to block your own SMTP server connections. However, as long as you configure the PHP directive SMTP correctly, you could force all outgoing mail traffic through that point, thus moving logging to a more reliable point.
PHP on *nix systems only uses sendmail to deliver e-mail the SMTP settings in PHP are exclusively for Win32 installs.
I suppose you could add a filter around the mailer making it the sendmail wrapper, but this is tricky to do and probably requires far more effort then most people are willing to expand.
Ah, I see, the problem is not logging the emails per-say, but determining the originating script. It still won't catch direct socket connections to the mail-server, but should be sufficient for catching non-malicious users with buggy scripts.
Thanks Ilia!
This is definitely a good idea to add into the core as the mail function is one of the most complained about functions since people can never figure out why it doesn't work or what its actually doing. Some form of this patch or even anything done to enhance the mail functions logging and debugging information would be extremely superb!
Hi Ilia,
This sounds realy great! I'd also love to see it in PHP 5.2.2 or ASAP. Thanks very much!
Suhosin has a similar feature, but much more complete .. anyway is a nice addtional, better late than never
![]()
In what way is it more complete? I am unfamiliar with the code in the Suhosin extension, so you'll need to fill me in.
Great news! Will be waiting this feature in future releases of PHP =)
Great. I have searched for this a long time. Thanks for publishing this.
I guess it's worth to note that the suhosin extension (http://suhosin.org) has features to detect mail() injection (newlines in subject, to, additonal headers) and to log or drop them. Both features combined could make a well secured mailing system.
too late, brudder!
![]() I had been teased toooo much and hard with our hosters, and that made me think harder, and I wrote a patch to qmail two years ago... Now I can see everybody in the header as shown below, and have total control over them. X-LocalID: this was for CGI scripts... X-LocalID: this was for those using php mail() or command line...
In a virtual hosting environment where sendmail is executed by Apache with always the same uid, how does it distinguish different users?
That patch is a really great idea, but can anybody give me a hint how to apply the patch.
I tried to run it in the PHP soruce directory with "patch -i ../mail_log_path/patch.txt" but this produces Errors when patching mail.c, php.ini-dist and php.ini-recommended Any ideas?
Here is the patch for PHP 5.2.0 it should be mostly applicable for 5.1.5
http://ilia.ws/uploads/patches/mail_log.txt.gz
It looks like something lost in the patch.
--- main/php_globals.h.orig Fri Dec 15 12:13:39 2006 +++ main/php_globals.h Fri Dec 15 11:52:55 2006 @@ -141,6 +141,8 @@ zend_bool always_populate_raw_post_data; long serialize_precision; + zend_bool mail_x_header; + char *mail_log; };
Thanks, I missed php_globals.h in the diff line I ran to generate the patch. I've now updated the patch.
It doesn't work for php 4.2.2, right? - the patch went fine, but whether a log-file nor a X-entry in the mail-header would be created (according to php.ini).
PS: Sorry for my bad English. d@tenmaulwurf
GREAT Ilia, thanks!
It doesn't work though on a fresh unpatched 5.2.0 (using your http://ilia.ws/uploads/patches/mail_log.txt.gz): $>> patch -p 0 -i ../mail_log.txt patching file php.ini-dist Hunk #1 FAILED at 605. 1 out of 1 hunk FAILED -- saving rejects to file php.ini-dist.rej patching file php.ini-recommended Hunk #1 FAILED at 620. 1 out of 1 hunk FAILED -- saving rejects to file php.ini-recommended.rej patching file main/main.c Hunk #1 succeeded at 310 with fuzz 1 (offset -32 lines). patching file main/php_globals.h Hunk #1 FAILED at 141. 1 out of 1 hunk FAILED -- saving rejects to file main/php_globals.h.rej patching file ext/standard/mail.c Hunk #3 FAILED at 182. Hunk #4 succeeded at 262 (offset 3 lines). Hunk #5 succeeded at 284 (offset 3 lines). 1 out of 5 hunks FAILED -- saving rejects to file ext/standard/mail.c.rej Any idea? Thanks a log Pipo
For PHP 5.2.0 you need to use the patch posted here:
http://ilia.ws/uploads/patches/mail_log.txt.gz
I was using exactly that one but it just doesn't seem to be a diff of the current stable 5.2.0. I have downloaded php-5.2.0.tar.bz2 from 3 different mirrors to make sure I haven't got any "dirty" release.
mhhh... I'm stuck here
I also tried to apply the patch with PHP 5.2.0 but it didn't work. So I copied the
patch parts manually into the .c files.
I tried to patch manually php 5.2.0...
Compile fails with php_basename function /usr/src/php-5.2.0/ext/standard/mail.c:212: error: too few arguments to function php_basename
I had the same problem and corrected it with the following code. (for complete if-statement)
if (PG(mail_x_header)) { char *tmp = zend_get_executed_filename(TSRMLS_C); size_t ret_len; char* f; php_basename(tmp, strlen(tmp), NULL, 0,&f, &ret_len TSRMLS_CC); if (headers != NULL) { spprintf(&hdr, 0, "%s\r\nX-PHP-Originating-Script: %ld:%s\r\n", headers, php_getuid(), f); } else { spprintf(&hdr, 0, "X-PHP-Originating-Script: %ld:%s\r\n", php_getuid(), f); } efree(f); }
thanks Stephan, with your fix it compiled fine but PHP crashed every 3rd call (unter mod_fcgid) with a 500 internal server error. I've tried it with the following without success:
size_t ret_len; char* f = php_basename(tmp, strlen(tmp), NULL, 0,&f, &ret_len TSRMLS_CC); /usr/src/php-5.2.0/ext/standard/mail.c:213: error: void value not ignored as it ought to be Hey, I'm not into C. Could anybody please provide us a working patch for 5.2.0? Thanks a lot! Merry Christmas
Would be really great to get this into the core (PHP5).
Just so you all know, this is built against PHP 4.4. Save everyone some time.
There is another interesting patch, also made to harness mail() usage :
http://choon.net/php-mail-header.php
I really hope it will be included into 4.4.x or 4.5 (if there will be any). as this is really a urgent feature if you are ISP and running many sites
I have a working patch using this idea for PHP-5.2.0. I have added a lot more features and taken the original patch from choon.net to make a simple but very workable patch. It also logs system and exec calls
I will write against php-4.4.4 and release in the next couple of days on our website (http://www.phpfaq.info) Darren
Hello Ilia
Thanks for the wonderful patch. Here is a significant bug: For locally injected mail line endings must be a single linefeed (\n) and not, contrary to popular ideas, a carriage-return + linefeed (\r\n). The crlf sequence IS required however, over SMTP transactions. PHP injects mail locally, and thus line-endings must be \n. The MTA will then convert these to \r\n. Change this: + if (headers != NULL) { + spprintf(&hdr, 0, "%s\r\nX-PHP-Originating-Script: %ld:%s\r\n", headers, php_getuid(), f); + } else { + spprintf(&hdr, 0, "X-PHP-Originating-Script: %ld:%s\r\n", php_getuid(), f); + } To this: + if (headers != NULL) { + spprintf(&hdr, 0, "%s\nX-PHP-Originating-Script: %ld:%s\n", headers, php_getuid(), f); + } else { + spprintf(&hdr, 0, "X-PHP-Originating-Script: %ld:%s\n", php_getuid(), f); + } Thanks! Quinn
The logging functionality breaks if the log is outside the open_basedir. Is that intended? Since I want to have one global log for all of the domains aggregated, not one per domain.
I see this logging function as being "for admin only" and set on a PHP_INI_SYSTEM (php.ini) level only. As such don't see the reason to restrict the system administrator to open_basedir.
I think we're not getting to each other
![]() When I enable the log in php.ini and execute a PHP script from within a customer domain, a warning is displayed since, say, /var/log/php-mail.log is outside that customer domain's open_basedir and cannot be opened for writing.
Oh I see. The file writing code needs to be adjusted to skip the open_basedir/safe_mode checks in this case.
I will be revising the patch shortly and this will definitely be incorporated.
Hi, I installed the patch but it still do not give me the script name the mail() was called from. Am I missing something
Return-Path: Authentication-Results: mta327.mail.mud.yahoo.com from=test.com; domainkeys=neutral (no sig) Received: from xx.xxx.xx.xx (EHLO test.com) (xx.xxx.xx.xx) by mta327.mail.mud.yahoo.com with SMTP; Tue, 21 Jul 2009 08:46:49 -0700 Received: by test.com (Postfix, from userid 48) id 672D77A027D; Tue, 21 Jul 2009 11:46:49 -0400 (EDT) To: test@yahoo.com Subject: Test from test.com From: "test.com" Add sender to Contacts Message-Id: Date: Tue, 21 Jul 2009 11:46:49 -0400 (EDT) Content-Length: 120
Did you enable logging inside the php.ini file?
|
Categories
|