Created
January 13, 2021 08:13
-
-
Save ohid/a495ba6a7f088ac61968625e7fcee6f1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Plugin Name: [Defender Pro] - Fix Current User IP | |
* Plugin URI: https://premium.wpmudev.org/ | |
* Description: This plugin is specially integrated with the Defender Pro plugin that sets the current user IP | |
* Task: SLS-1326 | |
* Version: 1.0.0 | |
* Author: Ohid @ WPMUDEV | |
* Author URI: https://premium.wpmudev.org/ | |
* License: GPLv2 or later | |
*/ | |
if ( ! defined( 'ABSPATH' ) ) { | |
exit; | |
} | |
if ( defined( 'WP_CLI' ) && WP_CLI ) { | |
return; | |
} | |
if ( ! class_exists( 'WPMUDEV_DEFENDER_CURRENT_USER_IP_ADDRESS' ) ) { | |
class WPMUDEV_DEFENDER_CURRENT_USER_IP_ADDRESS { | |
private $ip = null; | |
private $localhost_ip = '127.0.0.1'; | |
private static $_instance = null; | |
public static function get_instance() { | |
if( is_null( self::$_instance ) ){ | |
self::$_instance = new WPMUDEV_DEFENDER_CURRENT_USER_IP_ADDRESS(); | |
} | |
return self::$_instance; | |
} | |
private function __construct() { | |
add_filter( 'defender_user_ip', array( $this, 'defender_user_ip' ), 20 ); | |
} | |
/** | |
* Set the user IP using the defender_user_ip hook | |
* | |
* @return void | |
*/ | |
public function defender_user_ip() | |
{ | |
// Set the user IP | |
$this->set_user_ip(); | |
if ($this->ip !== null) { | |
return $this->ip; | |
} | |
return false; | |
} | |
/** | |
* Set the user IP | |
* | |
* @return void | |
*/ | |
public function set_user_ip() { | |
// Check if it's any cloudflare IP | |
$cf_ip = $this->check_cf_ip(); | |
// If we found client IP from Cloudflare then set it as current user IP | |
if ($cf_ip !== null) { | |
$this->ip = $cf_ip; | |
return; | |
} | |
$headers = [ | |
'HTTP_CLIENT_IP', | |
'HTTP_X_REAL_IP', | |
'HTTP_X_FORWARDED_FOR', | |
'HTTP_X_FORWARDED', | |
'HTTP_X_CLUSTER_CLIENT_IP', | |
'HTTP_FORWARDED_FOR', | |
'HTTP_FORWARDED', | |
'REMOTE_ADDR', | |
]; | |
foreach ( $headers as $key ) { | |
if ( array_key_exists( $key, $_SERVER ) === true ) { | |
// Check if the variable is not an array | |
if ( ! is_array( $_SERVER[ $key ] ) ) { | |
if ( $this->validate_ip($_SERVER[ $key ]) ) { | |
$this->ip = $_SERVER[ $key ]; | |
break; | |
} | |
} | |
$ip_array = explode( ',', $_SERVER[ $key ] ); | |
$the_ip = reset($ip_array); | |
if ( $the_ip !== false && $this->validate_ip($the_ip) ) { | |
$this->ip = $the_ip; | |
} | |
// Break the loop once we have found the IP | |
break; | |
} | |
} | |
} | |
/** | |
* Check cloudflare IP | |
* | |
* @return void | |
*/ | |
public function check_cf_ip() | |
{ | |
$ip_address = null; | |
if ( isset( $_SERVER["HTTP_CF_CONNECTING_IP"] ) ) { | |
//this looks like it come from cloudflare, so this should contain the actual IP, and REMOTE_ADDR is contain | |
//cloudflare IP | |
list( $cloudflare_ipv4_range, $cloudflare_ipv6_range ) = $this->cloudflare_ip_ranges(); | |
$ip_helper = new IP_Helper(); | |
if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) { | |
foreach ( $cloudflare_ipv4_range as $cf_ip ) { | |
if ( $ip_helper->ipv4_in_range( $ip, $cf_ip ) ) { | |
$ip_address = $_SERVER["HTTP_CF_CONNECTING_IP"]; | |
break; | |
} | |
} | |
} elseif ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) { | |
foreach ( $cloudflare_ipv6_range as $cf_ip ) { | |
if ( $ip_helper->ipv6_in_range( $ip, $cf_ip ) ) { | |
$ip_address = $_SERVER["HTTP_CF_CONNECTING_IP"]; | |
break; | |
} | |
} | |
} | |
} | |
return $ip_address; | |
} | |
/** | |
* We fetching the ip range here | |
* https://www.cloudflare.com/ips/ | |
* | |
* @return array | |
*/ | |
private function cloudflare_ip_ranges() { | |
return [ | |
[ | |
'173.245.48.0/20', | |
'103.21.244.0/22', | |
'103.22.200.0/22', | |
'103.31.4.0/22', | |
'141.101.64.0/18', | |
'108.162.192.0/18', | |
'190.93.240.0/20', | |
'188.114.96.0/20', | |
'197.234.240.0/22', | |
'198.41.128.0/17', | |
'162.158.0.0/15', | |
'104.16.0.0/12', | |
'172.64.0.0/13', | |
'131.0.72.0/22', | |
], | |
[ | |
'2400:cb00::/32', | |
'2606:4700::/32', | |
'2803:f800::/32', | |
'2405:b500::/32', | |
'2405:8100::/32', | |
'2a06:98c0::/29', | |
'2c0f:f248::/32' | |
] | |
]; | |
} | |
/** | |
* Validate the IP address | |
* | |
* @param string $ip | |
* | |
* @return boolean | |
*/ | |
public function validate_ip($ip) | |
{ | |
// Validate the localhost IP address | |
if ($this->localhost_ip === $ip) { | |
return true; | |
} | |
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) { | |
return false; | |
} | |
return true; | |
} | |
} | |
if ( ! function_exists( 'WPMUDEV_DEFENDER_CURRENT_USER_IP_ADDRESS' ) ) { | |
function WPMUDEV_DEFENDER_CURRENT_USER_IP_ADDRESS(){ | |
return WPMUDEV_DEFENDER_CURRENT_USER_IP_ADDRESS::get_instance(); | |
}; | |
add_action( 'plugins_loaded', 'WPMUDEV_DEFENDER_CURRENT_USER_IP_ADDRESS', 10 ); | |
} | |
} | |
class IP_Helper { | |
// decbin32 | |
// In order to simplify working with IP addresses (in binary) and their | |
// netmasks, it is easier to ensure that the binary strings are padded | |
// with zeros out to 32 characters - IP addresses are 32 bit numbers | |
function decbin32( $dec ) { | |
return str_pad( decbin( $dec ), 32, '0', STR_PAD_LEFT ); | |
} | |
// ipv4_in_range | |
// This function takes 2 arguments, an IP address and a "range" in several | |
// different formats. | |
// Network ranges can be specified as: | |
// 1. Wildcard format: 1.2.3.* | |
// 2. CIDR format: 1.2.3/24 OR 1.2.3.4/255.255.255.0 | |
// 3. Start-End IP format: 1.2.3.0-1.2.3.255 | |
// The function will return true if the supplied IP is within the range. | |
// Note little validation is done on the range inputs - it expects you to | |
// use one of the above 3 formats. | |
function ipv4_in_range( $ip, $range ) { | |
if ( strpos( $range, '/' ) !== false ) { | |
// $range is in IP/NETMASK format | |
list( $range, $netmask ) = explode( '/', $range, 2 ); | |
if ( strpos( $netmask, '.' ) !== false ) { | |
// $netmask is a 255.255.0.0 format | |
$netmask = str_replace( '*', '0', $netmask ); | |
$netmask_dec = ip2long( $netmask ); | |
return ( ( ip2long( $ip ) & $netmask_dec ) == ( ip2long( $range ) & $netmask_dec ) ); | |
} else { | |
// $netmask is a CIDR size block | |
// fix the range argument | |
$x = explode( '.', $range ); | |
while ( count( $x ) < 4 ) { | |
$x[] = '0'; | |
} | |
list( $a, $b, $c, $d ) = $x; | |
$range = sprintf( "%u.%u.%u.%u", empty( $a ) ? '0' : $a, empty( $b ) ? '0' : $b, empty( $c ) ? '0' : $c, empty( $d ) ? '0' : $d ); | |
$range_dec = ip2long( $range ); | |
$ip_dec = ip2long( $ip ); | |
# Strategy 1 - Create the netmask with 'netmask' 1s and then fill it to 32 with 0s | |
#$netmask_dec = bindec(str_pad('', $netmask, '1') . str_pad('', 32-$netmask, '0')); | |
# Strategy 2 - Use math to create it | |
$wildcard_dec = pow( 2, ( 32 - $netmask ) ) - 1; | |
$netmask_dec = ~$wildcard_dec; | |
return ( ( $ip_dec & $netmask_dec ) == ( $range_dec & $netmask_dec ) ); | |
} | |
} else { | |
// range might be 255.255.*.* or 1.2.3.0-1.2.3.255 | |
if ( strpos( $range, '*' ) !== false ) { // a.b.*.* format | |
// Just convert to A-B format by setting * to 0 for A and 255 for B | |
$lower = str_replace( '*', '0', $range ); | |
$upper = str_replace( '*', '255', $range ); | |
$range = "$lower-$upper"; | |
} | |
if ( strpos( $range, '-' ) !== false ) { // A-B format | |
list( $lower, $upper ) = explode( '-', $range, 2 ); | |
$lower_dec = (float) sprintf( "%u", ip2long( $lower ) ); | |
$upper_dec = (float) sprintf( "%u", ip2long( $upper ) ); | |
$ip_dec = (float) sprintf( "%u", ip2long( $ip ) ); | |
return ( ( $ip_dec >= $lower_dec ) && ( $ip_dec <= $upper_dec ) ); | |
} | |
return false; | |
} | |
} | |
function ip2long6( $ip ) { | |
if ( substr_count( $ip, '::' ) ) { | |
$ip = str_replace( '::', str_repeat( ':0000', 8 - substr_count( $ip, ':' ) ) . ':', $ip ); | |
} | |
$ip = explode( ':', $ip ); | |
$r_ip = ''; | |
foreach ( $ip as $v ) { | |
$r_ip .= str_pad( base_convert( $v, 16, 2 ), 16, 0, STR_PAD_LEFT ); | |
} | |
return base_convert( $r_ip, 2, 10 ); | |
} | |
// Get the ipv6 full format and return it as a decimal value. | |
function get_ipv6_full( $ip ) { | |
$pieces = explode( "/", $ip, 2 ); | |
$left_piece = $pieces[0]; | |
$right_piece = $pieces[1]; | |
// Extract out the main IP pieces | |
$ip_pieces = explode( "::", $left_piece, 2 ); | |
$main_ip_piece = $ip_pieces[0]; | |
$last_ip_piece = $ip_pieces[1]; | |
// Pad out the shorthand entries. | |
$main_ip_pieces = explode( ":", $main_ip_piece ); | |
foreach ( $main_ip_pieces as $key => $val ) { | |
$main_ip_pieces[ $key ] = str_pad( $main_ip_pieces[ $key ], 4, "0", STR_PAD_LEFT ); | |
} | |
// Check to see if the last IP block (part after ::) is set | |
$last_piece = ""; | |
$size = count( $main_ip_pieces ); | |
if ( trim( $last_ip_piece ) != "" ) { | |
$last_piece = str_pad( $last_ip_piece, 4, "0", STR_PAD_LEFT ); | |
// Build the full form of the IPV6 address considering the last IP block set | |
for ( $i = $size; $i < 7; $i ++ ) { | |
$main_ip_pieces[ $i ] = "0000"; | |
} | |
$main_ip_pieces[7] = $last_piece; | |
} else { | |
// Build the full form of the IPV6 address | |
for ( $i = $size; $i < 8; $i ++ ) { | |
$main_ip_pieces[ $i ] = "0000"; | |
} | |
} | |
// Rebuild the final long form IPV6 address | |
$final_ip = implode( ":", $main_ip_pieces ); | |
return $this->ip2long6( $final_ip ); | |
} | |
// Determine whether the IPV6 address is within range. | |
// $ip is the IPV6 address in decimal format to check if its within the IP range created by the cloudflare IPV6 address, $range_ip. | |
// $ip and $range_ip are converted to full IPV6 format. | |
// Returns true if the IPV6 address, $ip, is within the range from $range_ip. False otherwise. | |
function ipv6_in_range( $ip, $range_ip ) { | |
$pieces = explode( "/", $range_ip, 2 ); | |
$left_piece = $pieces[0]; | |
$right_piece = $pieces[1]; | |
// Extract out the main IP pieces | |
$ip_pieces = explode( "::", $left_piece, 2 ); | |
$main_ip_piece = $ip_pieces[0]; | |
$last_ip_piece = $ip_pieces[1]; | |
// Pad out the shorthand entries. | |
$main_ip_pieces = explode( ":", $main_ip_piece ); | |
foreach ( $main_ip_pieces as $key => $val ) { | |
$main_ip_pieces[ $key ] = str_pad( $main_ip_pieces[ $key ], 4, "0", STR_PAD_LEFT ); | |
} | |
// Create the first and last pieces that will denote the IPV6 range. | |
$first = $main_ip_pieces; | |
$last = $main_ip_pieces; | |
// Check to see if the last IP block (part after ::) is set | |
$last_piece = ""; | |
$size = count( $main_ip_pieces ); | |
if ( trim( $last_ip_piece ) != "" ) { | |
$last_piece = str_pad( $last_ip_piece, 4, "0", STR_PAD_LEFT ); | |
// Build the full form of the IPV6 address considering the last IP block set | |
for ( $i = $size; $i < 7; $i ++ ) { | |
$first[ $i ] = "0000"; | |
$last[ $i ] = "ffff"; | |
} | |
$main_ip_pieces[7] = $last_piece; | |
} else { | |
// Build the full form of the IPV6 address | |
for ( $i = $size; $i < 8; $i ++ ) { | |
$first[ $i ] = "0000"; | |
$last[ $i ] = "ffff"; | |
} | |
} | |
// Rebuild the final long form IPV6 address | |
$first = $this->ip2long6( implode( ":", $first ) ); | |
$last = $this->ip2long6( implode( ":", $last ) ); | |
$in_range = ( $ip >= $first && $ip <= $last ); | |
return $in_range; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment