CVE-2022-1453: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: The RSVPMaker plugin for WordPress is vulnerable to unauthenticated SQL Injection due to missing SQL escaping and parameterization on user supplied data passed to a SQL query in the rsvpmaker-util.php file. This makes it possible for unauthenticated attackers to steal sensitive information from the database in versions up to and including 9.2.5.
CWE description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
CVSS v3 base score: 7.5
Published: 2022-05-10T20:15Z
Repository: https://github.com/davidfcarr/rsvpmaker
Language: PHP
Files changed: README.txt, rsvpmaker-admin.php, rsvpmaker-api-endpoints.php, rsvpmaker-email.php, rsvpmaker-group-email.php, rsvpmaker-plugabble.php, rsvpmaker-stripe.php, rsvpmaker.php
Commit date: 2022-04-25 09:11:49 -0400
Commit message: fixes for sql injection, card testing

Diff: diff --git a/README.txt b/README.txt
index 3e7b957..ad4bb16 100644
--- a/README.txt
+++ b/README.txt
@@ -5,12 +5,12 @@ Tags: event, calendar, rsvp, custom post type, paypal, stripe, email, mailchimp,
 Donate link: http://rsvpmaker.com/
 License: GPLv2
 License URI: http://www.gnu.org/licenses/gpl-2.0.html
 Requires PHP: 5.6
 Requires at least: 5.0
-Tested up to: 5.9.2
-Stable tag: 9.2.5
+Tested up to: 5.9.3
+Stable tag: 9.2.6
 
 Schedule events, send invitations, track RSVPs, and collect payment via PayPal or Stripe.
 
 == Description ==
 
diff --git a/rsvpmaker-admin.php b/rsvpmaker-admin.php
index 95e8f0d..7a2f43b 100644
--- a/rsvpmaker-admin.php
+++ b/rsvpmaker-admin.php
@@ -1025,10 +1025,14 @@ if(isset($_REQUEST['tab']) && $_REQUEST['tab'] == 'security')
 <option value=",|."><?php echo esc_html(number_format(1000.00, 2, ',',  '.')); ?></option>
 <option value=",| "><?php echo esc_html(number_format(1000.00, 2, ',',  ' ')); ?></option>
 </select>    
 </div>
 
+<h3><?php esc_html_e('Minimum Amount','rsvpmaker'); ?>:</h3>
+<div><input type="text" name="payment_option[payment_minimum]" value="<?php if(isset($options["payment_minimum"])) {echo esc_attr($options["payment_minimum"]);} else echo '5.00';?>" size="5" /> <br /><em><?php _e('Prevents fraudulent uses such as $1 donations to test stolen cards','rsvpmaker'); ?></em>
+</div>
+
 <h3>PayPal (REST API)</h3>
 <p><?php esc_html_e('Keys may be obtained from','rsvpmaker'); ?> <a target="_blank" href="https://developer.paypal.com/developer/applications">developer.paypal.com/developer/applications/</a></p>
 <?php
 $test_keys = sprintf(' <a href="%s">%s</a> ',admin_url('options-general.php?page=rsvpmaker-admin.php&payment_key_test=1'),__('Test Keys','rsvpmaker'));
 $pp_on = false;
@@ -5119,10 +5123,13 @@ style_formats: [
 { title: 'Inline', items: [
 	{ title: 'Bold', format: 'bold' },
 	{ title: 'Italic', format: 'italic' },
 ]},]},
 toolbar: 'bold italic link',
+relative_urls: false,
+remove_script_host : false,
+document_base_url : "'.site_url().'/",
 });	
 </script>
 <?php
 printf('<form method="post" action="%s" id="rsvpmaker_submission">',get_permalink());
 rsvpmaker_nonce();
diff --git a/rsvpmaker-api-endpoints.php b/rsvpmaker-api-endpoints.php
index 7db65f4..f7b40b0 100644
--- a/rsvpmaker-api-endpoints.php
+++ b/rsvpmaker-api-endpoints.php
@@ -383,11 +383,11 @@ class RSVPMaker_Sked_Controller extends WP_REST_Controller {
 
 
 
 	public function get_items( $request ) {
 
-		$sked = get_template_sked( $request['post_id'] );
+		$sked = get_template_sked( intval($request['post_id']) );
 
 		return new WP_REST_Response( $sked, 200 );
 
 	}
 
diff --git a/rsvpmaker-email.php b/rsvpmaker-email.php
index c71694d..09c218a 100644
--- a/rsvpmaker-email.php
+++ b/rsvpmaker-email.php
@@ -2788,11 +2788,11 @@ foreach($rsvpdata as $field => $value)
 	}
 
 $send_confirmation = get_post_meta($post->ID,'_rsvp_rsvpmaker_send_confirmation_email',true);
 $confirm_on_payment = get_post_meta($post->ID,'_rsvp_confirmation_after_payment',true);
 
-if(($send_confirmation ||!is_numeric($send_confirmation)) && empty($confirm_on_payment) )//if it hasn't been set to 0, send it
+if(($send_confirmation ||!is_numeric($send_confirmation)) && $rsvpdata['yesno'] && empty($confirm_on_payment) )//if it hasn't been set to 0, send it
 {
 $confirmation_subject = $templates['confirmation']['subject']; 
 foreach($rsvpdata as $field => $value)
 	$confirmation_subject = str_replace('['.$field.']',$value,$confirmation_subject);
 
diff --git a/rsvpmaker-group-email.php b/rsvpmaker-group-email.php
index 5ca0880..ca60652 100644
--- a/rsvpmaker-group-email.php
+++ b/rsvpmaker-group-email.php
@@ -44,17 +44,18 @@ function rsvpmaker_relay_menu_pages() {
 		__( 'Group Email', 'rsvpmaker' ),
 		'manage_options',
 		'rsvpmaker_relay_manual_test',
 		'rsvpmaker_relay_manual_test'
 	);
+
 	add_submenu_page(
 		$parent_slug,
 		__( 'Group Email Log', 'rsvpmaker' ),
 		__( 'Group Email Log', 'rsvpmaker' ),
 		'manage_options',
-		'rsvpmaker_relay_log',
-		'rsvpmaker_relay_log'
+		'rsvpmaker_relay_queue_monitor',
+		'rsvpmaker_relay_queue_monitor'
 	);
 
 }
 
 add_action( 'admin_menu', 'rsvpmaker_relay_menu_pages' );
@@ -933,13 +934,34 @@ function rsvpmaker_qemail ($mail, $recipients) {
 	rsvpmaker_debug_log($post_id,'rsvpmaker_qemail_insert_post_result');
 	if(is_multisite())
 		restore_current_blog();
 }
 
-function rsvpmaker_relay_log() {
+function rsvpmaker_relay_queue_monitor () {
+	do_action('rsvpmaker_relay_queue_monitor');
 	global $wpdb;
-	$sql = "SELECT * from $wpdb->posts JOIN $wpdb->postmeta ON $wpdb->posts.ID = $wpdb->postmeta.post_id WHERE post_type='rsvpemail' AND meta_key LIKE 'rsvp%' ORDER BY ID DESC, meta_key LIMIT 0,500";
+	$sql = "SELECT ID, post_title, wpt_postmeta.meta_key, wpt_postmeta.meta_value FROM `wpt_posts` JOIN wpt_postmeta on wpt_posts.ID = wpt_postmeta.post_id WHERE post_type='rsvpemail' AND (post_status='draft' OR post_status='publish') AND meta_key='rsvprelay_to' ORDER BY ID DESC";
 	$results = $wpdb->get_results($sql);
-	foreach($results as $row) {
-		printf('<p>%s<br><strong>%s</strong> %s</p>',$row->post_title,$row->meta_key,$row->meta_value);
-	}	
-}
+	$was = 0;
+	echo '<h1>In Queue</h2>';
+	if(empty($results))
+		echo '<p>none</p>';
+	else
+	foreach($results as $row)
+	{
+		if($row->ID != $was)
+			printf('<h2>%s</h2>',$row->post_title);
+		printf('<p>%s %s</p>',$row->meta_key, $row->meta_value);
+		$was = $row->ID;
+	}
+	echo '<h1>Sent (200 Latest)</h2>';
+	$sql = "SELECT ID, post_title, wpt_postmeta.meta_key, wpt_postmeta.meta_value FROM `wpt_posts` JOIN wpt_postmeta on wpt_posts.ID = wpt_postmeta.post_id WHERE post_type='rsvpemail' AND meta_key='rsvpmail_sent' ORDER BY ID DESC LIMIT 0, 200";
+	$results = $wpdb->get_results($sql);
+	$was = 0;
+	foreach($results as $row)
+	{
+		if($row->ID != $was)
+			printf('<h2>%s</h2>',$row->post_title);
+		printf('<p>%s %s</p>',$row->meta_key, $row->meta_value);
+		$was = $row->ID;
+	}
+}
\ No newline at end of file
diff --git a/rsvpmaker-plugabble.php b/rsvpmaker-plugabble.php
index 8c3971b..959c810 100644
--- a/rsvpmaker-plugabble.php
+++ b/rsvpmaker-plugabble.php
@@ -2571,17 +2571,17 @@ if ( ! function_exists( 'save_rsvp' ) ) {
 				$rsvpdata['rsvpmessage'] = $rsvp_confirm; // confirmation message from editor
 
 				// $rsvpdata["rsvptitle"] = $post->post_title;
 
 				$rsvpdata['rsvpyesno'] = $answer;
-
+				$rsvpdata['yesno'] = $yesno;
 				$rsvpdata['rsvpdate'] = $date;
 
 				$rsvp_options['rsvplink'] = get_rsvp_link( $post->ID );
 
 				$rsvpdata['rsvpupdate'] = preg_replace( '/#rsvpnow">[^<]+/', '#rsvpnow">' . $rsvp_options['update_rsvp'], str_replace( '*|EMAIL|*', $rsvp['email'] . '&update=' . $rsvp_id, $rsvp_options['rsvplink'] ) );
-
+				
 				rsvp_notifications_via_template( $rsvp, $rsvp_to, $rsvpdata );
 
 				// rsvp_notifications ($rsvp,$rsvp_to,$subject,$cleanmessage,$rsvp_confirm);
 
 			}
diff --git a/rsvpmaker-stripe.php b/rsvpmaker-stripe.php
index 8b96f20..b1d16b9 100644
--- a/rsvpmaker-stripe.php
+++ b/rsvpmaker-stripe.php
@@ -41,11 +41,11 @@ function rsvpmaker_stripecharge( $atts ) {
 	if ( is_admin() || wp_is_json_request() ) {
 
 		return;
 	}
 
-	global $current_user;
+	global $current_user, $rsvp_options;
 
 	$vars['description'] = ( ! empty( $atts['description'] ) ) ? $atts['description'] : __( 'charge from', 'rsvpmaker' ) . ' ' . get_bloginfo( 'name' );
 
 	$vars['paymentType'] = $paymentType = ( empty( $atts['paymentType'] ) ) ? 'once' : $atts['paymentType'];
 
@@ -99,10 +99,11 @@ function rsvpmaker_stripecharge( $atts ) {
 
 
 $rsvpmaker_stripe_form = '';
 
 function rsvpmaker_stripe_form( $vars, $show = false ) {
+	global $rsvp_options;
 
 	rsvpmaker_debug_log( $vars, 'rsvpmaker_stripe_form' );
 
 	global $post, $rsvp_options, $current_user, $button, $rsvpmaker_stripe_form, $wpdb;
 	if ( ! $show ) {
@@ -173,11 +174,11 @@ function rsvpmaker_stripe_form( $vars, $show = false ) {
 	if(empty($keys['pk']) && $keys['sandbox_pk'])
 		;//if Stripe not enabled
 	elseif ( isset( $vars['paymentType'] ) && ( $vars['paymentType'] == 'donation' ) ) {
 		if(isset($_GET['amount']))
 			$vars['amount'] = sanitize_text_field($_GET['amount']); //needed when both Stripe and PayPal are active
-		$output = sprintf( '<form action="%s" method="get">%s (%s): <input type="text" name="amount" value="%s"><br />%s<br /><textarea name="stripenote" cols="80" rows="2"></textarea><br /><input type="hidden" name="txid" value="%s"><button class="stripebutton">%s</button>%s</form>', $url, __( 'Amount', 'rsvpmaker' ), esc_attr( strtoupper( $vars['currency'] ) ), esc_attr( $vars['amount'] ), __('Note','rsvpmaker'), esc_attr( $idempotency_key ), __( 'Pay with Card' ), rsvpmaker_nonce('return') );
+		$output = sprintf( '<form action="%s" method="get">%s (%s, %s %s): <input type="text" name="amount" value="%s"><br />%s<br /><textarea name="stripenote" cols="80" rows="2"></textarea><br /><input type="hidden" name="txid" value="%s"><button class="stripebutton">%s</button>%s</form>', $url, __( 'Amount', 'rsvpmaker' ), esc_attr( strtoupper( $vars['currency'] ) ), __('minimum','rsvpmaker'), $rsvp_options['payment_minimum'], esc_attr( $vars['amount'] ), __('Note','rsvpmaker'), esc_attr( $idempotency_key ), __( 'Pay with Card' ), rsvpmaker_nonce('return') );
 	} else {
 		$output = sprintf( '<form action="%s" method="get"><input type="hidden" name="txid" value="%s"><button class="stripebutton">%s</button>%s</form>', $url, esc_attr( $idempotency_key ), __( 'Pay with Card' ), rsvpmaker_nonce('return') );
 	}
 
 	if(!empty($vars['paypal'])) {
@@ -246,10 +247,12 @@ function rsvpmaker_stripe_checkout() {
 
 	rsvpmaker_debug_log( 'rsvpmaker_stripe_checkout' );
 
 	global $post, $rsvp_options, $current_user;
 
+	$keys = get_rsvpmaker_stripe_keys();
+
 	if ( empty( $_GET['txid'] ) ) {
 
 		return;
 	}
 
@@ -273,19 +276,22 @@ function rsvpmaker_stripe_checkout() {
 
 		$vars['amount'] = sanitize_text_field( $_GET['amount'] );
 
 	}
 
+	if($vars['amount'] < $rsvp_options['payment_minimum']) {
+		do_action('rsvpmaker_possible_card_testing',$vars);
+		return '<p>Transactions of less than '.$rsvp_options['payment_minimum'].' not accepted.</p>';
+	}
+
 	if(!empty($_GET['stripenote']))
 		$vars['note'] = sanitize_text_field($_GET['stripenote']);
 
 	update_option( $idempotency_key, $vars );
 	
 	require_once 'stripe-php/init.php';
 
-	$keys = get_rsvpmaker_stripe_keys();
-
 	if ( ! empty( $vars['email'] ) ) {
 
 		$email = sanitize_email( $vars['email'] );
 
 		$name = ( empty( $vars['name'] ) ) ? '' : sanitize_text_field( $vars['name'] );
diff --git a/rsvpmaker.php b/rsvpmaker.php
index 93b172c..0385d25 100644
--- a/rsvpmaker.php
+++ b/rsvpmaker.php
@@ -5,15 +5,15 @@ Plugin URI: http://www.rsvpmaker.com
 Description: Schedule events, send invitations to your mailing list and track RSVPs. You get all your familiar WordPress editing tools with extra options for setting dates and RSVP options. Online payments with PayPal or Stripe can be added with a little extra configuration. Email invitations can be sent through MailChimp or to members of your website community who have user accounts. Recurring events can be tracked according to a schedule such as "First Monday" or "Every Friday" at a specified time, and the software will calculate future dates according to that schedule and let you track them together. <a href="options-general.php?page=rsvpmaker-admin.php">Options</a>
 Author: David F. Carr
 Author URI: http://www.carrcommunications.com
 Text Domain: rsvpmaker
 Domain Path: /translations
-Version: 9.2.5
+Version: 9.2.6
 */
 
 function get_rsvpversion() {
-	return '9.2.5';
+	return '9.2.6';
 }
 global $wp_version;
 global $default_tz;
 
 $default_tz = date_default_timezone_get();
@@ -109,10 +109,12 @@ function rsvp_options_defaults() {
 
 		'currency_decimal'                  => '.',
 
 		'currency_thousands'                => ',',
 
+		'payment_minimum'                   => '5.00',
+
 		'paypal_invoiceno'                  => 1,
 
 		'stripe'                            => 0,
 
 		'show_screen_recurring'             => 0,

Vulnerable code: Tested up to: 5.9.2
Stable tag: 9.2.5
		$sked = get_template_sked( $request['post_id'] );
if(($send_confirmation ||!is_numeric($send_confirmation)) && empty($confirm_on_payment) )//if it hasn't been set to 0, send it
		'rsvpmaker_relay_log',
		'rsvpmaker_relay_log'
function rsvpmaker_relay_log() {
	$sql = "SELECT * from $wpdb->posts JOIN $wpdb->postmeta ON $wpdb->posts.ID = $wpdb->postmeta.post_id WHERE post_type='rsvpemail' AND meta_key LIKE 'rsvp%' ORDER BY ID DESC, meta_key LIMIT 0,500";
	foreach($results as $row) {
		printf('<p>%s<br><strong>%s</strong> %s</p>',$row->post_title,$row->meta_key,$row->meta_value);
	}	
}


	global $current_user;
		$output = sprintf( '<form action="%s" method="get">%s (%s): <input type="text" name="amount" value="%s"><br />%s<br /><textarea name="stripenote" cols="80" rows="2"></textarea><br /><input type="hidden" name="txid" value="%s"><button class="stripebutton">%s</button>%s</form>', $url, __( 'Amount', 'rsvpmaker' ), esc_attr( strtoupper( $vars['currency'] ) ), esc_attr( $vars['amount'] ), __('Note','rsvpmaker'), esc_attr( $idempotency_key ), __( 'Pay with Card' ), rsvpmaker_nonce('return') );
	$keys = get_rsvpmaker_stripe_keys();

Version: 9.2.5
	return '9.2.5';
Fixed code: Tested up to: 5.9.3
Stable tag: 9.2.6
<h3><?php esc_html_e('Minimum Amount','rsvpmaker'); ?>:</h3>
<div><input type="text" name="payment_option[payment_minimum]" value="<?php if(isset($options["payment_minimum"])) {echo esc_attr($options["payment_minimum"]);} else echo '5.00';?>" size="5" /> <br /><em><?php _e('Prevents fraudulent uses such as $1 donations to test stolen cards','rsvpmaker'); ?></em>
</div>

relative_urls: false,
remove_script_host : false,
document_base_url : "'.site_url().'/",
		$sked = get_template_sked( intval($request['post_id']) );
if(($send_confirmation ||!is_numeric($send_confirmation)) && $rsvpdata['yesno'] && empty($confirm_on_payment) )//if it hasn't been set to 0, send it

		'rsvpmaker_relay_queue_monitor',
		'rsvpmaker_relay_queue_monitor'
function rsvpmaker_relay_queue_monitor () {
	do_action('rsvpmaker_relay_queue_monitor');
	$sql = "SELECT ID, post_title, wpt_postmeta.meta_key, wpt_postmeta.meta_value FROM `wpt_posts` JOIN wpt_postmeta on wpt_posts.ID = wpt_postmeta.post_id WHERE post_type='rsvpemail' AND (post_status='draft' OR post_status='publish') AND meta_key='rsvprelay_to' ORDER BY ID DESC";
	$was = 0;
	echo '<h1>In Queue</h2>';
	if(empty($results))
		echo '<p>none</p>';
	else
	foreach($results as $row)
	{
		if($row->ID != $was)
			printf('<h2>%s</h2>',$row->post_title);
		printf('<p>%s %s</p>',$row->meta_key, $row->meta_value);
		$was = $row->ID;
	}
	echo '<h1>Sent (200 Latest)</h2>';
	$sql = "SELECT ID, post_title, wpt_postmeta.meta_key, wpt_postmeta.meta_value FROM `wpt_posts` JOIN wpt_postmeta on wpt_posts.ID = wpt_postmeta.post_id WHERE post_type='rsvpemail' AND meta_key='rsvpmail_sent' ORDER BY ID DESC LIMIT 0, 200";
	$results = $wpdb->get_results($sql);
	$was = 0;
	foreach($results as $row)
	{
		if($row->ID != $was)
			printf('<h2>%s</h2>',$row->post_title);
		printf('<p>%s %s</p>',$row->meta_key, $row->meta_value);
		$was = $row->ID;
	}
}
				$rsvpdata['yesno'] = $yesno;
				
	global $current_user, $rsvp_options;
	global $rsvp_options;
		$output = sprintf( '<form action="%s" method="get">%s (%s, %s %s): <input type="text" name="amount" value="%s"><br />%s<br /><textarea name="stripenote" cols="80" rows="2"></textarea><br /><input type="hidden" name="txid" value="%s"><button class="stripebutton">%s</button>%s</form>', $url, __( 'Amount', 'rsvpmaker' ), esc_attr( strtoupper( $vars['currency'] ) ), __('minimum','rsvpmaker'), $rsvp_options['payment_minimum'], esc_attr( $vars['amount'] ), __('Note','rsvpmaker'), esc_attr( $idempotency_key ), __( 'Pay with Card' ), rsvpmaker_nonce('return') );
	$keys = get_rsvpmaker_stripe_keys();

	if($vars['amount'] < $rsvp_options['payment_minimum']) {
		do_action('rsvpmaker_possible_card_testing',$vars);
		return '<p>Transactions of less than '.$rsvp_options['payment_minimum'].' not accepted.</p>';
	}

Version: 9.2.6
	return '9.2.6';
		'payment_minimum'                   => '5.00',