AeroMail 2 Multiple Vulnerabilities

30 November -0001

Description of Vulnerability:

AeroMail 2 is a lightweight e-mail client written in PHP.

From the product homepage (http://www.nicolaas.net/aeromail/index.php?page=index): "AeroMail 2 is the next generation of Mark Cushman's AeroMail. AeroMail is a web-based e-mail client written in PHP. You can view HTML and rich text mail with attachments, write mail and send attachments yourself, and optionally flag spam using e.g. SpamCop. AeroMail uses PHP's IMAP module to read and store messages in user-defined folders. "

AeroMail 2 suffers from a number of cross site scripting (XSS) vulnerabilities (both reflected and persistent) due to the fact that the code fails to sanitize folder names and e-mail subjects prior to display.

Aeromail 2 also suffers from a number of cross site request forgery (CSRF or XSRF) vulnerabilities as forms are not protected with one time tokens.

Systems affected:

AeroMail 2.80 was tested and shown to be vulnerable.

Impact

Malicious attackers could inject arbitrary scripts into pages affecting AeroMail 2 users. This could result in account compromise. Another likely scenario would be for an attacker to inject hidden content (such as iframes, applets, or embedded objects) that would attack client browsers in an attempt to compromise site users' machines.

CSRF vulnerabilities could allow attackers to send e-mail, delete e-mail, or create folders as the site user. This vulnerability could be used to perform persistent XSS attacks based on XSS vulnerabilities persistent in AeroMail 2.

Mitigating factors:

In order to exploit vulnerabilities an attacker must be able to send e-mail to a target user, trick a user into following a malicious URL, or trick a logged in user into viewing malicious content.

Vulnerabilities Enumerated:

  • Aeromail 2 suffers from an arbitrary script injection vulnerability because it fails to sanitize the 'folder' URL variable (e.x. aeromail/index.php?folder=<script>alert('document.cookie');</script>)
  • No CSRF protection on mail composition screen could allow attackers to send spam without a users knowledge.
  • No CSRF protection is provided in message delete functionality, which could allow unauthorized attackers to delete messages without user consent.
  • AeroMail 2 contains a persistent XSS vulnerability due to the fact that folder names are not sanitized before display which causes arbitrary HTML to be rendered
  • AeroMail 2 suffers from a persistent XSS vulnerability due to the fact that e-mail attachment names are not sanitized before display.
  • Folders are created via GET with no CSRF protection, which could allow unauthorized users to create malicious folders and exploit the arbitrary script vulnerability described above.
  • AeroMail 2 fails to sanitize message subject lines before display. This could allow remote attackers to execute arbitrary scripts when users view their e-mail. This vector could allow for exploitation of all of the above vulnerabilities without having to trick the user into executing a CSRF trigger. Simply viewing mail could be used to send mail, delete mail, leak login credentials, or create persistent XSS via malicious folder names.

Vendor Response:

Vendor was unresponsive to contact attempts.

Patch:

The following patch should mitigate these vulnerabilities, excepting sanity checks for filenames of attachments:


# diff -up aeromail aeromail.fixed/
diff -up aeromail/action.php aeromail.fixed/action.php
--- aeromail/action.php	2009-10-20 06:46:52.000000000 -0400
+++ aeromail.fixed/action.php	2011-06-24 09:44:51.000000000 -0400
@@ -1,10 +1,11 @@
 <?php
 include("global.inc.php");
+if ($_POST['token'] != $_SESSION['token']) die("Possible XSRF attempt!");
 
-$delall = $_GET['delall'];
-$delete = $_GET['delete'];
-$move = $_GET['move'];
-$readall = $_GET['readall'];
+$delall = $_POST['delall'];
+$delete = $_POST['delete'];
+$move = $_POST['move'];
+$readall = $_POST['readall'];
 if (!DELETE_MESSAGES and ($delall or $delete))
     exit;
 if (!USE_FOLDERS and $move)
@@ -21,11 +22,11 @@ if (($delall || $delete) && TRASH && $fo
     $movetotrash = 1;
 }
 
-$tofolder = $_GET['tofolder'];
+$tofolder = $_POST['tofolder'];
 $tofolder = $tofolder == "INBOX" ? "INBOX" : construct_folder_str($tofolder);
 
-$msgnum = $_GET['msgnum'];
-$msglist = $_GET['msglist'];
+$msgnum = $_POST['msgnum'];
+$msglist = $_POST['msglist'];
 if ($move)
 {
     $msgs = $msgnum ? $msgnum : implode($msglist, ",");
diff -up aeromail/compose.php aeromail.fixed/compose.php
--- aeromail/compose.php	2009-10-20 07:22:50.000000000 -0400
+++ aeromail.fixed/compose.php	2011-06-24 09:46:24.000000000 -0400
@@ -202,6 +202,7 @@ function pagecontent()
     </form>
 
     <form enctype="multipart/form-data" name="doit" action="send_message.php" method="POST">
+    <input type="hidden" name="token" value="<?php echo $_SESSION['token'];?>">
     <input type="hidden" name="folder" value="<?php echo $folder ?>">
     <input type="hidden" name="msgnum" value="<?php echo $msgnum ?>">
     <input type="hidden" name="headers" value="<?php echo $headers ?>">
Only in aeromail: configure.php
diff -up aeromail/folder.php aeromail.fixed/folder.php
--- aeromail/folder.php	2009-10-20 07:34:59.000000000 -0400
+++ aeromail.fixed/folder.php	2011-06-24 09:42:41.000000000 -0400
@@ -10,15 +10,17 @@ include("global.inc.php");
 imap_close($mailbox);
 $mailbox = mailbox_log_in($user, $pass, "INBOX");
 
-$name = $_GET['name'];
+$name = $_POST['name'];
 $folder_str = construct_folder_str($name);
-$action = $_GET['action'];
+$action = $_POST['action'];
 if($action == "create")
-{
+{	
+	if ($_POST['token'] !== $_SESSION['token']) die("XSRF attempt detected!");
     imap_createmailbox($mailbox, IMAP_STR . "$folder_str");
 }
 if($action == "delete")
 {
+	if ($_POST['token'] !== $_SESSION['token']) die("XSRF attempt detected!");
     imap_deletemailbox($mailbox, IMAP_STR . "$folder_str");
     // ignore errors
     $foo = imap_errors();
@@ -46,7 +48,7 @@ function pagecontent()
     global $folder;
 ?>
     <form action="index.php" method="get">
-    <input type="hidden" name="folder" value="<?php echo $folder ?>">
+    <input type="hidden" name="folder" value="<?php echo urlencode($folder) ?>">
     <input type="submit" value="<?php echo L_GO_BACK_BTN ?>">
     </form>
 <?php
@@ -95,7 +97,7 @@ function pagecontent()
 
             $url_nm = urlencode($nm);
 
-            echo "<a class=\"intf\" href=\"index.php?folder=$url_nm\">$nm</a>";
+            echo "<a class=\"intf\" href=\"index.php?folder=$url_nm\">" . htmlspecialchars($nm) . "</a>";
             body_left_end();
             body_left();
             echo imap_num_msg($mailbox);
@@ -121,9 +123,10 @@ function pagecontent()
     header_left('colspan="2"');
     ?>
 
-<form action="folder.php" method="get">
+<form action="folder.php" method="post">
 <label for="name"><?php echo L_NAMED ?></label>
-<input type="text" name="name" id="name"><input type="hidden" name="folder" value="<?php echo $folder ?>">
+<input type="hidden" name="token" value="<?php echo $_SESSION['token'];?>">
+<input type="text" name="name" id="name"><input type="hidden" name="folder" value="<?php echo htmlspecialchars($folder) ?>">
 <select name="action">
     <option value="create"><?php echo L_FOLDER_CREATE ?>
     <option value="delete"><?php echo L_FOLDER_DELETE ?>
diff -up aeromail/global.inc.php aeromail.fixed/global.inc.php
--- aeromail/global.inc.php	2009-11-11 20:53:00.000000000 -0500
+++ aeromail.fixed/global.inc.php	2011-06-24 09:34:54.000000000 -0400
@@ -2,7 +2,7 @@
 
 define('VERSION', "AeroMail 2.80");
 
-$folder = $_REQUEST['folder'];
+$folder = isset($_REQUEST['folder']) ? $_REQUEST['folder'] : 'INBOX';
 
 $newerrorrep = error_reporting(0) & ~E_NOTICE;
 error_reporting($newerrorrep);
@@ -47,8 +47,8 @@ function mailbox_log_in($user, $pass, $f
 {
     error_reporting(error_reporting() - 2);
     $folder = $folder == "INBOX" ? "INBOX" : construct_folder_str($folder);
-    $mbox = imap_open(IMAP_STR . "$folder", $user , $pass);
-
+    $mbox = imap_open(IMAP_STR . "$folder", $user , $pass);  
+    
     error_reporting(error_reporting() + 2);
     return $mbox;
 }
@@ -190,7 +190,7 @@ function list_folders($mailbox)
             echo "<option>";
             if ($nm != "INBOX")
             {
-                echo deconstruct_folder_str($nm);
+                echo htmlspecialchars(deconstruct_folder_str($nm));
             }
             else
             {
Common subdirectories: aeromail/images and aeromail.fixed/images
diff -up aeromail/index.php aeromail.fixed/index.php
--- aeromail/index.php	2009-11-11 20:53:00.000000000 -0500
+++ aeromail.fixed/index.php	2011-06-24 09:38:56.000000000 -0400
@@ -360,8 +360,9 @@ function check_all()
     {
 ?>
 
-        <form name="delmov" action="action.php" method="GET" onsubmit="return checkchecked()">
+        <form name="delmov" action="action.php" method="POST" onsubmit="return checkchecked()">
         <input type="hidden" name="folder" value="<?php echo "$folder" ?>">
+        <input type="hidden" name="token" value="<?php echo $_SESSION['token'];?>"/>
 <?php
     }
 ?>
@@ -489,7 +490,7 @@ function check_all()
 
         $attach = has_attachment($struct) ? " @" : "";
         $msg = imap_headerinfo($mailbox, imap_msgno($mailbox, $msg_array[$i]));
-        $subject = !$msg->Subject ? L_NO_SUBJECT : $msg->Subject;
+        $subject = !$msg->Subject ? L_NO_SUBJECT : htmlspecialchars($msg->Subject);
         $subject = decode_header_string($subject);
 
         $ksize = round($msg->Size/1024);
Common subdirectories: aeromail/lang and aeromail.fixed/lang
diff -up aeromail/layout.inc.php aeromail.fixed/layout.inc.php
--- aeromail/layout.inc.php	2009-11-11 20:53:00.000000000 -0500
+++ aeromail.fixed/layout.inc.php	2011-06-24 08:52:32.000000000 -0400
@@ -203,6 +203,8 @@ function version()
 function maketitle($name)
 {
     global $user;
+    $user = htmlspecialchars($user);
+    $name = htmlspecialchars($name);
     if (isset($user) and $user !== "") 
         return "$name - $user - " . PROG_NAME;
     return "$name - " . PROG_NAME;
diff -up aeromail/login.php aeromail.fixed/login.php
--- aeromail/login.php	2009-10-20 06:30:44.000000000 -0400
+++ aeromail.fixed/login.php	2011-06-24 09:15:43.000000000 -0400
@@ -13,6 +13,7 @@ if (isset($_POST['user']) && isset($_POS
     session_start();
     $_SESSION['user'] = $_POST['user'];
     $_SESSION['pass'] = $_POST['pass'];
+    $_SESSION['token'] = md5(time());
 	Header("Location: index.php");
 	exit;
 }
diff -up aeromail/send_message.php aeromail.fixed/send_message.php
--- aeromail/send_message.php	2009-11-03 05:19:13.000000000 -0500
+++ aeromail.fixed/send_message.php	2011-06-24 09:46:39.000000000 -0400
@@ -1,6 +1,7 @@
 <?php   
 
 include("global.inc.php");
+if ($_POST['token'] != $_SESSION['token']) die("Possible XSRF attempt!");
 
 function removecrlf($string)
 {
Common subdirectories: aeromail/themes and aeromail.fixed/themes