PluginProbe
Patchstack – WordPress & Plugins Security / 2.3.2
Patchstack – WordPress & Plugins Security v2.3.2
2.3.7 trunk 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.17 2.1.18 2.1.19 2.1.2 2.1.20 2.1.21 2.1.22 2.1.23 2.1.24 2.1.25 2.1.3 2.1.4 2.1.5 2.1.6 All 49 releases
← All changes | includes/firewall.php +194 -217 trunk2.3.2 View file →
@@ -1,217 +1,194 @@
1 -<?php
2 -
3 -// Do not allow the file to be called directly.
4 -if ( ! defined( 'ABSPATH' ) ) {
5 - exit;
6 -}
7 -
8 -/**
9 - * This class provides the firewall functionality.
10 - */
11 -class P_Firewall extends P_Core {
12 -
13 - /**
14 - * Launch the firewall rule processor.
15 - *
16 - * @param bool $from_main Whether or not the firewall is loaded from the main script or not.
17 - * @param Patchstack $core
18 - * @param bool $skip Whether or not to process and execute the rules.
19 - * @param bool $muCall Whether or not this was called from mu-plugin.
20 - * @return void
21 - */
22 - public function __construct( $from_main = false, $core = null, $skip = false, $muCall = false ) {
23 - if ( ! $from_main || ! $core ) {
24 - if ( $core ) {
25 - parent::__construct( $core );
26 - }
27 - return;
28 - }
29 -
30 - parent::__construct( $core );
31 -
32 - // If we only want to initialize the firewall but not execute the rules.
33 - if ( $skip || defined( 'DOING_CRON' ) ) {
34 - return;
35 - }
36 -
37 - // Load the extension.
38 - require_once __DIR__ . '/../lib/patchstack/vendor/autoload.php';
39 - $extension = new Patchstack\Extensions\WordPress\Extension(
40 - [
41 - 'patchstack_basic_firewall_roles' => $this->get_option( 'patchstack_basic_firewall_roles', [ 'administrator', 'editor', 'author' ] ),
42 - 'patchstack_whitelist' => get_option( 'patchstack_whitelist', '' )
43 - ],
44 - $this
45 - );
46 -
47 - // Initiate the firewall processor with our settings.
48 - $firewall = new Patchstack\Processor(
49 - $extension,
50 - $this->decode_rule_option('patchstack_firewall_rules_v3'),
51 - $this->decode_rule_option('patchstack_whitelist_rules_v3'),
52 - [
53 - 'autoblockAttempts' => $this->get_option( 'patchstack_autoblock_attempts', 10 ),
54 - 'autoblockMinutes' => $this->get_option( 'patchstack_autoblock_minutes', 30 ),
55 - 'autoblockTime' => $this->get_option( 'patchstack_autoblock_blocktime', 60 ),
56 - 'whitelistKeysRules' => $this->decode_rule_option( 'patchstack_whitelist_keys_rules' ),
57 - 'mustUsePluginCall' => $muCall
58 - ],
59 - $this->decode_rule_option('patchstack_firewall_rules'),
60 - $this->decode_rule_option('patchstack_whitelist_rules')
61 - );
62 -
63 - // Launch the firewall.
64 - $firewall->launch();
65 - }
66 -
67 - /**
68 - * Safely decode a stored rule option into an array. The option is expected to be
69 - * a JSON string, but may already be an array (or a malformed value); passing a
70 - * non-string to json_decode() is a fatal TypeError on PHP 8.
71 - *
72 - * @param string $name
73 - * @return array
74 - */
75 - private function decode_rule_option( $name ) {
76 - $value = get_option( $name, '[]' );
77 -
78 - if ( is_array( $value ) ) {
79 - return $value;
80 - }
81 -
82 - if ( ! is_string( $value ) ) {
83 - return [];
84 - }
85 -
86 - $decoded = json_decode( $value, true );
87 - return is_array( $decoded ) ? $decoded : [];
88 - }
89 -
90 - /**
91 - * Determine if the user is authenticated and in the list of whitelisted roles.
92 - *
93 - * @return bool
94 - */
95 - public function is_authenticated() {
96 - if ( ! is_user_logged_in() ) {
97 - return false;
98 - }
99 -
100 - // Get the whitelisted roles.
101 - $roles = $this->get_option( 'patchstack_basic_firewall_roles', [ 'administrator', 'editor', 'author' ] );
102 - if ( ! is_array( $roles ) ) {
103 - return false;
104 - }
105 -
106 - // Special scenario for super admins on a multisite environment.
107 - if ( in_array( 'administrator', $roles ) && is_multisite() && is_super_admin() ) {
108 - return true;
109 - }
110 -
111 - // Get the roles of the user.
112 - $user = wp_get_current_user();
113 - if ( ! isset( $user->roles ) || count( (array) $user->roles ) == 0 ) {
114 - return false;
115 - }
116 -
117 - // Is the user in the whitelist roles list?
118 - $role_count = array_intersect( $user->roles, $roles );
119 - return count( $role_count ) != 0;
120 - }
121 -
122 - /**
123 - * Display error page.
124 - *
125 - * @param integer $fid
126 - * @return void
127 - */
128 - public function display_error_page( $fid = 1 ) {
129 - if ( $fid != 22 && $fid != 23 && $fid != 24 && $fid != 'login' ) {
130 - $this->log_request( $fid );
131 - }
132 -
133 - // Supported by a number of popular caching plugins.
134 - if ( ! defined( 'DONOTCACHEPAGE' ) ) {
135 - define( 'DONOTCACHEPAGE', true );
136 - }
137 -
138 - // Because WP Fastest Cache just has to be special...
139 - if (function_exists('wpfc_exclude_current_page')) {
140 - @wpfc_exclude_current_page();
141 - }
142 -
143 - // Send forbidden headers and no-caching headers as well.
144 - status_header(403);
145 - send_nosniff_header();
146 - nocache_headers();
147 -
148 - if ( $fid == 'login' ) {
149 - require_once dirname( __FILE__ ) . '/views/access-denied-login.php';
150 - } else {
151 - require_once dirname( __FILE__ ) . '/views/access-denied.php';
152 - }
153 -
154 - exit;
155 - }
156 -
157 - /**
158 - * Log the blocked request.
159 - *
160 - * @param int $fid
161 - * @return void
162 - */
163 - private function log_request( $fid = 1 ) {
164 - global $wpdb;
165 - if ( ! $wpdb || $fid == 22 || $fid == 23 || $fid == 24 || $fid == 'login' ) {
166 - return;
167 - }
168 -
169 - // Insert into the logs.
170 - $wpdb->insert(
171 - $wpdb->prefix . 'patchstack_firewall_log',
172 - array(
173 - 'ip' => $this->get_ip(),
174 - 'request_uri' => isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '',
175 - 'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '',
176 - 'method' => isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : '',
177 - 'fid' => $fid,
178 - 'flag' => '',
179 - 'post_data' => '',
180 - 'block_type' => 'BLOCK',
181 - )
182 - );
183 - }
184 -
185 - /**
186 - * Extract the number of blocked hits the past 30 days based on the current counters.
187 - *
188 - * @return int
189 - */
190 - public function get_hits_counter() {
191 - $counters = get_option( 'patchstack_hits_last_30', [] );
192 - if (!is_array($counters) || count($counters) === 0) {
193 - return 0;
194 - }
195 -
196 - // Set the range of dates we need.
197 - $hits = 0;
198 - $start = new \DateTime();
199 - $start->modify('-30 days');
200 -
201 - $end = new \DateTime();
202 - $end->modify('+1 day');
203 -
204 - $interval = new \DateInterval('P1D');
205 - $range = new \DatePeriod($start, $interval, $end);
206 -
207 - // Set the range from -6 days to +1 day from now.
208 - foreach ($range as $date) {
209 - $formattedDate = $date->format('Y-m-d');
210 - if (isset($counters[$formattedDate])) {
211 - $hits += $counters[$formattedDate];
212 - }
213 - }
214 -
215 - return $hits;
216 - }
217 -}
1 +<?php
2 +
3 +// Do not allow the file to be called directly.
4 +if ( ! defined( 'ABSPATH' ) ) {
5 + exit;
6 +}
7 +
8 +/**
9 + * This class provides the firewall functionality.
10 + */
11 +class P_Firewall extends P_Core {
12 +
13 + /**
14 + * Launch the firewall rule processor.
15 + *
16 + * @param bool $from_main Whether or not the firewall is loaded from the main script or not.
17 + * @param Patchstack $core
18 + * @param bool $skip Whether or not to process and execute the rules.
19 + * @param bool $muCall Whether or not this was called from mu-plugin.
20 + * @return void
21 + */
22 + public function __construct( $from_main = false, $core = null, $skip = false, $muCall = false ) {
23 + if ( ! $from_main || ! $core ) {
24 + if ( $core ) {
25 + parent::__construct( $core );
26 + }
27 + return;
28 + }
29 +
30 + parent::__construct( $core );
31 +
32 + // If we only want to initialize the firewall but not execute the rules.
33 + if ( $skip || defined( 'DOING_CRON' ) ) {
34 + return;
35 + }
36 +
37 + // Load the extension.
38 + require_once __DIR__ . '/../lib/patchstack/vendor/autoload.php';
39 + $extension = new Patchstack\Extensions\WordPress\Extension(
40 + [
41 + 'patchstack_basic_firewall_roles' => $this->get_option( 'patchstack_basic_firewall_roles', [ 'administrator', 'editor', 'author' ] ),
42 + 'patchstack_whitelist' => get_option( 'patchstack_whitelist', '' )
43 + ],
44 + $this
45 + );
46 +
47 + // Initiate the firewall processor with our settings.
48 + $firewall = new Patchstack\Processor(
49 + $extension,
50 + json_decode(get_option('patchstack_firewall_rules_v3', '[]'), true),
51 + json_decode(get_option('patchstack_whitelist_rules_v3', '[]'), true),
52 + [
53 + 'autoblockAttempts' => $this->get_option( 'patchstack_autoblock_attempts', 10 ),
54 + 'autoblockMinutes' => $this->get_option( 'patchstack_autoblock_minutes', 30 ),
55 + 'autoblockTime' => $this->get_option( 'patchstack_autoblock_blocktime', 60 ),
56 + 'whitelistKeysRules' => json_decode( get_option( 'patchstack_whitelist_keys_rules', '[]' ), true ),
57 + 'mustUsePluginCall' => $muCall
58 + ],
59 + json_decode(get_option('patchstack_firewall_rules', '[]'), true),
60 + json_decode(get_option('patchstack_whitelist_rules', '[]'), true)
61 + );
62 +
63 + // Launch the firewall.
64 + $firewall->launch();
65 + }
66 +
67 + /**
68 + * Determine if the user is authenticated and in the list of whitelisted roles.
69 + *
70 + * @return bool
71 + */
72 + public function is_authenticated() {
73 + if ( ! is_user_logged_in() ) {
74 + return false;
75 + }
76 +
77 + // Get the whitelisted roles.
78 + $roles = $this->get_option( 'patchstack_basic_firewall_roles', [ 'administrator', 'editor', 'author' ] );
79 + if ( ! is_array( $roles ) ) {
80 + return false;
81 + }
82 +
83 + // Special scenario for super admins on a multisite environment.
84 + if ( in_array( 'administrator', $roles ) && is_multisite() && is_super_admin() ) {
85 + return true;
86 + }
87 +
88 + // Get the roles of the user.
89 + $user = wp_get_current_user();
90 + if ( ! isset( $user->roles ) || count( (array) $user->roles ) == 0 ) {
91 + return false;
92 + }
93 +
94 + // Is the user in the whitelist roles list?
95 + $role_count = array_intersect( $user->roles, $roles );
96 + return count( $role_count ) != 0;
97 + }
98 +
99 + /**
100 + * Display error page.
101 + *
102 + * @param integer $fid
103 + * @return void
104 + */
105 + public function display_error_page( $fid = 1 ) {
106 + if ( $fid != 22 && $fid != 23 && $fid != 24 && $fid != 'login' ) {
107 + $this->log_request( $fid );
108 + }
109 +
110 + // Supported by a number of popular caching plugins.
111 + if ( ! defined( 'DONOTCACHEPAGE' ) ) {
112 + define( 'DONOTCACHEPAGE', true );
113 + }
114 +
115 + // Because WP Fastest Cache just has to be special...
116 + if (function_exists('wpfc_exclude_current_page')) {
117 + @wpfc_exclude_current_page();
118 + }
119 +
120 + // Send forbidden headers and no-caching headers as well.
121 + status_header(403);
122 + send_nosniff_header();
123 + nocache_headers();
124 +
125 + if ( $fid == 'login' ) {
126 + require_once dirname( __FILE__ ) . '/views/access-denied-login.php';
127 + } else {
128 + require_once dirname( __FILE__ ) . '/views/access-denied.php';
129 + }
130 +
131 + exit;
132 + }
133 +
134 + /**
135 + * Log the blocked request.
136 + *
137 + * @param int $fid
138 + * @return void
139 + */
140 + private function log_request( $fid = 1 ) {
141 + global $wpdb;
142 + if ( ! $wpdb || $fid == 22 || $fid == 23 || $fid == 24 || $fid == 'login' ) {
143 + return;
144 + }
145 +
146 + // Insert into the logs.
147 + $wpdb->insert(
148 + $wpdb->prefix . 'patchstack_firewall_log',
149 + array(
150 + 'ip' => $this->get_ip(),
151 + 'request_uri' => isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '',
152 + 'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '',
153 + 'method' => isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : '',
154 + 'fid' => $fid,
155 + 'flag' => '',
156 + 'post_data' => '',
157 + 'block_type' => 'BLOCK',
158 + )
159 + );
160 + }
161 +
162 + /**
163 + * Extract the number of blocked hits the past 30 days based on the current counters.
164 + *
165 + * @return int
166 + */
167 + public function get_hits_counter() {
168 + $counters = get_option( 'patchstack_hits_last_30', [] );
169 + if (!is_array($counters) || count($counters) === 0) {
170 + return 0;
171 + }
172 +
173 + // Set the range of dates we need.
174 + $hits = 0;
175 + $start = new \DateTime();
176 + $start->modify('-30 days');
177 +
178 + $end = new \DateTime();
179 + $end->modify('+1 day');
180 +
181 + $interval = new \DateInterval('P1D');
182 + $range = new \DatePeriod($start, $interval, $end);
183 +
184 + // Set the range from -6 days to +1 day from now.
185 + foreach ($range as $date) {
186 + $formattedDate = $date->format('Y-m-d');
187 + if (isset($counters[$formattedDate])) {
188 + $hits += $counters[$formattedDate];
189 + }
190 + }
191 +
192 + return $hits;
193 + }
194 +}