.htaccess
7 years ago
bootstrap.php
7 years ago
wfWAFGeoIP2.php
7 years ago
wfWAFIPBlocksController.php
7 years ago
wfWAFUserIPRange.php
7 years ago
wfWAFUserIPRange.php
261 lines
| 1 | <?php |
| 2 | if (!defined('WFWAF_RUN_COMPLETE')) { |
| 3 | |
| 4 | /** |
| 5 | * |
| 6 | */ |
| 7 | class wfWAFUserIPRange { |
| 8 | |
| 9 | /** |
| 10 | * @var string|null |
| 11 | */ |
| 12 | private $ip_string; |
| 13 | |
| 14 | /** |
| 15 | * @param string|null $ip_string |
| 16 | */ |
| 17 | public function __construct($ip_string = null) { |
| 18 | $this->setIPString($ip_string); |
| 19 | } |
| 20 | |
| 21 | public function isIPInRange($ip) { |
| 22 | $ip_string = $this->getIPString(); |
| 23 | |
| 24 | if (strpos($ip_string, '/') !== false) { //CIDR range -- 127.0.0.1/24 |
| 25 | return wfWAFUtils::subnetContainsIP($ip_string, $ip); |
| 26 | } |
| 27 | else if (strpos($ip_string, '[') !== false) //Bracketed range -- 127.0.0.[1-100] |
| 28 | { |
| 29 | // IPv4 range |
| 30 | if (strpos($ip_string, '.') !== false && strpos($ip, '.') !== false) { |
| 31 | // IPv4-mapped-IPv6 |
| 32 | if (preg_match('/:ffff:([^:]+)$/i', $ip_string, $matches)) { |
| 33 | $ip_string = $matches[1]; |
| 34 | } |
| 35 | if (preg_match('/:ffff:([^:]+)$/i', $ip, $matches)) { |
| 36 | $ip = $matches[1]; |
| 37 | } |
| 38 | |
| 39 | // Range check |
| 40 | if (preg_match('/\[\d+\-\d+\]/', $ip_string)) { |
| 41 | $IPparts = explode('.', $ip); |
| 42 | $whiteParts = explode('.', $ip_string); |
| 43 | $mismatch = false; |
| 44 | if (count($whiteParts) != 4 || count($IPparts) != 4) { |
| 45 | return false; |
| 46 | } |
| 47 | |
| 48 | for ($i = 0; $i <= 3; $i++) { |
| 49 | if (preg_match('/^\[(\d+)\-(\d+)\]$/', $whiteParts[$i], $m)) { |
| 50 | if ($IPparts[$i] < $m[1] || $IPparts[$i] > $m[2]) { |
| 51 | $mismatch = true; |
| 52 | } |
| 53 | } |
| 54 | else if ($whiteParts[$i] != $IPparts[$i]) { |
| 55 | $mismatch = true; |
| 56 | } |
| 57 | } |
| 58 | if ($mismatch === false) { |
| 59 | return true; // Is whitelisted because we did not get a mismatch |
| 60 | } |
| 61 | } |
| 62 | else if ($ip_string == $ip) { |
| 63 | return true; |
| 64 | } |
| 65 | |
| 66 | // IPv6 range |
| 67 | } |
| 68 | else if (strpos($ip_string, ':') !== false && strpos($ip, ':') !== false) { |
| 69 | $ip = strtolower(wfWAFUtils::expandIPv6Address($ip)); |
| 70 | $ip_string = strtolower(self::expandIPv6Range($ip_string)); |
| 71 | if (preg_match('/\[[a-f0-9]+\-[a-f0-9]+\]/i', $ip_string)) { |
| 72 | $IPparts = explode(':', $ip); |
| 73 | $whiteParts = explode(':', $ip_string); |
| 74 | $mismatch = false; |
| 75 | if (count($whiteParts) != 8 || count($IPparts) != 8) { |
| 76 | return false; |
| 77 | } |
| 78 | |
| 79 | for ($i = 0; $i <= 7; $i++) { |
| 80 | if (preg_match('/^\[([a-f0-9]+)\-([a-f0-9]+)\]$/i', $whiteParts[$i], $m)) { |
| 81 | $ip_group = hexdec($IPparts[$i]); |
| 82 | $range_group_from = hexdec($m[1]); |
| 83 | $range_group_to = hexdec($m[2]); |
| 84 | if ($ip_group < $range_group_from || $ip_group > $range_group_to) { |
| 85 | $mismatch = true; |
| 86 | break; |
| 87 | } |
| 88 | } |
| 89 | else if ($whiteParts[$i] != $IPparts[$i]) { |
| 90 | $mismatch = true; |
| 91 | break; |
| 92 | } |
| 93 | } |
| 94 | if ($mismatch === false) { |
| 95 | return true; // Is whitelisted because we did not get a mismatch |
| 96 | } |
| 97 | } |
| 98 | else if ($ip_string == $ip) { |
| 99 | return true; |
| 100 | } |
| 101 | } |
| 102 | } |
| 103 | else if (strpos($ip_string, '-') !== false) { //Linear range -- 127.0.0.1 - 127.0.1.100 |
| 104 | list($ip1, $ip2) = explode('-', $ip_string); |
| 105 | $ip1N = wfWAFUtils::inet_pton($ip1); |
| 106 | $ip2N = wfWAFUtils::inet_pton($ip2); |
| 107 | $ipN = wfWAFUtils::inet_pton($ip); |
| 108 | return (strcmp($ip1N, $ipN) <= 0 && strcmp($ip2N, $ipN) >= 0); |
| 109 | } |
| 110 | else { //Treat as a literal IP |
| 111 | $ip1 = @wfWAFUtils::inet_pton($ip_string); |
| 112 | $ip2 = @wfWAFUtils::inet_pton($ip); |
| 113 | if ($ip1 !== false && $ip1 == $ip2) { |
| 114 | return true; |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | return false; |
| 119 | } |
| 120 | |
| 121 | /** |
| 122 | * Expand a compressed printable range representation of an IPv6 address. |
| 123 | * |
| 124 | * @todo Hook up exceptions for better error handling. |
| 125 | * @todo Allow IPv4 mapped IPv6 addresses (::ffff:192.168.1.1). |
| 126 | * @param string $ip_range |
| 127 | * @return string |
| 128 | */ |
| 129 | public static function expandIPv6Range($ip_range) { |
| 130 | $colon_count = substr_count($ip_range, ':'); |
| 131 | $dbl_colon_count = substr_count($ip_range, '::'); |
| 132 | if ($dbl_colon_count > 1) { |
| 133 | return false; |
| 134 | } |
| 135 | $dbl_colon_pos = strpos($ip_range, '::'); |
| 136 | if ($dbl_colon_pos !== false) { |
| 137 | $ip_range = str_replace('::', str_repeat(':0000', |
| 138 | (($dbl_colon_pos === 0 || $dbl_colon_pos === strlen($ip_range) - 2) ? 9 : 8) - $colon_count) . ':', $ip_range); |
| 139 | $ip_range = trim($ip_range, ':'); |
| 140 | } |
| 141 | $colon_count = substr_count($ip_range, ':'); |
| 142 | if ($colon_count != 7) { |
| 143 | return false; |
| 144 | } |
| 145 | |
| 146 | $groups = explode(':', $ip_range); |
| 147 | $expanded = ''; |
| 148 | foreach ($groups as $group) { |
| 149 | if (preg_match('/\[([a-f0-9]{1,4})\-([a-f0-9]{1,4})\]/i', $group, $matches)) { |
| 150 | $expanded .= sprintf('[%s-%s]', str_pad(strtolower($matches[1]), 4, '0', STR_PAD_LEFT), str_pad(strtolower($matches[2]), 4, '0', STR_PAD_LEFT)) . ':'; |
| 151 | } else if (preg_match('/[a-f0-9]{1,4}/i', $group)) { |
| 152 | $expanded .= str_pad(strtolower($group), 4, '0', STR_PAD_LEFT) . ':'; |
| 153 | } else { |
| 154 | return false; |
| 155 | } |
| 156 | } |
| 157 | return trim($expanded, ':'); |
| 158 | } |
| 159 | |
| 160 | /** |
| 161 | * @return bool |
| 162 | */ |
| 163 | public function isValidRange() { |
| 164 | return $this->isValidCIDRRange() || $this->isValidBracketedRange() || $this->isValidLinearRange() || filter_var($this->getIPString(), FILTER_VALIDATE_IP) !== false; |
| 165 | } |
| 166 | |
| 167 | public function isValidCIDRRange() { //e.g., 192.0.2.1/24 |
| 168 | $ip_string = $this->getIPString(); |
| 169 | if (preg_match('/[^0-9a-f:\/\.]/i', $ip_string)) { return false; } |
| 170 | $components = explode('/', $ip_string); |
| 171 | if (count($components) != 2) { return false; } |
| 172 | |
| 173 | list($ip, $prefix) = $components; |
| 174 | if (filter_var($ip, FILTER_VALIDATE_IP) === false) { return false; } |
| 175 | |
| 176 | if (!preg_match('/^\d+$/', $prefix)) { return false; } |
| 177 | |
| 178 | if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { |
| 179 | if ($prefix < 0 || $prefix > 32) { return false; } |
| 180 | } |
| 181 | else { |
| 182 | if ($prefix < 1 || $prefix > 128) { return false; } |
| 183 | } |
| 184 | |
| 185 | return true; |
| 186 | } |
| 187 | |
| 188 | public function isValidBracketedRange() { //e.g., 192.0.2.[1-10] |
| 189 | $ip_string = $this->getIPString(); |
| 190 | if (preg_match('/[^0-9a-f:\.\[\]\-]/i', $ip_string)) { return false; } |
| 191 | if (strpos($ip_string, '.') !== false) { //IPv4 |
| 192 | if (preg_match_all('/(\d+)/', $ip_string, $matches) > 0) { |
| 193 | foreach ($matches[1] as $match) { |
| 194 | $group = (int) $match; |
| 195 | if ($group > 255 || $group < 0) { |
| 196 | return false; |
| 197 | } |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | $group_regex = '([0-9]{1,3}|\[[0-9]{1,3}\-[0-9]{1,3}\])'; |
| 202 | return preg_match('/^' . str_repeat("{$group_regex}\\.", 3) . $group_regex . '$/i', $ip_string) > 0; |
| 203 | } |
| 204 | |
| 205 | //IPv6 |
| 206 | if (strpos($ip_string, '::') !== false) { |
| 207 | $ip_string = self::expandIPv6Range($ip_string); |
| 208 | } |
| 209 | if (!$ip_string) { |
| 210 | return false; |
| 211 | } |
| 212 | $group_regex = '([a-f0-9]{1,4}|\[[a-f0-9]{1,4}\-[a-f0-9]{1,4}\])'; |
| 213 | return preg_match('/^' . str_repeat("$group_regex:", 7) . $group_regex . '$/i', $ip_string) > 0; |
| 214 | } |
| 215 | |
| 216 | public function isValidLinearRange() { //e.g., 192.0.2.1-192.0.2.100 |
| 217 | $ip_string = $this->getIPString(); |
| 218 | if (preg_match('/[^0-9a-f:\.\-]/i', $ip_string)) { return false; } |
| 219 | list($ip1, $ip2) = explode("-", $ip_string); |
| 220 | $ip1N = @wfWAFUtils::inet_pton($ip1); |
| 221 | $ip2N = @wfWAFUtils::inet_pton($ip2); |
| 222 | |
| 223 | if ($ip1N === false || filter_var($ip1, FILTER_VALIDATE_IP) === false || $ip2N === false || filter_var($ip2, FILTER_VALIDATE_IP) === false) { |
| 224 | return false; |
| 225 | } |
| 226 | |
| 227 | return strcmp($ip1N, $ip2N) <= 0; |
| 228 | } |
| 229 | |
| 230 | protected function _sanitizeIPRange($ip_string) { |
| 231 | $ip_string = preg_replace('/\s/', '', $ip_string); //Strip whitespace |
| 232 | $ip_string = preg_replace('/[\\x{2013}-\\x{2015}]/u', '-', $ip_string); //Non-hyphen dashes to hyphen |
| 233 | $ip_string = strtolower($ip_string); |
| 234 | |
| 235 | if (preg_match('/^\d+-\d+$/', $ip_string)) { //v5 32 bit int style format |
| 236 | list($start, $end) = explode('-', $ip_string); |
| 237 | $start = long2ip($start); |
| 238 | $end = long2ip($end); |
| 239 | $ip_string = "{$start}-{$end}"; |
| 240 | } |
| 241 | |
| 242 | return $ip_string; |
| 243 | } |
| 244 | |
| 245 | |
| 246 | /** |
| 247 | * @return string|null |
| 248 | */ |
| 249 | public function getIPString() { |
| 250 | return $this->ip_string; |
| 251 | } |
| 252 | |
| 253 | /** |
| 254 | * @param string|null $ip_string |
| 255 | */ |
| 256 | public function setIPString($ip_string) { |
| 257 | $this->ip_string = $this->_sanitizeIPRange($ip_string); |
| 258 | } |
| 259 | } |
| 260 | } |
| 261 |