File: /var/www/html/wp-content/plugins/memberpress/app/controllers/MeprAntiCardTestingCtrl.php
<?php
defined('ABSPATH') || exit;
class MeprAntiCardTestingCtrl extends MeprBaseCtrl
{
/**
* Loads the hooks.
*
* @return void
*/
public function load_hooks()
{
add_action('mepr_display_general_options', [$this, 'display_options']);
add_action('mepr_stripe_payment_failed', [$this, 'record_payment_failure']);
add_filter('mepr_validate_signup', [$this, 'maybe_block_payment']);
add_filter('mepr_validate_payment_form', [$this, 'maybe_block_payment']);
add_action('mepr_process_signup_form_ajax', [$this, 'maybe_block_payment_ajax']);
add_action('mepr_process_payment_form_ajax', [$this, 'maybe_block_payment_ajax']);
add_action('wp_ajax_mepr_anti_card_testing_get_ip', [$this, 'get_detected_ip_ajax']);
}
/**
* Displays the options.
*
* @return void
*/
public function display_options()
{
$mepr_options = MeprOptions::fetch();
?>
<h3><?php esc_html_e('Card Testing Protection', 'memberpress'); ?></h3>
<table class="form-table">
<tbody>
<tr valign="top">
<th scope="row">
<label for="<?php echo esc_attr($mepr_options->anti_card_testing_enabled_str); ?>"><?php esc_html_e('Enable Card Testing Protection', 'memberpress'); ?></label>
<?php
MeprAppHelper::info_tooltip(
$mepr_options->anti_card_testing_enabled_str,
__('Enable Card Testing Protection', 'memberpress'),
sprintf(
// Translators: %1$s: br tag.
__('Card testing is a type of fraudulent activity where someone tries to determine if stolen card information can be used to make purchases, by repeatedly attempting a purchase with different card numbers until one succeeds.%1$s%1$sBy enabling this protection, MemberPress will permanently block any further payment attempts by any user that has had 5 failed payments in a 2 hour window.', 'memberpress'),
'<br>'
)
);
?>
</th>
<td>
<input type="checkbox" name="<?php echo esc_attr($mepr_options->anti_card_testing_enabled_str); ?>" id="<?php echo esc_attr($mepr_options->anti_card_testing_enabled_str); ?>" <?php checked($mepr_options->anti_card_testing_enabled); ?> class="mepr-toggle-checkbox" data-box="mepr-anti-card-testing-box" />
</td>
</tr>
</tbody>
</table>
<div class="mepr-sub-box-white mepr-hidden mepr-anti-card-testing-box">
<div class="mepr-arrow mepr-white mepr-up mepr-sub-box-arrow"> </div>
<table class="form-table">
<tbody>
<tr valign="top">
<th scope="row">
<?php esc_html_e('How To Get Visitor IP?', 'memberpress'); ?>
<?php
MeprAppHelper::info_tooltip(
$mepr_options->anti_card_testing_ip_method_str,
__('How To Get Visitor IP?', 'memberpress'),
sprintf(
// Translators: %1$s: br tag, %2$s: open link tag, %3$s: close link tag.
__('Which method should MemberPress use to retrieve the visitor\'s IP address?%1$s%1$sIt\'s important to use a method that is compatible with your site. The REMOTE_ADDR method is the most secure but may not be correct if your site is using a front-end proxy.%1$s%1$sCompare the displayed detected IP address with what is displayed on %2$sthis site%3$s to find the correct method for your site.', 'memberpress'),
'<br>',
'<a href="https://whatismyipaddress.com/" target="_blank">',
'</a>'
)
);
?>
</th>
<td>
<p>
<input type="radio" name="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>" id="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>_default" value="" <?php checked($mepr_options->anti_card_testing_ip_method, ''); ?>>
<label for="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>_default">
<?php
printf(
// Translators: %1$s: open strong tag, %2$s: close strong tag.
esc_html__('%1$sDefault%2$s - Compatible with most sites, but not as secure as the methods below.', 'memberpress'),
'<strong>',
'</strong>'
);
?>
</label>
</p>
<p>
<input type="radio" name="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>" id="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>_remote_addr" value="REMOTE_ADDR" <?php checked($mepr_options->anti_card_testing_ip_method, 'REMOTE_ADDR'); ?>>
<label for="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>_remote_addr">
<?php
printf(
// Translators: %1$s: open strong tag, %2$s: close strong tag.
esc_html__('%1$sUse PHP\'s built-in REMOTE_ADDR%2$s - The most secure method if this is compatible with your site.', 'memberpress'),
'<strong>',
'</strong>'
);
?>
</label>
</p>
<p>
<input type="radio" name="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>" id="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>_x_forwarded_for" value="HTTP_X_FORWARDED_FOR" <?php checked($mepr_options->anti_card_testing_ip_method, 'HTTP_X_FORWARDED_FOR'); ?>>
<label for="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>_x_forwarded_for">
<?php
printf(
// Translators: %1$s: open strong tag, %2$s: close strong tag.
esc_html__('%1$sUse the X-Forwarded-For HTTP header%2$s - Only use this if you\'re using a front-end proxy or spoofing may result.', 'memberpress'),
'<strong>',
'</strong>'
);
?>
</label>
</p>
<p>
<input type="radio" name="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>" id="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>_x_real_ip" value="HTTP_X_REAL_IP" <?php checked($mepr_options->anti_card_testing_ip_method, 'HTTP_X_REAL_IP'); ?>>
<label for="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>_x_real_ip">
<?php
printf(
// Translators: %1$s: open strong tag, %2$s: close strong tag.
esc_html__('%1$sUse the X-Real-IP HTTP header%2$s - Only use this if you\'re using a front-end proxy or spoofing may result.', 'memberpress'),
'<strong>',
'</strong>'
);
?>
</label>
</p>
<p>
<input type="radio" name="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>" id="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>_cf_connecting_ip" value="HTTP_CF_CONNECTING_IP" <?php checked($mepr_options->anti_card_testing_ip_method, 'HTTP_CF_CONNECTING_IP'); ?>>
<label for="<?php echo esc_attr($mepr_options->anti_card_testing_ip_method_str); ?>_cf_connecting_ip">
<?php
printf(
// Translators: %1$s: open strong tag, %2$s: close strong tag.
esc_html__('%1$sUse the CF-Connecting-IP HTTP header%2$s - Only use this if you\'re using Cloudflare.', 'memberpress'),
'<strong>',
'</strong>'
);
?>
</label>
</p>
<p class="mepr-detected-ip-address-p"><?php esc_html_e('Detected IP address using the selected method:', 'memberpress'); ?><code id="mepr-detected-ip-address"><?php echo esc_html(self::get_ip()); ?></code></p>
</td>
</tr>
<tr valign="top">
<th scope="row">
<label for="<?php echo esc_attr($mepr_options->anti_card_testing_blocked_str); ?>"><?php esc_html_e('Blocked IP Addresses', 'memberpress'); ?></label>
<?php
MeprAppHelper::info_tooltip(
$mepr_options->anti_card_testing_blocked_str,
__('Blocked IP Addresses', 'memberpress'),
sprintf(
// Translators: %1$s: br tag.
__('The IP addresses listed here are currently banned from making purchases.%1$s%1$sYou can add a new IP address (one per line) to block it, or remove an IP address to unblock it.', 'memberpress'),
'<br>'
)
);
?>
</th>
<td>
<textarea name="<?php echo esc_attr($mepr_options->anti_card_testing_blocked_str); ?>" id="<?php echo esc_attr($mepr_options->anti_card_testing_blocked_str); ?>"><?php echo esc_textarea(join("\n", $mepr_options->anti_card_testing_blocked)); ?></textarea>
</td>
</tr>
</tbody>
</table>
</div>
<?php
}
/**
* Get the visitor's IP address
*
* @param string|null $method The IP retrieval method to use or null to use the saved method.
* @return string
*/
public static function get_ip($method = null)
{
$mepr_options = MeprOptions::fetch();
$connection_ip = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '127.0.0.1';
$ips = [];
if (is_null($method)) {
$method = $mepr_options->anti_card_testing_ip_method;
}
if (empty($method)) {
$headers = [
'HTTP_X_FORWARDED_FOR',
'HTTP_X_REAL_IP',
'HTTP_CF_CONNECTING_IP',
];
foreach ($headers as $header) {
if (isset($_SERVER[$header])) {
$ips[] = sanitize_text_field(wp_unslash($_SERVER[$header]));
}
}
} elseif (isset($_SERVER[$method])) {
$ips[] = sanitize_text_field(wp_unslash($_SERVER[$method]));
}
$ips[] = $connection_ip;
$ip = self::get_client_ip_from_ips($ips);
if (is_null($ip)) {
$ip = $connection_ip;
}
return MeprHooks::apply_filters('mepr_anti_card_testing_ip', $ip);
}
/**
* Get the first valid public IP from the given array
*
* @param array $ips The array of IP addresses to check.
* @return string|null
*/
private static function get_client_ip_from_ips($ips)
{
foreach ($ips as $ip) {
$skip_to_next = false;
foreach ([',', ' ', "\t"] as $char) {
if (strpos($ip, $char) !== false) {
$parts = explode($char, $ip);
$parts = array_reverse($parts);
foreach ($parts as $part) {
$part = trim($part);
if (self::is_valid_ip_address($part) && !self::is_private_ip_address($part)) {
return $part;
}
}
$skip_to_next = true;
break;
}
}
if ($skip_to_next) {
continue; // This one had a delimiter and we didn't find anything.
}
if (self::is_valid_ip_address($ip) && !self::is_private_ip_address($ip)) {
return $ip;
}
}
return null;
}
/**
* Is the given IP address valid?
*
* @param string $ip The IP address to check.
* @return boolean
*/
private static function is_valid_ip_address($ip)
{
return filter_var($ip, FILTER_VALIDATE_IP) !== false;
}
/**
* Is the given IP address private?
*
* @param string $ip The IP address to check.
* @return boolean
*/
private static function is_private_ip_address($ip)
{
return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6) !== false
&& filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false;
}
/**
* Records a payment failure.
*
* @param string $ip The IP address of the user.
* @return void
*/
public function record_payment_failure($ip)
{
$mepr_options = MeprOptions::fetch();
if (!$mepr_options->anti_card_testing_enabled) {
return;
}
if (self::is_valid_ip_address($ip) && !self::is_private_ip_address($ip)) {
$failed = (int) get_transient("mepr_failed_payments_$ip");
set_transient("mepr_failed_payments_$ip", $failed + 1, MeprHooks::apply_filters('mepr_card_testing_timeframe', 2 * HOUR_IN_SECONDS));
}
}
/**
* Block signup or payment if the IP is blocked.
*
* @param array $errors The signup validation errors.
* @return array
*/
public function maybe_block_payment($errors)
{
$this->maybe_block_ip();
if ($this->is_ip_blocked()) {
$errors[] = __('We are not able to complete your purchase at this time. Please contact us for more information.', 'memberpress');
}
return $errors;
}
/**
* Block signup or payment via Ajax if the IP is blocked.
*
* @return void
*/
public function maybe_block_payment_ajax()
{
$this->maybe_block_ip();
if ($this->is_ip_blocked()) {
wp_send_json_error(
__('We are not able to complete your purchase at this time. Please contact us for more information.', 'memberpress')
);
}
}
/**
* Is the current IP address blocked?
*
* @return boolean
*/
public function is_ip_blocked()
{
$mepr_options = MeprOptions::fetch();
if (!$mepr_options->anti_card_testing_enabled) {
return false;
}
$ip = self::get_ip();
if ($ip && !self::is_private_ip_address($ip)) {
$blocked_ips = $mepr_options->anti_card_testing_blocked;
if (!is_array($blocked_ips)) {
$blocked_ips = [];
}
return in_array($ip, $blocked_ips, true);
}
return false;
}
/**
* Block the current IP address if there have been too many failed payment attempts
*/
protected function maybe_block_ip()
{
$mepr_options = MeprOptions::fetch();
if (!$mepr_options->anti_card_testing_enabled) {
return;
}
$ip = self::get_ip();
if ($ip && !self::is_private_ip_address($ip)) {
$failed = (int) get_transient("mepr_failed_payments_$ip");
$blocked_ips = $mepr_options->anti_card_testing_blocked;
if (!is_array($blocked_ips)) {
$blocked_ips = [];
}
// If there have been 5 or more failed payments, add to permanently banned IPs.
if ($failed >= MeprHooks::apply_filters('mepr_card_testing_failure_limit', 5) && !in_array($ip, $blocked_ips, true)) {
$blocked_ips[] = $ip;
$mepr_options->anti_card_testing_blocked = $blocked_ips;
$mepr_options->store(false);
}
}
}
/**
* Get the detected IP address.
*
* @return void
*/
public function get_detected_ip_ajax()
{
if (!MeprUtils::is_logged_in_and_an_admin() || !isset($_GET['method']) || !is_string($_GET['method'])) {
wp_send_json_error();
}
$valid_methods = ['', 'REMOTE_ADDR', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CF_CONNECTING_IP'];
$method = sanitize_text_field(wp_unslash($_GET['method']));
$method = in_array($method, $valid_methods, true) ? $method : '';
wp_send_json_success(self::get_ip($method));
}
}