advisor.php
11 months ago
chatbot.php
10 months ago
discussions.php
11 months ago
files.php
11 months ago
gdpr.php
11 months ago
search.php
11 months ago
security.php
11 months ago
tasks.php
11 months ago
wand.php
11 months ago
security.php
97 lines
| 1 | <?php |
| 2 | |
| 3 | class Meow_MWAI_Modules_Security { |
| 4 | public $core = null; |
| 5 | public $banned_ips = []; |
| 6 | public $banned_words = []; |
| 7 | |
| 8 | public function __construct( $core ) { |
| 9 | $this->core = $core; |
| 10 | $this->banned_ips = $this->core->get_option( 'banned_ips' ); |
| 11 | $this->banned_words = $this->core->get_option( 'banned_words' ); |
| 12 | |
| 13 | if ( !empty( $this->banned_ips ) ) { |
| 14 | add_filter( 'mwai_ai_allowed', [ $this, 'check_banned_ips' ], 5, 3 ); |
| 15 | } |
| 16 | if ( !empty( $this->banned_words ) ) { |
| 17 | add_filter( 'mwai_ai_allowed', [ $this, 'check_banned_words' ], 5, 3 ); |
| 18 | } |
| 19 | } |
| 20 | |
| 21 | public function check_banned_ips( $ok, $query, $limits ) { |
| 22 | if ( $ok !== true || empty( $this->banned_ips ) ) { |
| 23 | return $ok; |
| 24 | } |
| 25 | if ( is_a( $query, 'Meow_MWAI_Query_Embed' ) ) { |
| 26 | if ( $this->core->can_access_settings() ) { |
| 27 | return $ok; |
| 28 | } |
| 29 | } |
| 30 | $ip = $this->core->get_ip_address( true ); |
| 31 | if ( $this->is_blocked_ip( $ip, $this->banned_ips ) ) { |
| 32 | Meow_MWAI_Logging::warn( "Blocked IP: $ip", '🔒' ); |
| 33 | throw new Exception( 'Your query has been rejected.' ); |
| 34 | } |
| 35 | return $ok; |
| 36 | } |
| 37 | |
| 38 | public function check_banned_words( $ok, $query, $limits ) { |
| 39 | if ( $ok !== true || empty( $this->banned_words ) ) { |
| 40 | return $ok; |
| 41 | } |
| 42 | if ( is_a( $query, 'Meow_MWAI_Query_Embed' ) ) { |
| 43 | if ( $this->core->can_access_settings() ) { |
| 44 | return $ok; |
| 45 | } |
| 46 | } |
| 47 | $text = $query->get_message(); |
| 48 | $is_substring_match = $this->core->get_option( 'ignore_word_boundaries' ); |
| 49 | foreach ( $this->banned_words as $word ) { |
| 50 | // Use preg_quote to escape any special characters in the word |
| 51 | // This is necessary to safely include $word in the regex pattern |
| 52 | // Add the 'u' modifier to enable Unicode support |
| 53 | if ( $is_substring_match ) { |
| 54 | $pattern = '/' . preg_quote( $word, '/' ) . '/iu'; // no \b |
| 55 | } |
| 56 | else { |
| 57 | $pattern = '/\b' . preg_quote( $word, '/' ) . '\b/iu'; |
| 58 | } |
| 59 | if ( preg_match( $pattern, $text ) ) { |
| 60 | Meow_MWAI_Logging::warn( "Blocked word: $word", '🔒' ); |
| 61 | throw new Exception( 'Your query has been rejected.' ); |
| 62 | } |
| 63 | } |
| 64 | return $ok; |
| 65 | } |
| 66 | |
| 67 | public function ip_in_range( $ip, $range ) { |
| 68 | if ( strpos( $range, '/' ) === false ) { |
| 69 | $range .= '/32'; // Convert single IP to CIDR notation |
| 70 | } |
| 71 | list( $range_ip, $subnet ) = explode( '/', $range, 2 ); |
| 72 | if ( filter_var( $range_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) { |
| 73 | $ip_bin = ip2long( $ip ); |
| 74 | $range_ip_bin = ip2long( $range_ip ); |
| 75 | $subnet_mask = 0xFFFFFFFF << ( 32 - $subnet ); |
| 76 | return ( $ip_bin & $subnet_mask ) == ( $range_ip_bin & $subnet_mask ); |
| 77 | } |
| 78 | elseif ( filter_var( $range_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) { |
| 79 | $ip_bin = inet_pton( $ip ); |
| 80 | $range_ip_bin = inet_pton( $range_ip ); |
| 81 | $subnet_mask = str_repeat( "\xFF", $subnet >> 3 ) . str_repeat( "\x00", 16 - ( $subnet >> 3 ) ); |
| 82 | $subnet_mask[( $subnet >> 3 )] = chr( 0xFF << ( 8 - ( $subnet & 7 ) ) ); |
| 83 | return ( $ip_bin & $subnet_mask ) == ( $range_ip_bin & $subnet_mask ); |
| 84 | } |
| 85 | return false; |
| 86 | } |
| 87 | |
| 88 | public function is_blocked_ip( $ip, $blocked_ips ) { |
| 89 | foreach ( $blocked_ips as $range ) { |
| 90 | if ( $this->ip_in_range( $ip, $range ) ) { |
| 91 | return true; |
| 92 | } |
| 93 | } |
| 94 | return false; |
| 95 | } |
| 96 | } |
| 97 |