• The Horde webmail has been deprecated. Its complete removal is scheduled for April 2025. For details and recommended actions, see the Feature and Deprecation Plan.
  • We’re working on enhancing the Monitoring feature in Plesk, and we could really use your expertise! If you’re open to sharing your experiences with server and website monitoring or providing feedback, we’d love to have a one-hour online meeting with you.

PHP Email Injection

T

TDuncklee

Guest
PHP Email Injection. If your users have php email forms you are an open relay!

Ive managed to stop the hackers/spammers from using php email injection on my customers forms. http://securephp.damonkohler.com/index.php/Email_Injection
The correct way to stop this hack is for all form data to be properly validated. However, even just explaining what the issues are to some customers is impossible so I had to come up with a server wide solution. I also log all form submissions for now. Ill look through them occasionally to see if a spammer got through somehow. At the end of the code are the things I check for to flag it as spam. This is a tough one but for now it seems every message Ive looked at has multiple Subject: s so if I find that I drop the message. I also am checking for the known test email addresses the spammers are using. PLEASE, if anyone has ideas on what to look for to definitively determine if it is an email injection spam message, post them. Also, they do not change their test addressees very often in the thousands of messages Ive looked at anyway. If anyone sees a new address, post it.

Here is what I did:

Create a php script called fake_sendmail.php
Code:
#!/usr/local/psa/apache/bin/php 
<?php
// script to filter email generated from php mail() function.
// Used to block spammers from abusing the function as described here:
// [url]http://securephp.damonkohler.com/index.php/Email_Injection[/url]
// modify php.ini
//         sendmail_path = /path/to/fake_sendmail.php -t -i
// If message does not pass the message_chk function we just
// drop the message on the floor.

$size_of_mbox = 10; // Approx max size (in Meg) of logged messages
$sendmail="/usr/local/psa/qmail/bin/sendmail";  // the real sendmail program
$args = "";

for( $i=1; $i <= $argc; $i++ ){
     $args .= $argv[$i]." ";
}
$fd = fopen("php://stdin", "r");
$email = "";
while (!feof($fd)) {
    $email .= fread($fd, 1024);
}
fclose($fd);


$lines = explode("\n", $email);

$from = "";
$subject = "";
$headers = "";
$message = "";
$splittingheaders = true;

for ($i=0; $i<count($lines); $i++) {
    if ($splittingheaders) {    // this is a header until we find empty line
        if (trim($lines[$i])!="") {
            $headers .= $lines[$i]."\n";
        }  // look out for special headers
        if (preg_match("/^Subject: (.*)/", $lines[$i], $matches)) {    
            $subject = $matches[1];
        }
        if (preg_match("/^From: (.*)/", $lines[$i], $matches)) {
            $from = $matches[1];
        }
    } else { // not a header, but message
        $message .= $lines[$i]."\n";
    }
    
    if (trim($lines[$i])=="") { // empty line, header section has ended
        $splittingheaders = false;
    }
}

$chk_result = message_chk( $headers, $message );
if( file_exists( "/usr/mailarchive_php/php_mail_log" ) ){
    if( (filesize( "/usr/mailarchive_php/php_mail_log" )/1024/1000) > $size_of_mbox ){
        rename( "/usr/mailarchive_php/php_mail_log", 
                     "/usr/mailarchive_php/php_mail_log.".date( "y_m_d_H_i_s" ) );
    }
}
$archive_fp = fopen ("/usr/mailarchive_php/php_mail_log", "a");
fwrite( $archive_fp, "*******\n" );
if( $chk_result ){
    fwrite( $archive_fp, "******* Block Rule: ".$chk_result . "\n");
}
fwrite( $archive_fp, "Custom-Info2: Sent with php\n\n" . $headers.$message );
fclose( $archive_fp );
if( !$chk_result ){
    $fps = popen( $sendmail . " " . $args, "w" );
    // Doing this so I can identify all PHP created messages.
    fwrite( $fps,  $headers . "Custom-Info2: Sent with php\n\n" . $message ); 
    pclose( $fps );
}


function message_chk( $headers, $message ){

    if( substr_count ( $headers, "Subject: " ) > 1 ){    return "Multiple Subject:'s";    }
    else if (eregi( "[email protected]", $message.$headers ) ) 
            { return "[email protected]"; }
    else if (eregi( "[email protected]", $to.$subject.$message.$headers ) ) 
            { return "[email protected]"; }
    else if (eregi( "[email protected]", $to.$subject.$message.$headers ) ) 
            { return "[email protected]"; }
    else if (eregi( "[email protected]", $to.$subject.$message.$headers ) ) 
            { return "[email protected]"; }
    else if (eregi( "[email protected]", $to.$subject.$message.$headers ) ) 
            { return "[email protected]"; }
    else if (eregi( "[email protected]", $to.$subject.$message.$headers ) ) 
            { return "[email protected]"; }
    else if (eregi( "[email protected]", $to.$subject.$message.$headers ) ) 
            { return "[email protected]"; }
}

?>
Create /usr/mailarchive_php/ and make it rwx by apache only. This is where I chose to log messages for now.

In /usr/local/psa/apache/conf/php.ini add
sendmail_path = /path/to/fake_sendmail.php -t -i

Restart apache
 
Hey TDuncklee,
Just wondering how this is working for you? Any thoughts / experiences you'd like to share. I'm looking for something of this nature as well.

Best Regards,
Matt Simpson
 
Only change so far is the "Custom-Info2: " header entry is not valid according the the RFC. Needs to be "X-Custom-Info2: "

I also started a From: address validation routine but have not finished it. So far the multiple subject check has stopped all the junk from getting out but the hackers will get wise to it someday...
 
Does anyone know how to add extra headers to all out going email on a plesk server?

For the same reason as you two have for detecting spam, I want to add a x-report-spam header to all out going emails so that people can report spam to us to help us find invaded forms on our server.

Also, in my filtering, I do the same as you do with the "if spam, then die" but I also add tracking to capture their IP and I have any attempts report to a security address so that I get the attempts forwarded to me. i have one guy trying two full days now to get back in but my scripts does nothing but die when I detect attack and so he hasn't caught wind that I'm wasting his time.

I search for the setting of mime instructions like boundry and bit type because those things are not usually done in the script but occur after the mail function run.

please help someone on the server wide x-header idea.

Barry L. Salter
+1.602.413.6197
 
Back
Top