Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/bit-form/bitforms.php
+++ b/bit-form/bitforms.php
@@ -4,7 +4,7 @@
* Plugin Name: Bit Form
* Plugin URI: https://www.bitapps.pro/bit-form
* Description: Contact Form Builder Plugin: Multi Step Contact Form, Payment Form, Custom Contact Form Plugin by Bit Form
- * Version: 3.1.1
+ * Version: 3.1.2
* Author: Contact Form Builder - Bit Form
* Author URI: https://www.bitapps.pro
* Text Domain: bit-form
@@ -22,7 +22,7 @@
}
// Define most essential constants.
-define('BITFORMS_VERSION', '3.1.1');
+define('BITFORMS_VERSION', '3.1.2');
define('BITFORMS_PLUGIN_MAIN_FILE', __FILE__);
define('BITFORMS_REQUIRED_BITFORMPRO_VERSION', '3.1.0');
--- a/bit-form/includes/Core/Form/FormManager.php
+++ b/bit-form/includes/Core/Form/FormManager.php
@@ -909,6 +909,30 @@
return $updatedValue;
}
+ private function normalizeOldFileValues($stored_files, $old_values)
+ {
+ $stored_files = is_array($stored_files) ? $stored_files : [];
+ $old_values = is_array($old_values) ? $old_values : explode(',', (string) $old_values);
+
+ $normalized_values = [];
+ foreach ($old_values as $value) {
+ if (!is_string($value) && !is_numeric($value)) {
+ continue;
+ }
+
+ $trimmed_value = trim((string) $value);
+ if ('' === $trimmed_value) {
+ continue;
+ }
+
+ if (in_array($trimmed_value, $stored_files, true)) {
+ $normalized_values[] = $trimmed_value;
+ }
+ }
+
+ return array_values(array_unique($normalized_values));
+ }
+
public function updateFormEntry($updatedValue, $formID, $entryID)
{
// CSRF / entry-token verified upstream via FrontendFormManager::handleUpdateEntry() before this method is invoked.
@@ -1020,7 +1044,10 @@
if (isset($repeaterRow[$field_key]) && !empty($repeaterRow[$field_key])) {
$repeaterExistFiles[$index] = json_decode($repeaterRow[$field_key], true);
}
- $repeaterFiles_old[$index] = empty($updatedValue[$field_key . '_old'][$index]) ? [] : explode(',', $updatedValue[$field_key . '_old'][$index]);
+ if (!is_array($repeaterExistFiles[$index])) {
+ $repeaterExistFiles[$index] = [];
+ }
+ $repeaterFiles_old[$index] = $this->normalizeOldFileValues($repeaterExistFiles[$index], empty($updatedValue[$field_key . '_old'][$index]) ? [] : $updatedValue[$field_key . '_old'][$index]);
$repeaterDeleted_files[$index] = array_diff($repeaterExistFiles[$index], $repeaterFiles_old[$index]);
$fileHandler->deleteFiles($formID, $entryID, $repeaterDeleted_files[$index]);
}
@@ -1035,8 +1062,11 @@
]
);
if (!is_wp_error($file_exists) && count($file_exists) > 0) {
- $files_in_db = json_decode($file_exists[0]->meta_value);
- $files_old = empty($updatedValue[$field_key . '_old']) ? [] : explode(',', $updatedValue[$field_key . '_old']);
+ $files_in_db = json_decode($file_exists[0]->meta_value, true);
+ if (!is_array($files_in_db)) {
+ $files_in_db = [];
+ }
+ $files_old = $this->normalizeOldFileValues($files_in_db, empty($updatedValue[$field_key . '_old']) ? [] : $updatedValue[$field_key . '_old']);
$deleted_file = array_diff($files_in_db, $files_old);
if (count($deleted_file) > 0) {
$fileHandler->deleteFiles($formID, $entryID, $deleted_file);
--- a/bit-form/includes/Core/Util/FileHandler.php
+++ b/bit-form/includes/Core/Util/FileHandler.php
@@ -122,11 +122,57 @@
return $file_upoalded;
}
+ public static function isSafeFileName($name)
+ {
+ if (!is_string($name)) {
+ return false;
+ }
+
+ $trimmed = trim($name);
+ if ('' === $trimmed) {
+ return false;
+ }
+
+ $baseName = basename($trimmed);
+ if ('' === $baseName || $baseName !== $trimmed || 'index.php' === $baseName) {
+ return false;
+ }
+
+ return sanitize_file_name($trimmed) === $trimmed;
+ }
+
public function deleteFiles($form_id, $entry_id, $files)
{
$_upload_dir = self::getEntriesFileUploadDir($form_id, $entry_id);
- foreach ($files as $name) {
- wp_delete_file($_upload_dir . DIRECTORY_SEPARATOR . $name);
+ $resolvedBitformsUploadDir = realpath(BITFORMS_UPLOAD_DIR);
+ $resolvedUploadDir = realpath($_upload_dir);
+ if (false === $resolvedBitformsUploadDir || false === $resolvedUploadDir) {
+ return;
+ }
+
+ $bitformsUploadDirPrefix = trailingslashit(wp_normalize_path($resolvedBitformsUploadDir));
+ $uploadDirPrefix = trailingslashit(wp_normalize_path($resolvedUploadDir));
+ if (0 !== strpos($uploadDirPrefix, $bitformsUploadDirPrefix)) {
+ return;
+ }
+
+ foreach ((array) $files as $name) {
+ if (!self::isSafeFileName($name)) {
+ continue;
+ }
+
+ $candidatePath = $resolvedUploadDir . DIRECTORY_SEPARATOR . $name;
+ $resolvedPath = realpath($candidatePath);
+ if (false === $resolvedPath || !is_file($resolvedPath)) {
+ continue;
+ }
+
+ $normalizedPath = wp_normalize_path($resolvedPath);
+ if (0 !== strpos($normalizedPath, $uploadDirPrefix)) {
+ continue;
+ }
+
+ wp_delete_file($resolvedPath);
}
}
--- a/bit-form/includes/Frontend/Ajax/FrontendAjax.php
+++ b/bit-form/includes/Frontend/Ajax/FrontendAjax.php
@@ -45,6 +45,9 @@
wp_send_json_error(__('Form ID not found', 'bit-form'), 400);
}
$FrontendFormManager = FrontendFormManager::getInstance($form_id);
+ if (!$FrontendFormManager->checkStatus()) {
+ wp_send_json_error(__('Form is not active', 'bit-form'), 403);
+ }
$FrontendFormManager->fieldNameReplaceOfPost();
$validateStatus = $FrontendFormManager->beforeSubmittedValidate(false);
if (is_wp_error($validateStatus)) {
@@ -60,6 +63,9 @@
// CSRF verified inside FrontendFormManager::handleSubmission() via verifySubmissionNonce() using HMAC-SHA256 token (Helpers::csrfDecrypted).
$form_id = isset($_POST['bitforms_id']) ? str_replace('bitforms_', '', sanitize_text_field(wp_unslash($_POST['bitforms_id']))) : '';
$FrontendFormManager = FrontendFormManager::getInstance($form_id);
+ if (!$FrontendFormManager->checkStatus()) {
+ wp_send_json_error(__('Form is not active', 'bit-form'), 403);
+ }
$submitSatus = $FrontendFormManager->handleSubmission();
if (is_wp_error($submitSatus)) {
do_action('bitform_submit_error', $form_id, $submitSatus);
@@ -86,6 +92,9 @@
$GLOBALS['bitform_entry_id'] = $entryId;
if (Helpers::validateEntryTokenAndUser($entryToken, $entryId) || FrontendHelpers::is_current_user_can_access($form_id, 'entryEditAccess')) {
$FrontendFormManager = FrontendFormManager::getInstance($form_id);
+ if (!$FrontendFormManager->checkStatus()) {
+ wp_send_json_error(__('Form is not active', 'bit-form'), 403);
+ }
$updateStatus = $FrontendFormManager->handleUpdateEntry();
if (is_wp_error($updateStatus)) {
do_action('bitform_update_error', $form_id, $updateStatus);
@@ -148,6 +157,10 @@
wp_send_json_error('Form Id not found', 400);
} else {
$formId = absint($data->formId);
+ $frontendFormManager = FrontendFormManager::getInstance($formId);
+ if (!$frontendFormManager->isExist() || !$frontendFormManager->checkStatus()) {
+ wp_send_json_error(__('Form is not active', 'bit-form'), 403);
+ }
$fields = $this->hiddenFields($formId);
$properties = $this->hiddenPropeties($formId);
wp_send_json_success(['hidden_fields'=>$fields, 'hidden_properties'=>$properties]);
@@ -167,6 +180,11 @@
$submitted_fields = [];
if (isset($request->id, $request->cronNotOk)) {
$formID = absint(str_replace('bitforms_', '', sanitize_text_field($request->id)));
+ $frontendFormManager = FrontendFormManager::getInstance($formID);
+ if (!$frontendFormManager->isExist() || !$frontendFormManager->checkStatus()) {
+ Log::debug_log('Inactive or non-existent form for workflow trigger. FormID=' . $formID);
+ wp_send_json_error(['message' => 'Form is not active'], 403);
+ }
$cronNotOk = $request->cronNotOk;
// Validate and sanitize entry ID and log ID
--- a/bit-form/vendor/composer/installed.php
+++ b/bit-form/vendor/composer/installed.php
@@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'name' => 'bitcode/bitform',
- 'pretty_version' => 'v3.1.1',
- 'version' => '3.1.1.0',
- 'reference' => 'd285d8ff15c6b2784bf33e0f335e63fe553385ad',
+ 'pretty_version' => 'v3.1.2',
+ 'version' => '3.1.2.0',
+ 'reference' => '30f7ba5d513fa06bb86e85efe22fed3bdc4f7d65',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -20,9 +20,9 @@
'dev_requirement' => false,
),
'bitcode/bitform' => array(
- 'pretty_version' => 'v3.1.1',
- 'version' => '3.1.1.0',
- 'reference' => 'd285d8ff15c6b2784bf33e0f335e63fe553385ad',
+ 'pretty_version' => 'v3.1.2',
+ 'version' => '3.1.2.0',
+ 'reference' => '30f7ba5d513fa06bb86e85efe22fed3bdc4f7d65',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),