CVE-2022-0196 (published 2022-01-13T01:15Z)

phoronix-test-suite is vulnerable to Cross-Site Request Forgery (CSRF)

Severity: MEDIUM
CVSS v2 base score: 6.8 (AV:N/AC:M/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-352
cwe_name: Cross-Site Request Forgery (CSRF)
description: The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
extended_description: When a web server is designed to receive a request from a client without any mechanism for verifying that it was intentionally sent, then it might be possible for an attacker to trick a client into making an unintentional request to the web server which will be treated as an authentic request. This can be done via a URL, image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.
         

Fix information:
repo_url: https://github.com/phoronix-test-suite/phoronix-test-suite
commit_details: repo_url: https://github.com/phoronix-test-suite/phoronix-test-suite
author: Michael Larabel
author_date: 2022-01-11 18:45:29-06:00
msg: phoromatic: Some additional input sanitization and starting on some CSRF token handling
repository: repo_url: https://github.com/phoronix-test-suite/phoronix-test-suite
repo_name: phoronix-test-suite/phoronix-test-suite
description: The Phoronix Test Suite open-source, cross-platform automated testing/benchmarking software.
repo_language: PHP
owner: phoronix-test-suite
file_changes: filename: pts_strings.php
change_type: ModificationType.MODIFY
diff: @@ -785,7 +785,7 @@ class pts_strings
 	}
 	public static function simple($input)
 	{
-		return empty($str) ? '' : pts_strings::keep_in_string($input, pts_strings::CHAR_LETTER | pts_strings::CHAR_NUMERIC | pts_strings::CHAR_DASH | pts_strings::CHAR_DECIMAL | pts_strings::CHAR_SPACE | pts_strings::CHAR_UNDERSCORE | pts_strings::CHAR_COMMA | pts_strings::CHAR_AT | pts_strings::CHAR_COLON);
+		return empty($input) ? '' : pts_strings::keep_in_string($input, pts_strings::CHAR_LETTER | pts_strings::CHAR_NUMERIC | pts_strings::CHAR_DASH | pts_strings::CHAR_DECIMAL | pts_strings::CHAR_SPACE | pts_strings::CHAR_UNDERSCORE | pts_strings::CHAR_COMMA | pts_strings::CHAR_AT | pts_strings::CHAR_COLON);
 	}
 }
 

programming_language: PHP
filename: phoromatic_local_suites.php
change_type: ModificationType.MODIFY
diff: @@ -39,7 +39,7 @@ class phoromatic_local_suites implements pts_webui_interface
 		$suite_dir = phoromatic_server::phoromatic_account_suite_path($_SESSION['AccountID']);
 		$main = '<h1>Local Suites</h1><p>These are test suites created by you or another account within your group. Suites are an easy collection of test profiles. New suits can be trivially made via the <a href="/?build_suite">build suite</a> page.</p>';
 
-		if(!PHOROMATIC_USER_IS_VIEWER && isset($PATH[0]) && $PATH[0] == 'delete')
+		if(!PHOROMATIC_USER_IS_VIEWER && isset($PATH[0]) && $PATH[0] == 'delete' && verify_submission_token())
 		{
 			foreach(explode(',', $PATH[1]) as $id)
 			{
@@ -64,7 +64,7 @@ class phoromatic_local_suites implements pts_webui_interface
 			$main .= '<p><em>' . pts_strings::sanitize($test_suite->get_description()) . '</em></p>';
 			if(!PHOROMATIC_USER_IS_VIEWER)
 			{
-				$main .= '<p><a href="?build_suite/' . $id . '">Edit Suite</a> - <a href="?local_suites/delete/' . $id . '">Delete Suite</a></p>';
+				$main .= '<p><a href="?build_suite/' . $id . '">Edit Suite</a> - <a href="?local_suites/delete/' . $id . append_token_to_url() . '">Delete Suite</a></p>';
 			}
 			$main .= '<div style="max-height: 400px; width: 80%; overflow-y: scroll;">';
 			$test_suite->sort_contained_tests();

programming_language: PHP
filename: phoromatic_schedules.php
change_type: ModificationType.MODIFY
diff: @@ -123,7 +123,7 @@ class phoromatic_schedules implements pts_webui_interface
 					else if(isset($PATH[1]) && $PATH[1] == 'delete-trigger' && !empty($PATH[2]))
 					{
 						// REMOVE TRIGGER
-						$trigger = base64_decode($PATH[2]);
+						$trigger = pts_strings::sanitize(base64_decode($PATH[2]));
 						$stmt = phoromatic_server::$db->prepare('DELETE FROM phoromatic_schedules_triggers WHERE AccountID = :account_id AND Trigger = :trigger AND ScheduleID = :schedule_id');
 						$stmt->bindValue(':account_id', $_SESSION['AccountID']);
 						$stmt->bindValue(':schedule_id', $PATH[0]);
@@ -132,7 +132,7 @@ class phoromatic_schedules implements pts_webui_interface
 						if($result)
 							$main .= '<h2 style="color: red;">Trigger Removed: ' . $trigger . '</h2>';
 					}
-					else if(isset($PATH[1]) && in_array($PATH[1], array('activate', 'deactivate')))
+					else if(isset($PATH[1]) && in_array($PATH[1], array('activate', 'deactivate')) && verify_submission_token())
 					{
 						switch($PATH[1])
 						{
@@ -201,11 +201,11 @@ class phoromatic_schedules implements pts_webui_interface
 
 					if($row['State'] == 1)
 					{
-						$main .= '<a href="?schedules/' . $PATH[0] . '/deactivate">Deactivate Schedule</a>';
+						$main .= '<a href="?schedules/' . $PATH[0] . '/deactivate' . append_token_to_url()  . '">Deactivate Schedule</a>';
 					}
 					else
 					{
-						$main .= '<a href="?schedules/' . $PATH[0] . '/activate">Activate Schedule</a>';
+						$main .= '<a href="?schedules/' . $PATH[0] . '/activate' . append_token_to_url()  . '">Activate Schedule</a>';
 					}
 
 					$main .= '</p>';

programming_language: PHP
filename: phoromatic_users.php
change_type: ModificationType.MODIFY
diff: @@ -42,7 +42,7 @@ class phoromatic_users implements pts_webui_interface
 			return;
 		}
 
-		if(isset($_POST['group_name']))
+		if(isset($_POST['group_name']) && verify_submission_token())
 		{
 			phoromatic_quit_if_invalid_input_found(array('group_name'));
 			$stmt = phoromatic_server::$db->prepare('UPDATE phoromatic_accounts SET GroupName = :group_name WHERE AccountID = :account_id');
@@ -242,7 +242,7 @@ class phoromatic_users implements pts_webui_interface
 			</form>';
 
 		$group_name = phoromatic_server::account_id_to_group_name($_SESSION['AccountID']);
-		$main .= '<hr /><form action="' . $_SERVER['REQUEST_URI'] . '" name="group_name" id="group_name" method="post"><h2>Group Name</h2>
+		$main .= '<hr /><form action="' . $_SERVER['REQUEST_URI'] . '" name="group_name" id="group_name" method="post"><h2>Group Name</h2>' . write_token_in_form() . '
 			<p>A group name is an alternative, user-facing name for this set of accounts. The group name feature is primarily useful for being able to better distinguish results between groups when sharing of data within a large organization, etc. The group name is showed next to test results when viewing results from multiple groups/accounts.</p>
 			<h3>Group Name</h3>
 			<p><input type="text" name="group_name" value="' . $group_name . '" /></p>

programming_language: PHP
filename: phoromatic_welcome.php
change_type: ModificationType.MODIFY
diff: @@ -130,6 +130,7 @@ class phoromatic_welcome implements pts_webui_interface
 					$_SESSION['AccountID'] = $account_id;
 					$_SESSION['AdminLevel'] = $admin_level;
 					$_SESSION['CreatedOn'] = $created_on;
+					$_SESSION['Token'] = sha1($account_salt . (function_exists('random_bytes') ? bin2hex(random_bytes(32)) : rand()) . PTS_CORE_VERSION . time());
 					$_SESSION['CoreVersionOnSignOn'] = PTS_CORE_VERSION;
 					$account_salt = phoromatic_server::$db->exec('UPDATE phoromatic_users SET LastIP = \'' . $_SERVER['REMOTE_ADDR'] . '\', LastLogin = \'' . phoromatic_server::current_time() . '\' WHERE UserName = "' . $matching_user['UserName'] . '"');
 					session_write_close();

programming_language: PHP
filename: phoromatic_functions.php
change_type: ModificationType.MODIFY
diff: @@ -523,6 +523,18 @@ function phoromatic_oldest_result_for_schedule($schedule_id)
 
 	return $old_time[$schedule_id];
 }
+function write_token_in_form()
+{
+	return '<input type="hidden" name="token_submit" value="' . $_SESSION['Token'] . '" />';
+}
+function append_token_to_url()
+{
+	return '/&token_submit=' . $_SESSION['Token'];
+}
+function verify_submission_token()
+{
+	return isset($_REQUEST['token_submit']) && $_REQUEST['token_submit'] == $_SESSION['Token'];
+}
 function create_new_phoromatic_account($register_username, $register_password, $register_password_confirm, $register_email, $seed_accountid = null)
 {
 	// REGISTER NEW USER

programming_language: PHP
filename: event.php
change_type: ModificationType.MODIFY
diff: @@ -85,15 +85,16 @@ switch($_GET['type'])
 			}
 		}
 
+		$trigger = pts_strings::sanitize($_GET['trigger']);
 		$stmt = phoromatic_server::$db->prepare('INSERT INTO phoromatic_schedules_triggers (AccountID, ScheduleID, Trigger, TriggeredOn, SubTarget) VALUES (:account_id, :schedule_id, :trigger, :triggered_on, :sub_target)');
 		$stmt->bindValue(':account_id',	$user_row['AccountID']);
 		$stmt->bindValue(':schedule_id', $schedule_row['ScheduleID']);
-		$stmt->bindValue(':trigger', $_GET['trigger']);
+		$stmt->bindValue(':trigger', $trigger);
 		$stmt->bindValue(':triggered_on', phoromatic_server::current_time());
 		$stmt->bindValue(':sub_target', $sub_target);
 		if($stmt->execute())
 		{
-			echo 'Trigger ' . htmlspecialchars($_GET['trigger']) . ' added!';
+			echo 'Trigger ' . $trigger . ' added!';
 		}
 		break;
 

programming_language: PHP
filename: pts-core.php
change_type: ModificationType.MODIFY
diff: @@ -229,7 +229,7 @@ function pts_needed_extensions()
 }
 
 pts_define('PTS_VERSION', '10.8.0');
-pts_define('PTS_CORE_VERSION', 10800);
+pts_define('PTS_CORE_VERSION', 10801);
 pts_define('PTS_RELEASE_DATE', '2021125');
 pts_define('PTS_CODENAME', 'Nesseby');
 

programming_language: PHP