CVE-2022-28055: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') (CWE-78)
value: Fusionpbx v4.4 and below contains a command injection vulnerability via the download email logs function.
CWE description: The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
CVSS v3 base score: 9.8
Published: 2022-05-04T03:15Z
Repository: https://github.com/fusionpbx/fusionpbx
Language: PHP
Files changed: app/email_logs/email_logs.php, app/email_logs/resources/classes/email_logs.php
Commit date: 2022-03-21 10:01:05 -0600
Commit message: Remove email_logs download. (#6331)

* Remove email_logs download.

This feature has a security risk that is being eliminated by removing the download feature.

* Update email_logs.php
Diff: diff --git a/app/email_logs/email_logs.php b/app/email_logs/email_logs.php
index e7bb35be97..e25ea47224 100644
--- a/app/email_logs/email_logs.php
+++ b/app/email_logs/email_logs.php
@@ -42,17 +42,10 @@
 	}
 
 //process the http post data by action
 	if ($action != '' && is_array($emails) && @sizeof($emails) != 0) {
 		switch ($action) {
-			case 'download':
-				if (permission_exists('email_log_download')) {
-					$obj = new email_logs;
-					$obj->download($emails);
-					message::add($text['message-download_failed'],'negative',7000); //download failed, set message
-				}
-				break;
 			case 'resend':
 				if (permission_exists('email_log_resend')) {
 					$obj = new email_logs;
 					$obj->resend($emails);
 				}
@@ -212,13 +205,10 @@
 	echo "		<input type='hidden' name='".$token['name']."' value='".$token['hash']."'>\n";
 	echo "		</form>";
 	if (permission_exists('email_log_resend') && $result) {
 		echo button::create(['type'=>'button','label'=>$text['button-resend'],'icon'=>'paper-plane','onclick'=>"modal_open('modal-resend','btn_resend');"]);
 	}
-	if (permission_exists('email_log_download') && $result) {
-		echo button::create(['type'=>'button','label'=>$text['button-download'],'icon'=>$_SESSION['theme']['button_icon_download'],'onclick'=>"list_action_set('download'); list_form_submit('form_list');"]);
-	}
 	if (permission_exists('email_log_delete') && $result) {
 		echo button::create(['type'=>'button','label'=>$text['button-delete'],'icon'=>$_SESSION['theme']['button_icon_delete'],'name'=>'btn_delete','onclick'=>"modal_open('modal-delete','btn_delete');"]);
 	}
 	echo 		"<form id='form_search' class='inline' method='get'>\n";
 	if (permission_exists('email_log_all')) {
@@ -284,11 +274,11 @@
 	echo "<input type='hidden' id='action' name='action' value=''>\n";
 	echo "<input type='hidden' name='search' value=\"".escape($search)."\">\n";
 
 	echo "<table class='list'>\n";
 	echo "<tr class='list-header'>\n";
-	if (permission_exists('email_log_download') || permission_exists('email_log_resend') || permission_exists('email_log_delete')) {
+	if (permission_exists('email_log_resend') || permission_exists('email_log_delete')) {
 		echo "	<th class='checkbox'>\n";
 		echo "		<input type='checkbox' id='checkbox_all' name='checkbox_all' onclick='list_all_toggle();' ".($result ?: "style='visibility: hidden;'").">\n";
 		echo "	</th>\n";
 	}
 	if ($_GET['show'] == "all" && permission_exists('email_log_all')) {
@@ -307,11 +297,11 @@
 	if (is_array($result) && @sizeof($result) != 0) {
 		$x = 0;
 		foreach($result as $row) {
 			$list_row_url = "email_log_view.php?id=".urlencode($row['email_log_uuid']);
 			echo "<tr class='list-row' href='".$list_row_url."'>\n";
-			if (permission_exists('email_log_download') || permission_exists('email_log_resend') || permission_exists('email_log_delete')) {
+			if (permission_exists('email_log_resend') || permission_exists('email_log_delete')) {
 				echo "	<td class='checkbox'>\n";
 				echo "		<input type='checkbox' name='emails[$x][checked]' id='checkbox_".$x."' value='true' onclick=\"if (!this.checked) { document.getElementById('checkbox_all').checked = false; }\">\n";
 				echo "		<input type='hidden' name='emails[$x][uuid]' value='".escape($row['email_log_uuid'])."' />\n";
 				echo "	</td>\n";
 			}
@@ -324,13 +314,10 @@
 			echo "	<td>".$text['label-status_'.escape($row['status'])]."</td>\n";
 			echo "	<td class='middle button center no-link no-wrap'>";
 			if (permission_exists('email_log_resend')) {
 				echo button::create(['type'=>'button','title'=>$text['button-resend'],'icon'=>'paper-plane','onclick'=>"list_self_check('checkbox_".$x."'); list_action_set('resend'); list_form_submit('form_list')"]);
 			}
-			if (permission_exists('email_log_download')) {
-				echo button::create(['type'=>'button','title'=>$text['button-download'],'icon'=>$_SESSION['theme']['button_icon_download'],'onclick'=>"list_self_check('checkbox_".$x."'); list_action_set('download'); list_form_submit('form_list')"]);
-			}
 			echo "	</td>\n";
 			echo "	<td class='description overflow hide-sm-dn no-link'>";
 			echo button::create(['type'=>'button','class'=>'link','label'=>$text['label-reference_cdr'],'link'=>PROJECT_PATH.'/app/xml_cdr/xml_cdr_details.php?id='.urlencode($row['call_uuid'])]);
 			echo "		".($call[$row['call_uuid']]['caller_id_name'] != '' ? "&nbsp;&nbsp;".$call[$row['call_uuid']]['caller_id_name'].(is_numeric($call[$row['call_uuid']]['caller_id_number']) ? ' ('.format_phone($call[$row['call_uuid']]['caller_id_number']).')' : null) : $call[$row['call_uuid']]['caller_id_number']);
 			if ($call[$row['call_uuid']]['destination_number']) {
diff --git a/app/email_logs/resources/classes/email_logs.php b/app/email_logs/resources/classes/email_logs.php
index 6542543771..ae637323cd 100644
--- a/app/email_logs/resources/classes/email_logs.php
+++ b/app/email_logs/resources/classes/email_logs.php
@@ -196,131 +196,9 @@ if (!class_exists('email_logs')) {
 					}
 
 			}
 		}
 
-		/**
-		 * download records
-		 */
-		public function download($records) {
-			if (permission_exists($this->permission_prefix.'download')) {
-
-				//add multi-lingual support
-					$language = new text;
-					$text = $language->get();
-
-				//validate the token
-					$token = new token;
-					if (!$token->validate('/app/email_logs/email_logs.php')) {
-						message::add($text['message-invalid_token'],'negative');
-						header('Location: '.$this->list_page);
-						exit;
-					}
-
-				//download multiple records (eventually zip individual emails together)
-					if (is_array($records) && @sizeof($records) != 0) {
-
-						//retrieve checked records
-							foreach($records as $x => $record) {
-								if ($record['checked'] == 'true' && is_uuid($record['uuid'])) {
-									$uuids[] = $record['uuid'];
-								}
-							}
-
-						//download emails
-							if (is_array($uuids) && @sizeof($uuids) != 0) {
-								foreach ($uuids as $x => $uuid) {
-
-									//get email details
-										$sql = "select call_uuid, sent_date, type, email from v_email_logs ";
-										$sql .= "where email_log_uuid = :email_log_uuid ";
-										$parameters['email_log_uuid'] = $uuid;
-										$database = new database;
-										$row = $database->select($sql, $parameters, 'row');
-										if (is_array($row) && @sizeof($row) != 0 && is_uuid($row['call_uuid'])) {
-
-											//santize filename components
-												$sent_date = str_replace('-','', $row['sent_date']);
-												$sent_date = str_replace(':','', $sent_date);
-												$sent_date = str_replace(' ','_', $sent_date);
-												$type = strtolower($row['type']);
-												$email_filename = $sent_date.'_'.$type.'_'.$row['call_uuid'].'.eml';
-
-											//single email
-												if (@sizeof($uuids) == 1) {
-
-													//set headers
-														header("Content-Type: message/rfc822");
-														header('Content-Disposition: attachment; filename="'.$email_filename.'"');
-														header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
-														header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
-														header("Content-Length: ".strlen($row['email']));
-
-													//output content
-														echo $row['email'];
-														exit;
-												}
-
-											//multiple emails
-												else {
-													if (is_dir($_SESSION['server']['temp']['dir'])) {
-
-														if (file_put_contents($_SESSION['server']['temp']['dir'].'/'.$email_filename, $row['email'])) {
-															$email_files[] = $_SESSION['server']['temp']['dir'].'/'.$email_filename;
-														}
-													}
-												}
-
-										}
-										unset($sql, $parameters, $row);
-								}
-
-								//download compressed file
-								if (@sizeof($email_files) != 0) {
-
-									//define compressed file name
-										$compressed_filename = 'emails_'.date('Ymd_His').'.zip';
-
-									//compress email files
-										$command = 'zip -mj '.$_SESSION['server']['temp']['dir'].'/'.$compressed_filename.' '.implode(' ', $email_files).' 2>&1';
-										exec($command, $response, $restore_errlevel);
-										unset($command);
-
-									//push download
-										if (file_exists($_SESSION['server']['temp']['dir'].'/'.$compressed_filename)) {
-
-											//open file
-												session_cache_limiter('public');
-												$fd = fopen($_SESSION['server']['temp']['dir'].'/'.$compressed_filename, 'rb');
-
-											//set headers
-												header("Content-Type: application/zip");
-												header('Content-Disposition: attachment; filename="'.$compressed_filename.'"');
-												header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
-												header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
-												header("Content-Length: ".filesize($_SESSION['server']['temp']['dir'].'/'.$compressed_filename));
-
-											//output file content
-												ob_clean();
-												fpassthru($fd);
-												fclose($fd);
-
-											//remove compressed file
-												@unlink($_SESSION['server']['temp']['dir'].'/'.$compressed_filename);
-												exit;
-
-										}
-
-								}
-
-							}
-
-					}
-
-			}
-
-		} //method
-
 	} //class
 }
 
-?>
\ No newline at end of file
+?>

Vulnerable code: 			case 'download':
				if (permission_exists('email_log_download')) {
					$obj = new email_logs;
					$obj->download($emails);
					message::add($text['message-download_failed'],'negative',7000); //download failed, set message
				}
				break;
	if (permission_exists('email_log_download') && $result) {
		echo button::create(['type'=>'button','label'=>$text['button-download'],'icon'=>$_SESSION['theme']['button_icon_download'],'onclick'=>"list_action_set('download'); list_form_submit('form_list');"]);
	}
	if (permission_exists('email_log_download') || permission_exists('email_log_resend') || permission_exists('email_log_delete')) {
			if (permission_exists('email_log_download') || permission_exists('email_log_resend') || permission_exists('email_log_delete')) {
			if (permission_exists('email_log_download')) {
				echo button::create(['type'=>'button','title'=>$text['button-download'],'icon'=>$_SESSION['theme']['button_icon_download'],'onclick'=>"list_self_check('checkbox_".$x."'); list_action_set('download'); list_form_submit('form_list')"]);
			}
		/**
		 * download records
		 */
		public function download($records) {
			if (permission_exists($this->permission_prefix.'download')) {

				//add multi-lingual support
					$language = new text;
					$text = $language->get();

				//validate the token
					$token = new token;
					if (!$token->validate('/app/email_logs/email_logs.php')) {
						message::add($text['message-invalid_token'],'negative');
						header('Location: '.$this->list_page);
						exit;
					}

				//download multiple records (eventually zip individual emails together)
					if (is_array($records) && @sizeof($records) != 0) {

						//retrieve checked records
							foreach($records as $x => $record) {
								if ($record['checked'] == 'true' && is_uuid($record['uuid'])) {
									$uuids[] = $record['uuid'];
								}
							}

						//download emails
							if (is_array($uuids) && @sizeof($uuids) != 0) {
								foreach ($uuids as $x => $uuid) {

									//get email details
										$sql = "select call_uuid, sent_date, type, email from v_email_logs ";
										$sql .= "where email_log_uuid = :email_log_uuid ";
										$parameters['email_log_uuid'] = $uuid;
										$database = new database;
										$row = $database->select($sql, $parameters, 'row');
										if (is_array($row) && @sizeof($row) != 0 && is_uuid($row['call_uuid'])) {

											//santize filename components
												$sent_date = str_replace('-','', $row['sent_date']);
												$sent_date = str_replace(':','', $sent_date);
												$sent_date = str_replace(' ','_', $sent_date);
												$type = strtolower($row['type']);
												$email_filename = $sent_date.'_'.$type.'_'.$row['call_uuid'].'.eml';

											//single email
												if (@sizeof($uuids) == 1) {

													//set headers
														header("Content-Type: message/rfc822");
														header('Content-Disposition: attachment; filename="'.$email_filename.'"');
														header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
														header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
														header("Content-Length: ".strlen($row['email']));

													//output content
														echo $row['email'];
														exit;
												}

											//multiple emails
												else {
													if (is_dir($_SESSION['server']['temp']['dir'])) {

														if (file_put_contents($_SESSION['server']['temp']['dir'].'/'.$email_filename, $row['email'])) {
															$email_files[] = $_SESSION['server']['temp']['dir'].'/'.$email_filename;
														}
													}
												}

										}
										unset($sql, $parameters, $row);
								}

								//download compressed file
								if (@sizeof($email_files) != 0) {

									//define compressed file name
										$compressed_filename = 'emails_'.date('Ymd_His').'.zip';

									//compress email files
										$command = 'zip -mj '.$_SESSION['server']['temp']['dir'].'/'.$compressed_filename.' '.implode(' ', $email_files).' 2>&1';
										exec($command, $response, $restore_errlevel);
										unset($command);

									//push download
										if (file_exists($_SESSION['server']['temp']['dir'].'/'.$compressed_filename)) {

											//open file
												session_cache_limiter('public');
												$fd = fopen($_SESSION['server']['temp']['dir'].'/'.$compressed_filename, 'rb');

											//set headers
												header("Content-Type: application/zip");
												header('Content-Disposition: attachment; filename="'.$compressed_filename.'"');
												header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
												header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
												header("Content-Length: ".filesize($_SESSION['server']['temp']['dir'].'/'.$compressed_filename));

											//output file content
												ob_clean();
												fpassthru($fd);
												fclose($fd);

											//remove compressed file
												@unlink($_SESSION['server']['temp']['dir'].'/'.$compressed_filename);
												exit;

										}

								}

							}

					}

			}

		} //method

?>
Fixed code: 	if (permission_exists('email_log_resend') || permission_exists('email_log_delete')) {
			if (permission_exists('email_log_resend') || permission_exists('email_log_delete')) {
?>