PageRenderTime 53ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/wordfence/waf/wfWAFUserIPRange.php

https://gitlab.com/edgarze188/sunrise
PHP | 239 lines | 165 code | 23 blank | 51 comment | 57 complexity | ef6639964533ca1aa8b172eacf317f3c MD5 | raw file
  1. <?php
  2. /**
  3. *
  4. */
  5. class wfWAFUserIPRange {
  6. /**
  7. * @var string|null
  8. */
  9. private $ip_string;
  10. /**
  11. * @param string|null $ip_string
  12. */
  13. public function __construct($ip_string = null) {
  14. $this->setIPString($ip_string);
  15. }
  16. /**
  17. * Check if the supplied IP address is within the user supplied range.
  18. *
  19. * @param string $ip
  20. * @return bool
  21. */
  22. public function isIPInRange($ip) {
  23. $ip_string = $this->getIPString();
  24. // IPv4 range
  25. if (strpos($ip_string, '.') !== false && strpos($ip, '.') !== false) {
  26. // IPv4-mapped-IPv6
  27. if (preg_match('/:ffff:([^:]+)$/i', $ip_string, $matches)) {
  28. $ip_string = $matches[1];
  29. }
  30. if (preg_match('/:ffff:([^:]+)$/i', $ip, $matches)) {
  31. $ip = $matches[1];
  32. }
  33. // Range check
  34. if (preg_match('/\[\d+\-\d+\]/', $ip_string)) {
  35. $IPparts = explode('.', $ip);
  36. $whiteParts = explode('.', $ip_string);
  37. $mismatch = false;
  38. for ($i = 0; $i <= 3; $i++) {
  39. if (preg_match('/^\[(\d+)\-(\d+)\]$/', $whiteParts[$i], $m)) {
  40. if ($IPparts[$i] < $m[1] || $IPparts[$i] > $m[2]) {
  41. $mismatch = true;
  42. }
  43. } else if ($whiteParts[$i] != $IPparts[$i]) {
  44. $mismatch = true;
  45. }
  46. }
  47. if ($mismatch === false) {
  48. return true; // Is whitelisted because we did not get a mismatch
  49. }
  50. } else if ($ip_string == $ip) {
  51. return true;
  52. }
  53. // IPv6 range
  54. } else if (strpos($ip_string, ':') !== false && strpos($ip, ':') !== false) {
  55. $ip = strtolower(wfWAFUtils::expandIPv6Address($ip));
  56. $ip_string = strtolower(self::expandIPv6Range($ip_string));
  57. if (preg_match('/\[[a-f0-9]+\-[a-f0-9]+\]/i', $ip_string)) {
  58. $IPparts = explode(':', $ip);
  59. $whiteParts = explode(':', $ip_string);
  60. $mismatch = false;
  61. for ($i = 0; $i <= 7; $i++) {
  62. if (preg_match('/^\[([a-f0-9]+)\-([a-f0-9]+)\]$/i', $whiteParts[$i], $m)) {
  63. $ip_group = hexdec($IPparts[$i]);
  64. $range_group_from = hexdec($m[1]);
  65. $range_group_to = hexdec($m[2]);
  66. if ($ip_group < $range_group_from || $ip_group > $range_group_to) {
  67. $mismatch = true;
  68. break;
  69. }
  70. } else if ($whiteParts[$i] != $IPparts[$i]) {
  71. $mismatch = true;
  72. break;
  73. }
  74. }
  75. if ($mismatch === false) {
  76. return true; // Is whitelisted because we did not get a mismatch
  77. }
  78. } else if ($ip_string == $ip) {
  79. return true;
  80. }
  81. }
  82. return false;
  83. }
  84. /**
  85. * Return a set of where clauses to use in MySQL.
  86. *
  87. * @param string $column
  88. * @return false|null|string
  89. */
  90. public function toSQL($column = 'ip') {
  91. /** @var wpdb $wpdb */
  92. global $wpdb;
  93. $ip_string = $this->getIPString();
  94. if (strpos($ip_string, '.') !== false && preg_match('/\[\d+\-\d+\]/', $ip_string)) {
  95. $whiteParts = explode('.', $ip_string);
  96. $sql = "(SUBSTR($column, 1, 12) = LPAD(CHAR(0xff, 0xff), 12, CHAR(0)) AND ";
  97. for ($i = 0, $j = 24; $i <= 3; $i++, $j -= 8) {
  98. // MySQL can only perform bitwise operations on integers
  99. $conv = sprintf('CAST(CONV(HEX(SUBSTR(%s, 13, 8)), 16, 10) as UNSIGNED INTEGER)', $column);
  100. if (preg_match('/^\[(\d+)\-(\d+)\]$/', $whiteParts[$i], $m)) {
  101. $sql .= $wpdb->prepare("$conv >> $j & 0xFF BETWEEN %d AND %d", $m[1], $m[2]);
  102. } else {
  103. $sql .= $wpdb->prepare("$conv >> $j & 0xFF = %d", $whiteParts[$i]);
  104. }
  105. $sql .= ' AND ';
  106. }
  107. $sql = substr($sql, 0, -5) . ')';
  108. return $sql;
  109. } else if (strpos($ip_string, ':') !== false) {
  110. $ip_string = strtolower(self::expandIPv6Range($ip_string));
  111. if (preg_match('/\[[a-f0-9]+\-[a-f0-9]+\]/i', $ip_string)) {
  112. $whiteParts = explode(':', $ip_string);
  113. $sql = '(';
  114. for ($i = 0; $i <= 7; $i++) {
  115. // MySQL can only perform bitwise operations on integers
  116. $conv = sprintf('CAST(CONV(HEX(SUBSTR(%s, %d, 8)), 16, 10) as UNSIGNED INTEGER)', $column, $i < 4 ? 1 : 9);
  117. $j = 16 * (3 - ($i % 4));
  118. if (preg_match('/^\[([a-f0-9]+)\-([a-f0-9]+)\]$/i', $whiteParts[$i], $m)) {
  119. $sql .= $wpdb->prepare("$conv >> $j & 0xFFFF BETWEEN 0x%x AND 0x%x", hexdec($m[1]), hexdec($m[2]));
  120. } else {
  121. $sql .= $wpdb->prepare("$conv >> $j & 0xFFFF = 0x%x", hexdec($whiteParts[$i]));
  122. }
  123. $sql .= ' AND ';
  124. }
  125. $sql = substr($sql, 0, -5) . ')';
  126. return $sql;
  127. }
  128. }
  129. return $wpdb->prepare("($column = %s)", wfWAFUtils::inet_pton($ip_string));
  130. }
  131. /**
  132. * Expand a compressed printable range representation of an IPv6 address.
  133. *
  134. * @todo Hook up exceptions for better error handling.
  135. * @todo Allow IPv4 mapped IPv6 addresses (::ffff:192.168.1.1).
  136. * @param string $ip_range
  137. * @return string
  138. */
  139. public static function expandIPv6Range($ip_range) {
  140. $colon_count = substr_count($ip_range, ':');
  141. $dbl_colon_count = substr_count($ip_range, '::');
  142. if ($dbl_colon_count > 1) {
  143. return false;
  144. }
  145. $dbl_colon_pos = strpos($ip_range, '::');
  146. if ($dbl_colon_pos !== false) {
  147. $ip_range = str_replace('::', str_repeat(':0000',
  148. (($dbl_colon_pos === 0 || $dbl_colon_pos === strlen($ip_range) - 2) ? 9 : 8) - $colon_count) . ':', $ip_range);
  149. $ip_range = trim($ip_range, ':');
  150. }
  151. $colon_count = substr_count($ip_range, ':');
  152. if ($colon_count != 7) {
  153. return false;
  154. }
  155. $groups = explode(':', $ip_range);
  156. $expanded = '';
  157. foreach ($groups as $group) {
  158. if (preg_match('/\[([a-f0-9]{1,4})\-([a-f0-9]{1,4})\]/i', $group, $matches)) {
  159. $expanded .= sprintf('[%s-%s]', str_pad(strtolower($matches[1]), 4, '0', STR_PAD_LEFT), str_pad(strtolower($matches[2]), 4, '0', STR_PAD_LEFT)) . ':';
  160. } else if (preg_match('/[a-f0-9]{1,4}/i', $group)) {
  161. $expanded .= str_pad(strtolower($group), 4, '0', STR_PAD_LEFT) . ':';
  162. } else {
  163. return false;
  164. }
  165. }
  166. return trim($expanded, ':');
  167. }
  168. /**
  169. * @return bool
  170. */
  171. public function isValidRange() {
  172. return $this->isValidIPv4Range() || $this->isValidIPv6Range();
  173. }
  174. /**
  175. * @return bool
  176. */
  177. public function isValidIPv4Range() {
  178. $ip_string = $this->getIPString();
  179. if (preg_match_all('/(\d+)/', $ip_string, $matches) > 0) {
  180. foreach ($matches[1] as $match) {
  181. $group = (int) $match;
  182. if ($group > 255 || $group < 0) {
  183. return false;
  184. }
  185. }
  186. }
  187. $group_regex = '([0-9]{1,3}|\[[0-9]{1,3}\-[0-9]{1,3}\])';
  188. return preg_match('/^' . str_repeat("$group_regex.", 3) . $group_regex . '$/i', $ip_string) > 0;
  189. }
  190. /**
  191. * @return bool
  192. */
  193. public function isValidIPv6Range() {
  194. $ip_string = $this->getIPString();
  195. if (strpos($ip_string, '::') !== false) {
  196. $ip_string = self::expandIPv6Range($ip_string);
  197. }
  198. if (!$ip_string) {
  199. return false;
  200. }
  201. $group_regex = '([a-f0-9]{1,4}|\[[a-f0-9]{1,4}\-[a-f0-9]{1,4}\])';
  202. return preg_match('/^' . str_repeat("$group_regex:", 7) . $group_regex . '$/i', $ip_string) > 0;
  203. }
  204. /**
  205. * @return string|null
  206. */
  207. public function getIPString() {
  208. return $this->ip_string;
  209. }
  210. /**
  211. * @param string|null $ip_string
  212. */
  213. public function setIPString($ip_string) {
  214. $this->ip_string = strtolower(preg_replace('/[\x{2013}-\x{2015}]/u', '-', $ip_string)); //Replace em-dash, en-dash, and horizontal bar with a regular dash
  215. }
  216. }