tags:

views:

131

answers:

3

Hi all,

I am experiencing a relentless XSS attack that I can't seem to prevent. I've got three total input forms on my site - one is for the uploading of images, one for adding comments to a page, and a third that sends an email via php. I am protecting all of them in one way or another, but somehow the vulnerability is still there.

My comments code:

for($j = 0; $j < 3 ; $j++)
                    {
                            $s = $styles[array_rand($styles)];
                            if($song_arr[$k] != '' && $artist_arr[$k] != '' && $name_arr[$k] != '')
                            {
                            echo '<td>';    
                            echo '<div class="'.$s.'" style="clear:left" >';
                                echo '<p class="rendom">';
                                    echo 'Song:&nbsp;'.htmlspecialchars($song_arr[$k]).'<br>Artist:&nbsp;'.htmlspecialchars($artist_arr[$k]).'<br>Submitted By:&nbsp;'.htmlspecialchars($name_arr[$k]);
                                echo '</p>';
                            echo '</div>';
                            echo '</td>';
                            }
                        $k++;
                    }

Upload form:

    if ((($_FILES["userfile"]["type"] == "image/jpg")
|| ($_FILES["userfile"]["type"] == "image/jpeg")
|| ($_FILES["userfile"]["type"] == "image/pjpeg"))
&& ($_FILES["userfile"]["size"] < 20000)) {
    if (is_uploaded_file($_FILES['userfile']['tmp_name'])) {
        if (move_uploaded_file ($_FILES['userfile']['tmp_name'],'userfile.jpg')) {
            $image = new SimpleImage();
            $image->load('userfile.jpg');
            $image->resize(29,136);
            $image->save('userfile.jpg');
            ?>
            <img src="img/text/uploadSuccess.jpg" alt="Image uploaded successfully." /><br />
                    <br />
                    <img src="userfile.jpg?rand=<? echo rand(1,10000); ?>" />
            <?
        } else {
            echo 'Moving uploaded file failed';
        }
    } else {
        echo 'File upload failed';
    }
} else {
echo 'Invalid Filetype';
}

Email Form:

<?php
// Process input variables (trim, stripslash, reformat, generally prepare for email)
    $recipients = trim($_POST['recipients']);
    $sender_email = trim($_POST['sender_email']);
    $sender_name = stripslashes(trim($_POST['sender_name']));
    $subject = stripslashes(str_replace(array("\r\n", "\n", "\r"), " ", trim($_POST['subject'])));
    $message = stripslashes(str_replace(array("\r\n", "\n", "\r"), "<br />", trim($_POST['message'])));

// Check email addresses for validity
    // Explode the comma-separated list of recipients + the sender email address into an array. Even if there is only one recipient, this will check for validity.
    $addresses = explode("," , $recipients.",".$sender_email);
    // For each email address specified...
    foreach ($addresses as $address) {
        // If the email address doesn't match the RFC8622 spec regex, assume invalid 
        if (!(preg_match("~^[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+(?:[A-Z]{2}|com|org|net|uk|edu|jp|de|br|ca|gov|au|info|nl|fr|us|ru|it|cn|ch|tw|es|se|be|dk|pl|at|il|tv|nz|biz)$~i", trim($address)))) {
            // Output error message for invalid email address and end script.
            echo '"' . $address . '" is not a valid email address. Please try again.';
            return;
        }
    }

// Check other vars are not empty
    if ((empty($sender_name)) OR (empty($subject)) OR (empty($message))) {
        // Output error message and end script.
        echo 'Please complete all form fields and try again.';
        return;
    }

// Send HTML email
    $headers = "MIME-Version: 1.0\r\nContent-type:text/html;charset=iso-8859-1\r\nFrom: ". $sender_name ." <". $sender_email ."> \n\n";
    if (mail($recipients,$subject,$message,$headers)) {
        // Mail successfully sent, output success message and end script
        echo 'Message sent. We will be in touch with you shortly.';
        return;
    } else {
        // Something unknown went wrong. =(
        echo 'Something went wrong which the little worker monkeys could not fix. Please try again.';
        return;
    }
?>

The XSS keeps showing up at the absolute bottom of my index page, in which i include() all of the three above files whose contents are in different files.

Any ideas?

+1  A: 

After a quick look, it seems that the only place where you display untrusted data is in the comments. And you used htmlspecialchars, which sould prevent any html code to be interpreted.

You say that the malicious code is at the bottom of your page. Maybe the attacker found a way to upload and include his script directy on your server ? What does the included code look like ? Is it JavaScript, HTML ?

Wookai
The XSS is a javascript insertion that uses document.write to to insert an iframe which sources from an external malicious site.The only includes I use are via php:<?phpif (!isset($_GET["page"])) { include("inc/home.inc.php");} else { include("inc/".$_GET["page"].".inc.php");} ?>
Dave Kiss
Ok. Is the JS code in the middle of your comments section, or is it somewhere else ? In the latter case, the problem may not come from the code you posted.
Wookai
The JS code shows up at the very bottom of the index.php page, not in the middle of the comments
Dave Kiss
In this case, I'd say that your files were somehow compromised. If you look at your template/PHP file that generates the page, is there anything ? Maybe the attacker is using a .htacces to automatically append some code to all your php scrips ? Is it present on all pages or only the commend page ?
Wookai
The code is only present on the index page which is a barebones file including elements from various files to build the page.. I've changed my account password in the past to something much more secure to no avail..i'm not sure about .htaccess, but i'd assume thats something where the attacker would need account access?
Dave Kiss
It boils down to one thing : can you find the malicious code in one of your PHP files ? If yes, then the attacker was somehow able to access them. If no, then it's in the database (but it seems that you cleaned the output, so this must not be the case. Try to empty it to be sure).
Wookai
+2  A: 

In the e-mail form, you echo back invalid e-mail addresses that were submitted without escaping them. Change this line:

 echo '"' . $address . '" is not a valid email address. Please try again.';

to

 echo '"' . htmlspecialchars($address) . '" is not a valid email address. Please try again.';
ArIck
Good catch, do you think that XSS inserted here could somehow stick on the bottom of the page, even for subsequent page loads on different machines?
Dave Kiss
Nope, it can't. This kind of insertion is a non-persistent XSS (http://en.wikipedia.org/wiki/Cross-site_scripting#Non-persistent) and only affects the user that sent the request. Yours is nastier ;) !
Wookai
I'm going to go with Wookai and posit that the attacker got one of his scripts on your server. Also, including a file whose path you obtain by concatenating "inc/", the GET variable "page" and ".inc.php" is asking for trouble. If your PHP code is in /home/dave and mine is in /home/eric, I could open up one of your pages with "page=../../eric/evil_script" in the query string and make your page include my evil_script.inc.php file.
ArIck
Arick,Based on your recommendation, I check my php error_log and found some peculiar errors....[11-Dec-2009 00:38:56] PHP Warning: include(inc//../../../../../../var/log/apache2/access.log) : failed to open stream: No such file or directory in /home......../index2 copy.php on line 137Do you have suggestions on how i may be able to validate the input, or a different approach?
Dave Kiss
In general, you want to just have a whitelist of approved files to include and go off of that. Change the URLs so that 'page' is just some number from 0 to (number of pages - 1). Change how you include the page by declaring an array with the names of the files (we'll call it $MY_PAGES), then include($MY_PAGES[$_GET["page"]]).
ArIck
thanks for your suggestion... i'm also considering the effectiveness of $page = preg_replace('/[^-a-zA-Z0-9_]/', '', $_GET['page']);do you have any recommendations against using this other than potentially unwanted local file inclusion?
Dave Kiss
That should be fine. (The regex will strip away pound signs and tildes, which are valid in a file name in most *nixes...but I'm guessing no one wants to see your emacs backup files.)
ArIck
thanks dude, we'll see how this goes.
Dave Kiss
A: 

This is not an answer, and not good news, but I did see something very similar to what you described in an example in the rather disturbing video ad from Symantec, "Zeus: King of the Crimeware Toolkits" at Youtube: http://www.youtube.com/watch?v=hfjPO8_pGIk

It's worth seeing the video in any case.

I have no connection with Symantec.

LarryW