PluginProbe ʕ •ᴥ•ʔ
NinjaFirewall (WP Edition) – Advanced Security Plugin and Firewall / trunk
NinjaFirewall (WP Edition) – Advanced Security Plugin and Firewall vtrunk
4.8.8 4.8.7 4.8.6 trunk 4.5 4.5.1 4.5.10 4.5.11 4.5.2 4.5.3 4.5.4 4.5.5 4.5.6 4.5.7 4.5.8 4.5.9 4.6 4.6.1 4.7 4.7.1 4.7.2 4.7.3 4.7.4 4.7.5 4.8 4.8.1 4.8.2 4.8.3 4.8.4 4.8.5
ninjafirewall / lib / firewall.php
ninjafirewall / lib Last commit date
share 8 years ago .htaccess 11 years ago anti_malware.php 4 years ago class-coupon.php 6 months ago class-email-sodium.php 3 weeks ago class-firewall-log.php 1 week ago class-helpers.php 7 months ago class-import-export.php 3 months ago class-ip.php 4 months ago class-nfw-database.php 6 months ago class-nfw-session.php 1 week ago class-php-session.php 1 year ago class-plugin-upgrade.php 1 week ago class_mail.php 3 weeks ago event_updates.php 9 months ago firewall.php 4 months ago fw_centlog.php 1 week ago fw_fileguard.php 4 months ago fw_livelog.php 1 year ago help.php 3 weeks ago helpers.php 1 week ago i18n-extra.php 3 weeks ago i18n.php 1 year ago index.html 13 years ago init_update.php 1 year ago install.php 1 year ago install_default.php 6 months ago loader.php 6 months ago mail_template_firewall.php 1 year ago mail_template_plugin.php 1 month ago scheduled_tasks.php 3 years ago settings_dashboard.php 1 month ago settings_dashboard_about.php 3 weeks ago settings_dashboard_statistics.php 1 month ago settings_event_notifications.php 1 month ago settings_events.php 1 month ago settings_firewall_options.php 1 month ago settings_firewall_policies.php 1 week ago settings_login_protection.php 1 month ago settings_logs.php 1 month ago settings_logs_firewall_log.php 3 weeks ago settings_logs_live_log.php 1 month ago settings_monitoring.php 3 weeks ago settings_monitoring_file_check.php 1 month ago settings_monitoring_file_guard.php 1 month ago settings_network.php 1 month ago settings_security_rules.php 1 month ago settings_security_rules_editor.php 1 month ago settings_security_rules_update.php 1 week ago sign.pub 7 years ago thickbox.php 4 years ago widget.php 3 years ago wpplus.php 3 months ago
firewall.php
2113 lines
1 <?php
2 /*
3 +=====================================================================+
4 | _ _ _ _ _____ _ _ _ |
5 | | \ | (_)_ __ (_) __ _| ___(_)_ __ _____ ____ _| | | |
6 | | \| | | '_ \ | |/ _` | |_ | | '__/ _ \ \ /\ / / _` | | | |
7 | | |\ | | | | || | (_| | _| | | | | __/\ V V / (_| | | | |
8 | |_| \_|_|_| |_|/ |\__,_|_| |_|_| \___| \_/\_/ \__,_|_|_| |
9 | |__/ |
10 | (c) NinTechNet Limited ~ https://nintechnet.com/ |
11 +=====================================================================+
12 */
13 if ( strpos($_SERVER['SCRIPT_NAME'], '/nfwlog/') !== FALSE ||
14 strpos($_SERVER['SCRIPT_NAME'], '/ninjafirewall/') !== FALSE ) {
15 die('Forbidden');
16 }
17 if ( defined('NFW_STATUS') ) { return; }
18 if ( defined('WP_CLI') && WP_CLI && PHP_SAPI === 'cli' ) {
19 if (! defined('NFW_UWL') ) {
20 define('NFW_UWL', true);
21 }
22 return;
23 }
24
25 $nfw_ = [];
26 $nfw_['fw_starttime'] = nfw_fc_metrics('start');
27
28 /**
29 * Required classes and constants.
30 */
31 if ( ! defined('NFWLOG_DEBUG') ) {
32 define('NFWLOG_MEDIUM', 1);
33 define('NFWLOG_HIGH', 2);
34 define('NFWLOG_CRITICAL', 3);
35 define('NFWLOG_POSTDETECT', 4);
36 define('NFWLOG_UPLOAD', 5);
37 define('NFWLOG_INFO', 6);
38 define('NFWLOG_DEBUG', 7);
39 }
40 require_once __DIR__ .'/class-ip.php';
41 require_once __DIR__ .'/class-firewall-log.php';
42
43 /**
44 * Optional NinjaFirewall configuration file.
45 * See https://blog.nintechnet.com/ninjafirewall-wp-edition-the-htninja-configuration-file/
46 */
47 if ( @is_file( $nfw_['file'] = $_SERVER['DOCUMENT_ROOT'] .'/.htninja') ||
48 @is_file( $nfw_['file'] = dirname( $_SERVER['DOCUMENT_ROOT'] ) .'/.htninja') ) {
49
50 $nfw_['res'] = @include_once $nfw_['file'];
51 /**
52 * Allow and stop filtering.
53 */
54 if ( $nfw_['res'] == 'ALLOW') {
55 if (! defined('NFW_UWL') ) {
56 define('NFW_UWL', true );
57 }
58 nfw_quit( 20 );
59 return;
60 }
61 /**
62 * Reject immediately.
63 */
64 if ( $nfw_['res'] == 'BLOCK') {
65 header('HTTP/1.1 403 Forbidden');
66 header('Status: 403 Forbidden');
67 header('Pragma: no-cache');
68 header('Cache-Control: no-cache, no-store, must-revalidate');
69 header('Expires: 0');
70 die('403 Forbidden');
71 }
72 }
73 // Clear warning if there's an open_basedir restriction
74 if ( function_exists('error_clear_last') ) { // PHP 7.0+
75 error_clear_last();
76 }
77
78 $nfw_['wp_content'] = dirname(dirname(dirname( __DIR__ )));
79 // Check if we have a user-defined log directory
80 // (see "Path to NinjaFirewall's log and cache directory"
81 // at https://blog.nintechnet.com/ninjafirewall-wp-edition-the-htninja-configuration-file/ ) :
82 if ( defined('NFW_LOG_DIR') ) {
83 $nfw_['log_dir'] = NFW_LOG_DIR . '/nfwlog';
84 } else {
85 $nfw_['log_dir'] = $nfw_['wp_content'] . '/nfwlog';
86 }
87 if (! is_dir($nfw_['log_dir']) ) {
88 if (! mkdir( $nfw_['log_dir'] . '/cache', 0755, true) ) {
89 define( 'NFW_STATUS', 13 );
90 return;
91 }
92 }
93
94 /**
95 * Select whether we want to use PHP or NF (default since v4.8.1) sessions.
96 */
97 if ( is_file( "{$nfw_['log_dir']}/phpsession" ) ) {
98 require_once __DIR__ .'/class-php-session.php';
99 } else {
100 if (! defined('NFWSESSION_DIR') ) {
101 /**
102 * NFWSESSION_DIR can be defined in the .htninja.
103 */
104 define('NFWSESSION_DIR', "{$nfw_['log_dir']}/session" );
105 }
106 require_once __DIR__ .'/class-nfw-session.php';
107 }
108
109 // Get/set PID
110 if ( is_file( "{$nfw_['log_dir']}/cache/.pid" ) ) {
111 define( 'NFW_PID', file_get_contents( "{$nfw_['log_dir']}/cache/.pid" ) );
112 }
113
114 // Check if we are connecting over HTTPS
115 nfw_is_https();
116
117 if ( strpos($_SERVER['SCRIPT_NAME'], 'wp-login.php' ) !== FALSE ) {
118 nfw_bfd(1);
119 } elseif ( strpos($_SERVER['SCRIPT_NAME'], 'xmlrpc.php' ) !== FALSE ) {
120 nfw_bfd(2);
121 }
122
123 if (empty ($wp_config)) {
124 $wp_config = dirname($nfw_['wp_content']) . '/wp-config.php';
125 }
126
127 // Connection
128 $ret = nfw_connect();
129 if ( $ret !== true ) {
130 nfw_quit( $ret );
131 return;
132 }
133
134 // Fetch options
135 $ret = nfw_get_data( 'nfw_options' );
136 if ( $ret !== true || empty( $nfw_['nfw_options'] ) ) {
137 nfw_quit( $ret );
138 return;
139 }
140
141 /**
142 * Verify and retrieve the user IP address.
143 */
144 NinjaFirewall_IP::check_ip( ['ac_ip' => 1 ] );
145
146 if (! empty($nfw_['nfw_options']['clogs_pubkey']) && isset($_POST['clogs_req']) ) {
147 include_once 'fw_centlog.php';
148 fw_centlog();
149 exit;
150 }
151
152 if ( empty($nfw_['nfw_options']['enabled']) ) {
153 nfw_quit( 20 );
154 return;
155 }
156
157 // HTTP response headers
158 if ( (! empty( $nfw_['nfw_options']['response_headers'] ) || ! empty($nfw_['nfw_options']['custom_headers']) )
159 && function_exists('header_register_callback') ) {
160
161 if (! empty( $nfw_['nfw_options']['response_headers'] ) ) {
162 define('NFW_RESHEADERS', $nfw_['nfw_options']['response_headers']);
163 if (! empty( $nfw_['nfw_options']['response_headers'][6] ) && ! empty( $nfw_['nfw_options']['csp_frontend_data'] ) ) {
164 define( 'CSP_FRONTEND_DATA', $nfw_['nfw_options']['csp_frontend_data']);
165 }
166 if (! empty( $nfw_['nfw_options']['response_headers'][7] ) && ! empty( $nfw_['nfw_options']['csp_backend_data'] ) ) {
167 define( 'CSP_BACKEND_DATA', $nfw_['nfw_options']['csp_backend_data'] );
168 }
169 }
170 if (! empty( $nfw_['nfw_options']['custom_headers'] ) ) {
171 define('NFW_CUSTHEADERS', $nfw_['nfw_options']['custom_headers']);
172 }
173 header_register_callback('nfw_response_headers');
174 }
175
176 if (! empty($nfw_['nfw_options']['force_ssl']) ) {
177 define('FORCE_SSL_ADMIN', true);
178 }
179 if (! empty($nfw_['nfw_options']['disallow_edit']) ) {
180 define('DISALLOW_FILE_EDIT', true);
181 }
182 if (! empty($nfw_['nfw_options']['disallow_mods']) ) {
183 define('DISALLOW_FILE_MODS', true);
184 }
185 if (! empty($nfw_['nfw_options']['disable_error_handler']) ) {
186 define('WP_DISABLE_FATAL_ERROR_HANDLER', true);
187 }
188
189 // Superglobals override
190 if (! empty($nfw_['nfw_options']['php_superglobals']) ) {
191 $sgs = [
192 '_GET', '_POST', '_SESSION', '_COOKIE',
193 '_SERVER', '_FILES', '_ENV', '_REQUEST', 'GLOBALS'
194 ];
195 foreach( $sgs as $sg ) {
196 if ( isset( $_GET[$sg] ) ) {
197
198 $nfw_['incidentID'] = NinjaFirewall_log::write(
199 'Superglobals override attempt',
200 "\$_GET[$sg]: ". serialize( $_GET[$sg] ),
201 NFWLOG_INFO, 0, $nfw_['nfw_options'], $nfw_['log_dir']
202 );
203 unset( $_GET[$sg] );
204 }
205 if ( isset( $_POST[$sg] ) ) {
206
207 $nfw_['incidentID'] = NinjaFirewall_log::write(
208 'Superglobals override attempt',
209 "\$_POST[$sg]: ". serialize( $_POST[$sg] ),
210 NFWLOG_INFO, 0, $nfw_['nfw_options'], $nfw_['log_dir']
211 );
212 unset( $_POST[$sg] );
213 }
214 if ( isset( $_COOKIE[$sg] ) ) {
215
216 $nfw_['incidentID'] = NinjaFirewall_log::write(
217 'Superglobals override attempt',
218 "\$_COOKIE[$sg]: ". serialize( $_COOKIE[$sg] ),
219 NFWLOG_INFO, 0, $nfw_['nfw_options'], $nfw_['log_dir']
220 );
221 unset( $_COOKIE[$sg] );
222 }
223 }
224 }
225
226 // We only start a session if users already have a session
227 // cookie because we don't need write access yet
228 $session_name = NinjaFirewall_session::name();
229 if ( isset( $_COOKIE[ $session_name ] ) ) {
230 NinjaFirewall_session::start();
231 }
232
233 if (! empty( NinjaFirewall_session::read('nfw_goodguy') ) ) {
234 // Look for Live Log AJAX request
235 if (! empty( NinjaFirewall_session::read('nfw_livelog') ) &&
236 isset( $_POST['livecls'] ) && isset( $_POST['lines'] ) ) {
237
238 include_once 'fw_livelog.php';
239 fw_livelog_show();
240 }
241
242 // Fetch admin rules
243 $ret = nfw_get_data( 'nfw_rules' );
244 if ( $ret !== true ) {
245 nfw_quit( $ret );
246 return;
247 }
248 nfw_check_admin_request();
249
250 nfw_quit( 20 );
251 return;
252 }
253 define('NFW_SWL', 1);
254
255 if ( is_file($nfw_['log_dir'] .'/cache/livelogrun.php')) {
256 include_once 'fw_livelog.php';
257 fw_livelog_record();
258 }
259
260 if (! empty($nfw_['nfw_options']['php_errors']) ) {
261 @error_reporting(0);
262 @ini_set('display_errors', 0);
263 }
264
265 if ( empty($nfw_['nfw_options']['allow_local_ip']) && NFW_REMOTE_ADDR_PRIVATE == true ) {
266 nfw_quit(20);
267 return;
268 }
269
270 if ( NFW_REMOTE_ADDR_PRIVATE == true && strpos( $_SERVER['SCRIPT_NAME'], '/wp-cron.php' ) !== FALSE ) {
271 nfw_quit(20);
272 return;
273 }
274
275 if ( @$nfw_['nfw_options']['scan_protocol'] == 1 && NFW_IS_HTTPS == true ) {
276 nfw_quit(20);
277 return;
278 }
279 if ( @$nfw_['nfw_options']['scan_protocol'] == 2 && NFW_IS_HTTPS == false ) {
280 nfw_quit(20);
281 return;
282 }
283
284 /**
285 * File Guard.
286 */
287 if (! empty( $nfw_['nfw_options']['fg_enable'] ) ) {
288 include_once 'fw_fileguard.php';
289 fw_fileguard();
290 }
291
292 if (! empty($nfw_['nfw_options']['no_host_ip']) && @filter_var(parse_url('http://'.$_SERVER['HTTP_HOST'], PHP_URL_HOST), FILTER_VALIDATE_IP) ) {
293
294 $nfw_['incidentID'] = NinjaFirewall_log::write(
295 'HTTP_HOST is an IP',
296 $_SERVER['HTTP_HOST'],
297 NFWLOG_HIGH, 0, $nfw_['nfw_options'], $nfw_['log_dir']
298 );
299 nfw_block();
300 }
301
302 if (! empty($nfw_['nfw_options']['referer_post']) && $_SERVER['REQUEST_METHOD'] == 'POST' && ! isset($_SERVER['HTTP_REFERER']) ) {
303
304 $nfw_['incidentID'] = NinjaFirewall_log::write(
305 'POST method without Referer header',
306 $_SERVER['REQUEST_METHOD'],
307 NFWLOG_MEDIUM, 0, $nfw_['nfw_options'], $nfw_['log_dir']
308 );
309 nfw_block();
310 }
311
312 if (! empty($nfw_['nfw_options']['admin_ajax']) && strpos( $_SERVER['SCRIPT_NAME'], 'wp-admin/admin-ajax.php' ) !== FALSE ) {
313 nfw_is_bot( 'admin-ajax.php' );
314 }
315
316 if ( strpos($_SERVER['SCRIPT_NAME'], '/xmlrpc.php' ) !== FALSE ) {
317 if (! empty($nfw_['nfw_options']['no_xmlrpc']) ) {
318
319 $nfw_['incidentID'] = NinjaFirewall_log::write(
320 'Access to WordPress XML-RPC API',
321 $_SERVER['SCRIPT_NAME'],
322 NFWLOG_HIGH, 0, $nfw_['nfw_options'], $nfw_['log_dir']
323 );
324 nfw_block();
325 }
326 if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
327 if (! isset( $HTTP_RAW_POST_DATA ) ) {
328 @$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
329 }
330
331 if (! empty($nfw_['nfw_options']['no_xmlrpc_multi']) ) {
332
333 if ( @strpos( $HTTP_RAW_POST_DATA, '<methodName>system.multicall</methodName>') !== FALSE ) {
334
335 $nfw_['incidentID'] = NinjaFirewall_log::write(
336 'Access to WordPress XML-RPC API (system.multicall method)',
337 $_SERVER['SCRIPT_NAME'],
338 NFWLOG_HIGH, 0, $nfw_['nfw_options'], $nfw_['log_dir']
339 );
340 nfw_block();
341 }
342 }
343
344 if (! empty($nfw_['nfw_options']['no_xmlrpc_pingback']) ) {
345
346 if ( @strpos( $HTTP_RAW_POST_DATA, '<methodName>pingback.ping</methodName>') !== FALSE ) {
347
348 $nfw_['incidentID'] = NinjaFirewall_log::write(
349 'Access to WordPress XML-RPC API (pingback.ping)',
350 $_SERVER['SCRIPT_NAME'],
351 NFWLOG_HIGH, 0, $nfw_['nfw_options'], $nfw_['log_dir']
352 );
353 nfw_block();
354 }
355 }
356 }
357 }
358 if (! empty($nfw_['nfw_options']['no_xmlrpc_pingback']) && strpos($_SERVER['HTTP_USER_AGENT'], '; verifying pingback from ') !== FALSE) {
359
360 $nfw_['incidentID'] = NinjaFirewall_log::write(
361 'Blocked pingback verification',
362 $_SERVER['HTTP_USER_AGENT'],
363 NFWLOG_HIGH, 0, $nfw_['nfw_options'], $nfw_['log_dir']
364 );
365 nfw_block();
366 }
367
368 // WordPress Aplication Passwords
369 if (! empty($nfw_['nfw_options']['no_appswd']) && strpos( $_SERVER['SCRIPT_NAME'], '/wp-admin/authorize-application.php' ) !== FALSE ) {
370
371 $nfw_['incidentID'] = NinjaFirewall_log::write(
372 'Access to WordPress Application Passwords',
373 $_SERVER['SCRIPT_NAME'],
374 NFWLOG_HIGH, 0, $nfw_['nfw_options'], $nfw_['log_dir']
375 );
376 nfw_block();
377 }
378
379 if (! empty($nfw_['nfw_options']['no_post_themes']) && $_SERVER['REQUEST_METHOD'] == 'POST' && strpos($_SERVER['SCRIPT_NAME'], $nfw_['nfw_options']['no_post_themes']) !== FALSE ) {
380
381 $nfw_['incidentID'] = NinjaFirewall_log::write(
382 'POST request in the themes folder',
383 $_SERVER['SCRIPT_NAME'],
384 NFWLOG_HIGH, 0, $nfw_['nfw_options'], $nfw_['log_dir']
385 );
386 nfw_block();
387 }
388
389 if (! empty($nfw_['nfw_options']['wp_dir']) && preg_match( '`' . $nfw_['nfw_options']['wp_dir'] . '`', $_SERVER['SCRIPT_NAME']) ) {
390
391 $nfw_['incidentID'] = NinjaFirewall_log::write(
392 'Forbidden direct access to PHP script',
393 $_SERVER['SCRIPT_NAME'],
394 NFWLOG_HIGH, 0, $nfw_['nfw_options'], $nfw_['log_dir']
395 );
396 nfw_block();
397 }
398
399 nfw_check_upload();
400
401 // Fetch rules
402 $ret = nfw_get_data( 'nfw_rules' );
403 if ( $ret !== true ) {
404 nfw_quit( $ret );
405 return;
406 }
407
408 nfw_check_request( $nfw_['nfw_rules'], $nfw_['nfw_options'] );
409
410 if (! empty($nfw_['nfw_options']['get_sanitise']) && ! empty($_GET) ){
411 $_GET = nfw_sanitise( $_GET, 1, 'GET');
412 }
413 if (! empty($nfw_['nfw_options']['post_sanitise']) && ! empty($_POST) ){
414 $_POST = nfw_sanitise( $_POST, 1, 'POST');
415 }
416 if (! empty($nfw_['nfw_options']['request_sanitise']) && ! empty($_REQUEST) ){
417 $_REQUEST = nfw_sanitise( $_REQUEST, 1, 'REQUEST');
418 }
419 if (! empty($nfw_['nfw_options']['cookies_sanitise']) && ! empty($_COOKIE) ) {
420 $_COOKIE = nfw_sanitise( $_COOKIE, 3, 'COOKIE');
421 }
422 if (! empty($nfw_['nfw_options']['ua_sanitise']) && ! empty($_SERVER['HTTP_USER_AGENT']) ) {
423 $_SERVER['HTTP_USER_AGENT'] = nfw_sanitise( $_SERVER['HTTP_USER_AGENT'], 1, 'HTTP_USER_AGENT');
424 }
425 if (! empty($nfw_['nfw_options']['referer_sanitise']) && ! empty($_SERVER['HTTP_REFERER']) ) {
426 $_SERVER['HTTP_REFERER'] = nfw_sanitise( $_SERVER['HTTP_REFERER'], 1, 'HTTP_REFERER');
427 }
428 if (! empty($nfw_['nfw_options']['php_path_i']) && ! empty($_SERVER['PATH_INFO']) ) {
429 $_SERVER['PATH_INFO'] = nfw_sanitise( $_SERVER['PATH_INFO'], 2, 'PATH_INFO');
430 }
431 if (! empty($nfw_['nfw_options']['php_path_t']) && ! empty($_SERVER['PATH_TRANSLATED']) ) {
432 $_SERVER['PATH_TRANSLATED'] = nfw_sanitise( $_SERVER['PATH_TRANSLATED'], 2, 'PATH_TRANSLATED');
433 }
434 if (! empty($nfw_['nfw_options']['php_self']) && ! empty($_SERVER['PHP_SELF']) ) {
435 $_SERVER['PHP_SELF'] = nfw_sanitise( $_SERVER['PHP_SELF'], 2, 'PHP_SELF');
436 }
437
438 nfw_quit(20);
439 return;
440
441 // =====================================================================
442 // Close the SQL link, set the firewall status, clear the $nfw_ array
443 // and close the session before leaving.
444
445 function nfw_quit( $status ) {
446
447 global $nfw_;
448 define( 'NFW_STATUS', $status );
449
450 if ( isset( $nfw_['mysqli'] ) ) {
451 $nfw_['mysqli']->close();
452 }
453 $nfw_ = [];
454 }
455
456 // =====================================================================
457 // Connect to the DB.
458
459 function nfw_connect() {
460
461 global $nfw_, $wp_config;
462
463 // WPWAF mode?
464 if ( defined('NFW_WPWAF') && NFW_WPWAF == 2 ) {
465 $nfw_['wp_waf'] = 2;
466 return true;
467 }
468
469 // Check if we have a SQL link that was defined in the .htninja.
470 // See "Giving NinjaFirewall a MySQLi link identifier"
471 // at https://blog.nintechnet.com/ninjafirewall-wp-edition-the-htninja-configuration-file/
472 if (! empty( $GLOBALS['nfw_mysqli'] ) && ! empty( $GLOBALS['nfw_table_prefix'] ) ) {
473 $nfw_['mysqli'] = $GLOBALS['nfw_mysqli'];
474 $nfw_['table_prefix'] = $GLOBALS['nfw_table_prefix'];
475 return true;
476 }
477
478 // DB
479 if (! is_file( $wp_config ) ) {
480 if (! @is_file( $wp_config = dirname( dirname($nfw_['wp_content']) ) . '/wp-config.php') ) {
481 return 1;
482 }
483 }
484 if (! $nfw_['fh'] = fopen($wp_config, 'r') ) {
485 return 2;
486 }
487
488 // Potential SQL flags
489 $nfw_['MYSQL_CLIENT_FLAGS'] = 0;
490
491 while (! feof($nfw_['fh'])) {
492 $nfw_['line'] = fgets($nfw_['fh']);
493 if ( preg_match('/^\s*define\s*\(\s*[\'"]DB_NAME[\'"]\s*,\s*[\'"](.+?)[\'"]/', $nfw_['line'], $nfw_['match']) ) {
494 $nfw_['DB_NAME'] = $nfw_['match'][1];
495 } elseif ( preg_match('/^\s*define\s*\(\s*[\'"]DB_USER[\'"]\s*,\s*[\'"](.+?)[\'"]/', $nfw_['line'], $nfw_['match']) ) {
496 $nfw_['DB_USER'] = $nfw_['match'][1];
497 } elseif ( preg_match('/^\s*define\s*\(\s*[\'"]DB_PASSWORD[\'"]\s*,\s*([\'"])(.+?)\1\s*\);/', $nfw_['line'], $nfw_['match']) ) {
498 $nfw_['DB_PASSWORD'] = str_replace( '\\'.$nfw_['match'][1], $nfw_['match'][1], $nfw_['match'][2] );
499 if ( $nfw_['match'][1] == '"' ) {
500 $nfw_['DB_PASSWORD'] = str_replace( '\$', '$', $nfw_['DB_PASSWORD'] );
501 }
502 } elseif ( preg_match('/^\s*define\s*\(\s*[\'"]DB_HOST[\'"]\s*,\s*[\'"](.+?)[\'"]/', $nfw_['line'], $nfw_['match']) ) {
503 $nfw_['DB_HOST'] = $nfw_['match'][1];
504 } elseif ( preg_match('/^\s*\$table_prefix\s*=\s*[\'"](.*?)[\'"]/', $nfw_['line'], $nfw_['match']) ) {
505 $nfw_['table_prefix'] = $nfw_['match'][1];
506 } elseif ( preg_match('/^\s*define\s*\(\s*[\'"]MYSQL_CLIENT_FLAGS[\'"]\s*,\s*(.+?)\s*\)/', $nfw_['line'], $nfw_['match']) ) {
507 if ( empty( $nfw_['MYSQL_CLIENT_FLAGS'] ) ) {
508 $available_flags = [
509 'MYSQLI_CLIENT_COMPRESS' => MYSQLI_CLIENT_COMPRESS,
510 'MYSQLI_CLIENT_FOUND_ROWS' => MYSQLI_CLIENT_FOUND_ROWS,
511 'MYSQLI_CLIENT_IGNORE_SPACE' => MYSQLI_CLIENT_IGNORE_SPACE,
512 'MYSQLI_CLIENT_INTERACTIVE' => MYSQLI_CLIENT_INTERACTIVE,
513 'MYSQLI_CLIENT_SSL' => MYSQLI_CLIENT_SSL,
514 'MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT' => MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT
515 ];
516 // There could be one or more flags, e.g., 'MYSQLI_CLIENT_SSL | MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT'
517 $tmp_flags = explode( '|', $nfw_['match'][1] );
518 foreach( $tmp_flags as $tmp_flag ) {
519 $tmp_flag = trim( $tmp_flag );
520 if ( isset( $available_flags[$tmp_flag] ) ) {
521 $nfw_['MYSQL_CLIENT_FLAGS'] += $available_flags[$tmp_flag];
522 }
523 }
524 }
525 }
526 }
527 fclose($nfw_['fh']);
528 unset($wp_config);
529 if (! isset($nfw_['DB_NAME']) || ! isset($nfw_['DB_USER']) || ! isset($nfw_['DB_PASSWORD']) || ! isset($nfw_['DB_HOST']) || ! isset($nfw_['table_prefix']) ) {
530 return 3;
531 }
532
533 /**
534 * Parse hostname/port and socket.
535 */
536 require_once __DIR__ .'/class-nfw-database.php';
537 $host_data = NinjaFirewall_fwdatabase::parse_db_host( $nfw_['DB_HOST'] );
538 if ( $host_data ) {
539 list( $nfw_['DB_HOST'], $nfw_['port'], $nfw_['socket'] ) = $host_data;
540 }
541
542 // Make sure mysqli extension is loaded
543 if (! function_exists( 'mysqli_real_connect' ) ) {
544 return 14;
545 }
546 @$nfw_['mysqli'] = mysqli_init();
547 @mysqli_real_connect( $nfw_['mysqli'], $nfw_['DB_HOST'], $nfw_['DB_USER'], $nfw_['DB_PASSWORD'], $nfw_['DB_NAME'], $nfw_['port'], $nfw_['socket'], $nfw_['MYSQL_CLIENT_FLAGS'] );
548 if ($nfw_['mysqli']->connect_error) {
549 return 4;
550 }
551
552 return true;
553 }
554
555 // =====================================================================
556 // Fetch rules and options.
557
558 function nfw_get_data( $what ) {
559
560 global $nfw_;
561
562 if ( $what != 'nfw_rules' ) {
563 $what = 'nfw_options';
564 }
565
566 // WP API
567 if ( isset( $nfw_['wp_waf'] ) && $nfw_['wp_waf'] == 2 ) {
568 if ( is_multisite() ) {
569 $nfw_[ $what ] = get_site_option( $what );
570 } else {
571 $nfw_[ $what ] = get_option( $what );
572 }
573 return true;
574
575 // DB
576 } else {
577 // Rules
578 if ( $what == 'nfw_rules' ) {
579 if (! $nfw_['result'] = @$nfw_['mysqli']->query('SELECT * FROM `' . $nfw_['mysqli']->real_escape_string($nfw_['table_prefix']) . "options` WHERE `option_name` = 'nfw_rules'") ) {
580 return 7;
581 }
582 if (! $nfw_['rules'] = @$nfw_['result']->fetch_object() ) {
583 return 8;
584 }
585 if (! $nfw_['nfw_rules'] = @unserialize( $nfw_['rules']->option_value ) ) {
586 return 12;
587 }
588 // Options
589 } else {
590 /**
591 * Since PHP 8.1, MySQLi extension throws an Exception on errors
592 */
593 try {
594 $nfw_['result'] = @$nfw_['mysqli']->query('SELECT * FROM `' .
595 $nfw_['mysqli']->real_escape_string( $nfw_['table_prefix'] ) .
596 "options` WHERE `option_name` = 'nfw_options'"
597 );
598 }
599 catch ( Exception $e ) {
600 /**
601 * Maybe this is an old multisite install where the main site
602 * options table is named 'wp_1_options' instead of 'wp_options'
603 */
604 try {
605 $nfw_['result'] = @$nfw_['mysqli']->query('SELECT * FROM `' .
606 $nfw_['mysqli']->real_escape_string( $nfw_['table_prefix'] ) .
607 "1_options` WHERE `option_name` = 'nfw_options'"
608 );
609 }
610 catch ( Exception $e ) {
611 return 5;
612 }
613 /**
614 * Change the table prefix to match 'wp_1_options'
615 */
616 $nfw_['table_prefix'] = "{$nfw_['table_prefix']}1_";
617 }
618 if (! $nfw_['options'] = @$nfw_['result']->fetch_object() ) {
619 return 6;
620 }
621 if (! $nfw_['nfw_options'] = @unserialize( $nfw_['options']->option_value ) ) {
622 return 11;
623 }
624 }
625
626 // Make sure we have something or return an error
627 if ( $what == 'nfw_rules' && ! isset( $nfw_['nfw_rules']['1'] ) ) {
628 return 16;
629 } elseif ( $what == 'nfw_options' && ! isset( $nfw_['nfw_options']['enabled'] ) ) {
630 return 15;
631 }
632
633 // All good
634 return true;
635 }
636 }
637
638 // =====================================================================
639 // Check for HTTPS.
640
641 function nfw_is_https() {
642
643 // Can be defined in the .htninja:
644 if ( defined('NFW_IS_HTTPS') ) { return; }
645
646 if ( ( isset( $_SERVER['SERVER_PORT'] ) && $_SERVER['SERVER_PORT'] == 443 ) ||
647 ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') ||
648 ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ) ) {
649 define('NFW_IS_HTTPS', true);
650 } else {
651 define('NFW_IS_HTTPS', false);
652 }
653 }
654
655 // =====================================================================
656
657 function nfw_check_upload() {
658
659 if ( defined('NFW_STATUS') ) { return; }
660
661 global $nfw_;
662
663 $f_uploaded = [];
664 $f_uploaded = nfw_fetch_uploads();
665 $tmp = '';
666 if ( empty($nfw_['nfw_options']['uploads']) ) {
667 $tmp = '';
668 foreach ($f_uploaded as $key => $value) {
669 if (! $f_uploaded[$key]['name']) { continue; }
670 if ( empty( $f_uploaded[$key]['size'] ) ) { $f_uploaded[$key]['size'] = 0; }
671 $tmp .= $f_uploaded[$key]['name'] . ' (' . number_format($f_uploaded[$key]['size']) . ' bytes) ';
672 }
673 if ( $tmp ) {
674
675 $nfw_['incidentID'] = NinjaFirewall_log::write(
676 'Blocked file upload attempt',
677 rtrim( $tmp, ' '),
678 NFWLOG_CRITICAL, 0, $nfw_['nfw_options'], $nfw_['log_dir']
679 );
680 nfw_block();
681 }
682 } else {
683 foreach ($f_uploaded as $key => $value) {
684 if (! $f_uploaded[$key]['name']) { continue; }
685 if ( empty( $f_uploaded[$key]['size'] ) ) { $f_uploaded[$key]['size'] = 0; }
686 if ( $f_uploaded[$key]['size'] > 67 && $f_uploaded[$key]['size'] < 129 ) {
687 $data = file_get_contents( $f_uploaded[$key]['tmp_name'] );
688 if ( preg_match('`^X5O!P%@AP' . '\[4\\\PZX54\(P\^\)7CC\)7}\$EIC' .
689 'AR-STANDARD-ANTIVI' . 'RUS-TEST-FILE!\$H' . '\+H\*' .
690 '[\x09\x10\x13\x20\x1A]*`', $data) ) {
691
692 $nfw_['incidentID'] = NinjaFirewall_log::write(
693 'EICAR Standard Anti-Virus Test File blocked',
694 $f_uploaded[$key]['name'] .' ('. number_format($f_uploaded[$key]['size']) .' bytes)',
695 NFWLOG_CRITICAL, 0, $nfw_['nfw_options'], $nfw_['log_dir']
696 );
697 nfw_block();
698 }
699 }
700
701 if (! defined('NFW_NO_MIMECHECK') && isset( $f_uploaded[$key]['type'] ) && ! preg_match('/\/.*\bphp\d?\b/i', $f_uploaded[$key]['type']) &&
702 preg_match('/\.ph(?:p([34x7]|5\d?)?|t(ml)?)(?:\.|$)/', $f_uploaded[$key]['name']) ) {
703
704 $nfw_['incidentID'] = NinjaFirewall_log::write(
705 'Blocked file upload attempt (MIME-type mismatch)',
706 "{$f_uploaded[$key]['name']} != {$f_uploaded[$key]['type']}",
707 NFWLOG_CRITICAL, 0, $nfw_['nfw_options'], $nfw_['log_dir']
708 );
709 nfw_block();
710 }
711
712 if (! empty($nfw_['nfw_options']['sanitise_fn']) ) {
713 if ( empty( $nfw_['nfw_options']['substitute'] ) ) {
714 $nfw_['nfw_options']['substitute'] = 'X';
715 }
716 $tmp = '';
717 $f_uploaded_name = $f_uploaded[$key]['name'];
718 $f_uploaded[$key]['name'] = preg_replace('/[^\w\.\-]/i', $nfw_['nfw_options']['substitute'], $f_uploaded[$key]['name'], -1, $count);
719
720 // Sanitize double (or more) extensions (e.g., foo.php.gif => foo.php_.gif)
721 $ret = [];
722 $ret = nfw_sanitize_extensions( $f_uploaded[$key]['name'], $nfw_['nfw_options']['substitute'] );
723 if (! empty( $ret['count'] ) ) {
724 $count += $ret['count'];
725 $f_uploaded[$key]['name'] = $ret['name'];
726 }
727
728 if ($count) {
729 $tmp = ' (sanitising '. $count . ' char. from filename)';
730 $_FILES = nfw_sanitize_filename( $_FILES, $f_uploaded_name, $f_uploaded[$key]['name'] );
731 }
732
733 }
734
735 if (! isset( $f_uploaded[$key]['size'] ) ) {
736 $size = 'n/a';
737 } else {
738 $size = number_format( $f_uploaded[$key]['size'] );
739 }
740
741 $nfw_['incidentID'] = NinjaFirewall_log::write(
742 'File upload detected, no action taken' . $tmp ,
743 "{$f_uploaded[$key]['name']} ($size bytes)",
744 NFWLOG_UPLOAD, 0, $nfw_['nfw_options'], $nfw_['log_dir']
745 );
746 }
747 }
748 }
749
750 // =====================================================================
751
752 function nfw_fetch_uploads() {
753
754 global $file_buffer, $upload_array, $prop_key;
755 $upload_array = [];
756
757 foreach( $_FILES as $f_key => $f_value ) {
758
759 foreach( $f_value as $prop_key => $prop_value ) {
760
761 // Fetch all but 'error':
762 if (! in_array( $prop_key, ['name', 'type', 'tmp_name', 'size'] ) ) { continue; }
763
764 $file_buffer = $f_key;
765
766 if ( is_array( $_FILES[$f_key][$prop_key] ) ) {
767 nfw_recursive_upload( $_FILES[$f_key][$prop_key] );
768 } else {
769 if (! empty( $_FILES[$f_key][$prop_key] ) ) {
770 $upload_array[$f_key][$prop_key] = $_FILES[$f_key][$prop_key];
771 }
772 }
773 }
774 }
775 return $upload_array;
776 }
777
778 // =====================================================================
779
780 function nfw_recursive_upload( $data ) {
781
782 global $file_buffer, $upload_array, $prop_key;
783
784 foreach( $data as $data_key => $data_value ) {
785 if ( is_array( $data_value ) ) {
786 $file_buffer .= "_{$data_key}";
787 nfw_recursive_upload( $data_value );
788 } else {
789 if ( empty( $data_value ) ) { continue; }
790 $upload_array["{$file_buffer}_{$data_key}"][$prop_key] = $data_value;
791 }
792 }
793 }
794
795 // =====================================================================
796
797 function nfw_sanitize_filename( $array, $key, $value ) {
798
799 array_walk_recursive(
800 $array, function( &$v, $k ) use ( $key, $value ) {
801 if (! empty( $v ) && $v == $key ) { $v = $value; }
802 }
803 );
804 return $array;
805 }
806
807 function nfw_sanitize_extensions( $filename, $subs ) {
808
809 $ret = [];
810 $ret['count'] = 0;
811 $parts = explode( '.', $filename );
812 $ret['name'] = array_shift( $parts );
813 $extension = array_pop( $parts );
814 foreach ( $parts as $part ) {
815 if (! empty( $part ) ) {
816 $ret['name'] .= ".{$part}{$subs}";
817 ++$ret['count'];
818 }
819 }
820 if ( $extension ) {
821 $ret['name'] .= ".{$extension}";
822 }
823 return $ret;
824 }
825 // =====================================================================
826
827 function nfw_check_admin_request() {
828
829 global $nfw_;
830
831 if ( isset( $nfw_['nfw_rules']['999'] ) ) {
832 $nfw_['adm_rules'] = [];
833 foreach ( $nfw_['nfw_rules']['999'] as $key => $value ) {
834 if ( empty( $nfw_['nfw_rules'][$key]['ena'] ) ) { continue; }
835 $nfw_['adm_rules'][$key] = $nfw_['nfw_rules'][$key];
836 }
837 if (! empty( $nfw_['adm_rules'] ) ) {
838 nfw_check_request( $nfw_['adm_rules'], $nfw_['nfw_options'] );
839 }
840 }
841 }
842
843 // =====================================================================
844
845 function nfw_check_request( $nfw_rules, $nfw_options ) {
846
847 if ( defined('NFW_STATUS') ) { return; }
848
849 global $nfw_, $HTTP_RAW_POST_DATA;
850
851 foreach ( $nfw_rules as $id => $rules ) {
852
853 if ( empty( $rules['ena']) ) { continue; }
854
855 $wherelist = explode('|', $rules['cha'][1]['whe']);
856
857 foreach ($wherelist as $where) {
858
859 if ( nfw_disabled_scan( $where, $nfw_options ) ) { continue; }
860
861 // =================================================================
862 if ( $where == 'RAW' ) {
863 if (! isset( $HTTP_RAW_POST_DATA ) ) {
864 @$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
865 }
866
867 if ( nfw_matching( 'RAW', $_SERVER['REQUEST_METHOD'], $nfw_rules, $rules, 1, $id, $nfw_options, $HTTP_RAW_POST_DATA ) ) {
868 nfw_check_subrule( 'RAW', $_SERVER['REQUEST_METHOD'], $nfw_rules, $nfw_options, $rules, $id );
869 }
870 continue;
871 }
872
873 // =================================================================
874 if ( $where == 'POST' || $where == 'GET' || $where == 'COOKIE' ||
875 $where == 'SERVER' || $where == 'REQUEST' || $where == 'FILES' ||
876 $where == 'SESSION'
877 ) {
878
879 if (! isset( $GLOBALS['_'. $where ] ) ) { continue; }
880
881 foreach ($GLOBALS['_' . $where] as $key => $val) {
882
883 if ( nfw_matching( $where, $key, $nfw_rules, $rules, 1, $id, $nfw_options ) ) {
884 nfw_check_subrule( $where, $key, $nfw_rules, $nfw_options, $rules, $id );
885 }
886
887 }
888 continue;
889 }
890
891 // =================================================================
892
893 if ( isset( $_SERVER[$where] ) ) {
894
895 if ( nfw_matching( 'SERVER', $where, $nfw_rules, $rules, 1, $id, $nfw_options ) ) {
896 nfw_check_subrule( 'SERVER', $where, $nfw_rules, $nfw_options, $rules, $id );
897 }
898 continue;
899 }
900
901 // =================================================================
902
903 $w = explode(':', $where);
904
905 // Look for temp hash
906 if ( isset( $rules['cha'][1]['tmp'] ) && isset( $w[1] ) ) {
907 $w[1] = @nfw_check_temp_hash( $w[0], $w[1] );
908 }
909
910 if ( empty($w[1]) || ! isset( $GLOBALS['_'.$w[0]][$w[1]] ) || nfw_disabled_scan( $w[0], $nfw_options ) ) {
911 continue;
912 }
913
914 if ( nfw_matching( $w[0], $w[1], $nfw_rules, $rules, 1, $id, $nfw_options ) ) {
915 nfw_check_subrule( $w[0], $w[1], $nfw_rules, $nfw_options, $rules, $id );
916 }
917
918 // =================================================================
919
920 }
921
922 }
923
924 }
925
926 // =====================================================================
927 // Check hash found in a temporary rule (used for hotfix, 0-day etc).
928
929 function nfw_check_temp_hash( $where, $what ) {
930
931 global $nfw_;
932
933 if (is_array( $GLOBALS["_{$where}"] ) && ! empty( $GLOBALS["_{$where}"] ) ) {
934 // Loop
935 foreach( $GLOBALS["_{$where}"] as $key => $value ) {
936 if ( is_string( $key ) ) {
937 // Search in the cache
938 if ( isset( $nfw_['hash'][$key] ) ) {
939 if ( $nfw_['hash'][$key] == $what ) {
940 return $key;
941 }
942 } else {
943 // Save it to the cache
944 $nfw_['hash'][$key] = md5( substr_replace( $key, 'nfw', 2, 0 ) );
945 if ( $nfw_['hash'][$key] == $what ) {
946 return $key;
947 }
948 }
949 }
950 }
951 }
952 return $what;
953 }
954
955 // =====================================================================
956
957 function nfw_check_subrule( $w0, $w1, $nfw_rules, $nfw_options, $rules, $id ) {
958
959 if ( isset( $rules['cha'][1]['cap'] ) ) {
960 nfw_matching( $w0, $w1, $nfw_rules, $rules, 2, $id, $nfw_options );
961
962 } else {
963 $w = explode(':', $rules['cha'][2]['whe']);
964
965 if (! isset( $w[1] ) ) {
966
967 if ( $w[0] == 'RAW' ) {
968 if ( nfw_disabled_scan( 'POST', $nfw_options) && $_SERVER['REQUEST_METHOD'] == 'POST' ) {
969 return;
970 }
971 global $HTTP_RAW_POST_DATA;
972 if (! isset( $HTTP_RAW_POST_DATA ) ) {
973 @$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
974 }
975 nfw_matching( $_SERVER['REQUEST_METHOD'], 'RAW', $nfw_rules, $rules, 2, $id, $nfw_options, $HTTP_RAW_POST_DATA );
976 return;
977 }
978 $w[2] = $w[1] = $w[0];
979 $w[0] = 'SERVER';
980 } else {
981 $w[2] = null;
982
983 // Look for temp hash
984 if ( isset( $rules['cha'][2]['tmp'] ) ) {
985 $w[1] = @nfw_check_temp_hash( $w[0], $w[1] );
986 }
987 }
988
989 if (! isset( $GLOBALS['_'.$w[0]][$w[1]] ) ) {
990 return;
991 }
992
993 if ( nfw_disabled_scan( $w[0], $nfw_options, $w[2] ) ) {
994 return;
995 } else {
996 nfw_matching( $w[0], $w[1], $nfw_rules, $rules, 2, $id, $nfw_options);
997 }
998 }
999
1000 }
1001
1002 // =====================================================================
1003
1004 function nfw_disabled_scan( $where, $nfw_options, $extra = null ) {
1005
1006 if ( $extra ) { $where = $extra; }
1007
1008 if ( $where == 'POST' && empty($nfw_options['post_scan']) ||
1009 $where == 'GET' && empty($nfw_options['get_scan']) ||
1010 $where == 'COOKIE' && empty($nfw_options['cookies_scan']) ||
1011 $where == 'HTTP_USER_AGENT' && empty($nfw_options['ua_scan']) ||
1012 $where == 'HTTP_REFERER' && empty($nfw_options['referer_scan'])
1013 ) {
1014 return 1;
1015 }
1016 return 0;
1017 }
1018
1019 // =====================================================================
1020
1021 function nfw_matching( $where, $key, $nfw_rules, $rules, $subid, $id, $nfw_options, $RAW_POST = null ) {
1022
1023 global $nfw_;
1024
1025 if ( isset( $RAW_POST ) ) {
1026 $val = $RAW_POST;
1027 } else {
1028 $val = $GLOBALS['_'.$where][$key];
1029 }
1030
1031 /**
1032 * Check if the user has the required capability, if any.
1033 */
1034 if ( isset( $rules['cpb'] ) ) {
1035 $allcaps = NinjaFirewall_session::read('allcaps');
1036 if (! empty( $allcaps ) ) {
1037 $caps = explode('|', $rules['cpb'] );
1038 foreach( $caps as $cap ) {
1039 if ( isset( $allcaps[$cap] ) ) {
1040 return 0;
1041 }
1042 }
1043 }
1044 }
1045
1046 if ( is_array($val) ) {
1047 if ( isset( $nfw_['flattened'][$where][$key] ) ) {
1048 $val = $nfw_['flattened'][$where][$key];
1049 } else {
1050 $val = nfw_flatten( ' ', $val );
1051 $nfw_['flattened'][$where][$key] = $val;
1052 }
1053 }
1054
1055 if ( $where == 'POST' && ! empty($nfw_options['post_b64']) && ! isset($nfw_['b64'][$where][$key]) && $val ) {
1056 nfw_check_b64($key, $val);
1057 $nfw_['b64'][$where][$key] = 1;
1058 }
1059
1060 $transform = 1;
1061 // NF < 4.1.1:
1062 if ( isset( $rules['cha'][$subid]['exe'] ) ) {
1063 $transform = 0;
1064 if ( function_exists( $rules['cha'][$subid]['exe'] ) ) {
1065 $val = @$rules['cha'][$subid]['exe']( $val );
1066 }
1067 }
1068 // NF >= 4.1.1:
1069 if ( isset( $rules['cha'][$subid]['exm'] ) ) {
1070 $transform = 0;
1071 $exe = explode( '|', $rules['cha'][$subid]['exm'] );
1072 foreach ( $exe as $f ) {
1073 if (! function_exists( $f ) ) { break; }
1074 $val = @$f( $val );
1075 }
1076 }
1077
1078 $t = '';
1079
1080 if ( isset( $rules['cha'][$subid]['nor'] ) ) {
1081 $t .= 'N';
1082 if ( isset( $nfw_[$t][$where][$key] ) && $transform ) {
1083 $val = $nfw_[$t][$where][$key];
1084 } else {
1085 $val = nfw_normalize( $val, $nfw_rules );
1086 if ( $transform ) {
1087 $nfw_[$t][$where][$key] = $val;
1088 }
1089 }
1090 }
1091
1092 if ( isset( $rules['cha'][$subid]['tra'] ) ) {
1093 $t .= 'T' . $rules['cha'][$subid]['tra'];
1094 if ( isset( $nfw_[$t][$where][$key] ) && $transform ) {
1095 $val = $nfw_[$t][$where][$key];
1096 } else {
1097 $val = nfw_transform_string( $val, $rules['cha'][$subid]['tra'] );
1098 if ( $transform ) {
1099 $nfw_[$t][$where][$key] = $val;
1100 }
1101 }
1102 }
1103 if ( empty( $rules['cha'][$subid]['noc']) ) {
1104 $t .= 'C';
1105 if ( isset( $nfw_[$t][$where][$key] ) && $transform ) {
1106 $val = $nfw_[$t][$where][$key];
1107 } else {
1108 $val = nfw_compress_string( $val );
1109 if ( $transform ) {
1110 $nfw_[$t][$where][$key] = $val;
1111 }
1112 }
1113 }
1114
1115 if ( nfw_operator( $val, $rules['cha'][$subid]['wha'], $rules['cha'][$subid]['ope'] ) ) {
1116 if ( isset( $rules['cha'][$subid+1]) ) {
1117 return 1;
1118 } else {
1119 if ( isset( $nfw_['flattened'][$where][$key] ) ) {
1120
1121 $nfw_['incidentID'] = NinjaFirewall_log::write(
1122 $rules['why'],
1123 "$where:$key = {$nfw_['flattened'][$where][$key]}",
1124 $rules['lev'], $id, $nfw_['nfw_options'], $nfw_['log_dir']
1125 );
1126 } elseif ( isset( $RAW_POST ) ) {
1127
1128 $nfw_['incidentID'] = NinjaFirewall_log::write(
1129 $rules['why'],
1130 "$where:$key = $RAW_POST",
1131 $rules['lev'], $id, $nfw_['nfw_options'], $nfw_['log_dir']
1132 );
1133 } else {
1134
1135 $nfw_['incidentID'] = NinjaFirewall_log::write(
1136 $rules['why'],
1137 "$where:$key = {$GLOBALS['_'.$where][$key]}",
1138 $rules['lev'], $id, $nfw_['nfw_options'], $nfw_['log_dir']
1139 );
1140 }
1141 nfw_block();
1142 }
1143 }
1144 return 0;
1145 }
1146
1147 // =====================================================================
1148
1149 function nfw_operator( $val, $what, $op ) {
1150
1151 if (! $val ) { return false; }
1152
1153 if ( $op == 2 ) {
1154 if ( $val != $what ) {
1155 return true;
1156 }
1157 } elseif ( $op == 3 ) {
1158 if ( strpos($val, $what) !== FALSE ) {
1159 return true;
1160 }
1161 } elseif ( $op == 4 ) {
1162 if ( stripos($val, $what) !== FALSE ) {
1163 return true;
1164 }
1165 } elseif ( $op == 5 ) {
1166 if ( preg_match("`$what`", $val ) ) {
1167 return true;
1168 }
1169 } elseif ( $op == 6 ) {
1170 if (! preg_match("`$what`", $val) ) {
1171 return true;
1172 }
1173 } elseif ( $op == 7 ) {
1174 return true;
1175
1176 } elseif ( $op == 8 ) {
1177 if ( strpos($val, $what) === FALSE ) {
1178 return true;
1179 }
1180 } elseif ( $op == 9 ) {
1181 if ( stripos($val, $what) === FALSE ) {
1182 return true;
1183 }
1184 } else {
1185 if ( $val == $what ) {
1186 return true;
1187 }
1188 }
1189 }
1190
1191 // =====================================================================
1192
1193 function nfw_normalize( $string, $nfw_rules ) {
1194
1195 if ( empty( $string ) ) {
1196 return;
1197 }
1198
1199 $norm = rawurldecode( $string );
1200 if ( strpos( $norm, '%' ) !== false ) {
1201 $norm = rawurldecode( $norm );
1202 }
1203 if (! $norm ) {
1204 return $string;
1205 }
1206
1207 if ( preg_match('/&(?:#x(?:00)*[0-9a-f]{2}|#0*[12]?[0-9]{2}|amp|[lg]t|nbsp|quot)(?!;|\d)/i', $norm) ) {
1208 $norm = preg_replace('/&(#x(?:00)*[0-9a-f]{2}|#0*[12]?[0-9]{2}|amp|[lg]t|nbsp|quot)(?!;|\d)/i', '&\1;', $norm);
1209 if (! $norm ) {
1210 return $string;
1211 }
1212 }
1213
1214 if ( preg_match('/\\\(?:0?[4-9][0-9]|1[0-7][0-9])/', $norm) ) {
1215 $norm = preg_replace_callback('/\\\(0?[4-9][0-9]|1[0-7][0-9])/', 'nfw_oct2ascii', $norm);
1216 if (! $norm ) {
1217 return $string;
1218 }
1219 }
1220
1221 if ( preg_match('/\\\x[a-f0-9]{2}/i', $norm) ) {
1222 $norm = preg_replace_callback('/\\\x([a-f0-9]{2})/i', 'nfw_hex2ascii', $norm);
1223 if (! $norm ) {
1224 return $string;
1225 }
1226 }
1227
1228 $norm = nfw_html_decode( $norm );
1229 if (! $norm ) {
1230 return $string;
1231 }
1232
1233 if ( preg_match('/&#x?[0-9a-f]+;/i', $norm) ) {
1234 $norm = preg_replace('/(&#x?[0-9a-f]+;)/i', '', $norm);
1235 if (! $norm ) {
1236 return $string;
1237 }
1238 }
1239
1240 if ( preg_match( '/(?:%|\\\)u(?:[0-9a-f]{4}|\{0*[0-9a-f]{2}\})/i', $norm ) ) {
1241 $norm = preg_replace_callback('/(?:%|\\\)u(?:([0-9a-f]{4})|\{0*([0-9a-f]{2})\})/i', 'nfw_udecode', $norm);
1242 if (! $norm ) {
1243 return $string;
1244 }
1245 }
1246
1247 if ( empty( $nfw_rules[2]['ena'] ) ) {
1248 $norm = preg_replace('/\x0|%00/', '', $norm);
1249 if (! $norm ) {
1250 return $string;
1251 }
1252 }
1253
1254 return $norm;
1255 }
1256
1257 // =====================================================================
1258
1259 function nfw_html_decode( $norm ) {
1260
1261 global $nfw_;
1262
1263 $nfw_['entity_in'] = array (
1264 '&Tab;','&NewLine;','&excl;','&quot;','&QUOT;','&num;','&dollar;',
1265 '&percnt;','&amp;','&AMP;','&apos;','&lpar;','&rpar;','&ast;',
1266 '&midast;','&plus;','&comma;','&period;','&sol;','&colon;','&semi;',
1267 '&lt;','&LT;','&equals;','&gt;','&GT;','&quest;','&commat;','&lsqb;',
1268 '&lbrack;','&bsol;','&rsqb;','&rbrack;','&Hat;','&lowbar;','&grave;',
1269 '&DiacriticalGrave;','&lcub;','&lbrace;','&verbar;','&vert;','&VerticalLine;',
1270 '&rcub;','&rbrace;','&nbsp;','&NonBreakingSpace;','&nvlt;','&nvgt;',"\xa0"
1271 );
1272
1273 $nfw_['entity_out'] = array (
1274 '','','!','"','"','#','$','%','&','&',"'",'(',')','*','*','+',',','.','/',
1275 ':',';','<','<','=','>','>','?','@','[','[','\\',']',']','^','_','`','`',
1276 '{','{','|','|','|','}','}',' ',' ','','',' '
1277 );
1278
1279 $normout = str_replace( $nfw_['entity_in'], $nfw_['entity_out'], $norm);
1280 $normout = html_entity_decode( $normout, ENT_QUOTES, 'UTF-8' );
1281
1282 return $normout;
1283
1284 }
1285
1286 // =====================================================================
1287
1288 function nfw_compress_string( $string, $where = null ) {
1289
1290 if (! $string ) { return; }
1291
1292 if ( $where == 1 ) {
1293 $replace = ' ';
1294 } else {
1295 $replace = '';
1296 }
1297
1298 $string = str_replace( ["\x09", "\x0a","\x0b", "\x0c", "\x0d"],
1299 $replace, $string);
1300 $string = trim ( preg_replace('/\x20{2,}/', ' ', $string) );
1301 return $string;
1302
1303 }
1304
1305 // =====================================================================
1306
1307 function nfw_transform_string( $string, $where ) {
1308
1309 if (! $string ) { return; }
1310
1311 if ( $where == 1 ) {
1312 $norm = trim( preg_replace_callback('((^([^a-z/&|#]*)|([\'"])(?:\\\\.|[^\n\3\\\\])*?\3|(?:[0-9a-z_$]+)|.)'.
1313 '(?:\s|--[^\n]*+\n|/\*(?:[^*!]|\*(?!/))*+\*/)*'.
1314 '(?:(?:\#|--(?:[\x00-\x20\x7f]|$)|/\*$)[^\n]*+\n|/\*!(?:\d{5})?|\*/|/\*(?:[^*!]|\*(?!/))*+\*/)*)si',
1315 'nfw_delcomments1', $string . "\n") );
1316 $norm = preg_replace('/[\'"]\x20*\+?\x20*[\'"]/', '', $norm);
1317 $norm = strtolower( str_replace( ['+', "'", '"', "(", ')', '`', ',', ';'], ' ', $norm) );
1318
1319 } elseif ( $where == 2 ) {
1320 $norm = trim( preg_replace_callback('((^|([\'"])(?:\\\\.|[^\n\2\\\\])*?\2|(?:[0-9a-z_$]+)|.)'.
1321 '(?://[^\n]*+\n|/\*(?:[^*]|\*(?!/))*+\*/)*)si',
1322 'nfw_delcomments2', $string . "\n") );
1323 $norm = preg_replace(
1324 ['/[\n\r\t\f\v]/', '`/\*\s*\*/`', '/[\'"`]\x20*[+.]?\x20*[\'"`]/'],
1325 ['', ' ', ''],
1326 $norm
1327 );
1328 } elseif ( $where == 3 ) {
1329 $norm = preg_replace(
1330 ['`([\\\"\'^]|\$\w+)`', '`([,;]|\s+)`'],
1331 ['', ' '],
1332 $string
1333 );
1334 $norm = preg_replace(
1335 ['`/(\./)+`','`/{2,}`', '`/(.+?)/\.\./\1\b`', '`\n`', '`\\\`'],
1336 ['/', '/', '/\1', '', ''],
1337 $norm
1338 );
1339 }
1340
1341 return $norm;
1342
1343 }
1344
1345 // =====================================================================
1346
1347 function nfw_delcomments1 ( $match ) {
1348
1349 if (! empty($match[2]) ) { return ' '; }
1350 if ( $match[0] != $match[1] ) {
1351 return $match[1]. ' ';
1352 }
1353 return $match[1];
1354
1355 }
1356
1357 function nfw_delcomments2 ( $match ) {
1358
1359 if ( $match[0] != $match[1] ) {
1360 return $match[1]. ' ';
1361 }
1362 return $match[1];
1363
1364 }
1365
1366 // ===================================================================== 2023-05-16
1367
1368 function nfw_udecode( $match ) {
1369
1370 if ( isset( $match[2] ) ) {
1371 return @json_decode('"\\u00'.$match[2].'"');
1372 }
1373 return @json_decode('"\\u'.$match[1].'"');
1374
1375 }
1376
1377 // ===================================================================== 2023-05-16
1378
1379 function nfw_oct2ascii( $match ) {
1380
1381 return chr( octdec( $match[1] ) );
1382
1383 }
1384
1385 // ===================================================================== 2023-05-16
1386
1387 function nfw_hex2ascii( $match ) {
1388
1389 return chr( hexdec( $match[1] ) );
1390
1391 }
1392
1393 // ===================================================================== 2023-05-16
1394 // Flatten an array.
1395
1396 function nfw_flatten( $glue, $pieces ) {
1397
1398 if ( defined('NFW_STATUS') ) {
1399 return;
1400 }
1401
1402 $ret = [];
1403
1404 foreach ( $pieces as $r_pieces ) {
1405 if ( is_array( $r_pieces ) ) {
1406 $ret[] = nfw_flatten( $glue, $r_pieces );
1407 } else {
1408 if (! empty( $r_pieces ) ) {
1409 $ret[] = $r_pieces;
1410 }
1411 }
1412 }
1413 return implode( $glue, $ret );
1414 }
1415
1416 // =====================================================================
1417
1418 function nfw_check_b64( $key, $string ) {
1419
1420 if ( defined('NFW_STATUS') || strlen( $string ) < 4 ) {
1421 return;
1422 }
1423
1424 global $nfw_;
1425
1426 $whitelist = [
1427 'fpd_print_order', // Fancy Product Designer
1428 'g-recaptcha-response' // reCAPTCHA
1429 ];
1430 if ( in_array( $key, $whitelist ) ) {
1431 return;
1432 }
1433
1434 $decoded = base64_decode( $string );
1435 if ( strlen($decoded) < 4 ) {
1436 return;
1437 }
1438
1439 if ( preg_match( '`\b(?:\$?_(COOKIE|ENV|FILES|(?:GE|POS|REQUES)T|SE(RVER|SSION))|HTTP_(?:(?:POST|GET)_VARS|RAW_POST_DATA)|GLOBALS)\s*[=\[)]|\b(?i:array_map|assert|base64_(?:de|en)code|chmod|curl_exec|(?:ex|im)plode|error_reporting|eval|file(?:_get_contents)?|f(?:open|write|close)|fsockopen|function_exists|gzinflate|md5|move_uploaded_file|ob_start|passthru|[ep]reg_replace|phpinfo|stripslashes|strrev|(?:shell_)?exec|substr|system|unlink)\s*\(|[\s;]echo\s*[\'"]|<(?i:applet|embed|i?frame(?:set)?|marquee|object|script)\b|\W\$\{\s*[\'"]\w+[\'"]|<\?(?i:php|=)\s|(?i:(?:\b|\d)select\b.+?from\b.+?(?:\b|\d)where|(?:\b|\d)insert\b.+?into\b|(?:\b|\d)union\b.+?(?:\b|\d)select\b|(?:\b|\d)update\b.+?(?:\b|\d)set\b)|^.{0,25}[;{}]?\b[OC]:\d+:"[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*":\d+:{.*?}`', $decoded ) ) {
1440 // JetPack
1441 if ( $key === 'args' && ! defined('NFW_WPWAF') &&
1442 preg_match( '/^{"query":"SELECT/', $decoded ) &&
1443 strpos($_SERVER['SCRIPT_NAME'], '/jetpack-temp/jp-helper-') !== FALSE ) {
1444 return;
1445 }
1446
1447 $nfw_['incidentID'] = NinjaFirewall_log::write(
1448 'BASE64-encoded injection',
1449 "POST:$key = $string",
1450 NFWLOG_CRITICAL, 0, $nfw_['nfw_options'], $nfw_['log_dir']
1451 );
1452 nfw_block();
1453 }
1454 }
1455
1456 // =====================================================================
1457
1458 function nfw_sanitise( $str, $how, $msg ) {
1459
1460 if ( defined('NFW_STATUS') ) { return; }
1461
1462 if ( empty($str) ) { return $str; }
1463
1464 global $nfw_;
1465
1466 if (is_string($str) ) {
1467
1468 if ($how == 1) {
1469 // Full WAF
1470 if (! empty( $nfw_['mysqli'] ) ) {
1471 $str2 = $nfw_['mysqli']->real_escape_string($str);
1472 // WP WAF
1473 } else {
1474 global $wpdb;
1475 $str2 = @$wpdb->_real_escape($str);
1476 }
1477 $str2 = str_replace( ['`', '<', '>'], ['\\`', '&lt;', '&gt;'], $str2);
1478 if ( $msg == 'GET' && strpos( $str2, '/') !== false ) {
1479 $str2 = str_replace( ['*', '?'], ['\*', '\?'], $str2 );
1480 }
1481 } elseif ($how == 2) {
1482 $str2 = str_replace( ['\\', "'", '"', "\x0d", "\x0a", "\x00", "\x1a", '`', '<', '>'],
1483 ['\\\\', "\\'", '\\"', '-', '-', '-', '-', '\\`', '&lt;', '&gt;'], $str);
1484 } else {
1485 $str2 = str_replace( ['\\', "'", "\x00", "\x1a", '`', '<'],
1486 ['\\\\', "\\'", '-', '-', '\\`', '&lt;'], $str);
1487 }
1488 if (! empty($nfw_['nfw_options']['debug']) ) {
1489 if ($str2 != $str) {
1490
1491 $nfw_['incidentID'] = NinjaFirewall_log::write(
1492 'Sanitising user input',
1493 "$msg: $str",
1494 NFWLOG_DEBUG, 0, $nfw_['nfw_options'], $nfw_['log_dir'] // '7' for debugging mode only
1495 );
1496 }
1497 return $str;
1498 }
1499 if ($str2 != $str) {
1500
1501 $nfw_['incidentID'] = NinjaFirewall_log::write(
1502 'Sanitising user input',
1503 "$msg: $str",
1504 NFWLOG_INFO, 0, $nfw_['nfw_options'], $nfw_['log_dir']
1505 );
1506 }
1507 return $str2;
1508
1509 } else if (is_array($str) ) {
1510 foreach($str as $key => $value) {
1511 if ($how == 3) {
1512 $key2 = str_replace( ['\\', "'", "\x00", "\x1a", '`', '<', '>'],
1513 ['\\\\', "\\'", '-', '-', '\\`', '&lt;', '&gt;'], $key, $occ);
1514 } else {
1515 $key2 = str_replace( ['\\', "'", '"', "\x0d", "\x0a", "\x00", "\x1a", '`', '<', '>'],
1516 ['\\\\', "\\'", '\\"', '-', '-', '-', '-', '&#96;', '&lt;', '&gt;'], $key, $occ);
1517 }
1518 if ($occ) {
1519 unset($str[$key]);
1520
1521 $nfw_['incidentID'] = NinjaFirewall_log::write(
1522 'Sanitising user input',
1523 "$msg: $key",
1524 NFWLOG_INFO, 0, $nfw_['nfw_options'], $nfw_['log_dir']
1525 );
1526 }
1527 $str[$key2] = nfw_sanitise($value, $how, $msg);
1528 }
1529 return $str;
1530 }
1531 }
1532
1533 // ===================================================================== 2023-05-16
1534 // Block the user and display a message.
1535
1536 function nfw_block() {
1537
1538 if ( defined('NFW_STATUS') ) {
1539 return;
1540 }
1541
1542 global $nfw_;
1543
1544 if (! empty( $nfw_['nfw_options']['debug'] ) ) {
1545 return;
1546 }
1547
1548 $http_codes = [
1549 400 => '400 Bad Request',
1550 403 => '403 Forbidden',
1551 404 => '404 Not Found',
1552 406 => '406 Not Acceptable',
1553 418 => "418 I'm a teapot",
1554 500 => '500 Internal Server Error',
1555 503 => '503 Service Unavailable'
1556 ];
1557 if (! isset( $http_codes[$nfw_['nfw_options']['ret_code']] ) ) {
1558 $nfw_['nfw_options']['ret_code'] = 403;
1559 }
1560
1561 if ( empty( $nfw_['incidentID'] ) ) {
1562 $nfw_['incidentID'] = '000000';
1563 }
1564
1565 $tmp = str_replace(
1566 '%%NUM_INCIDENT%%',
1567 $nfw_['incidentID'],
1568 base64_decode( $nfw_['nfw_options']['blocked_msg'] )
1569 );
1570
1571 if ( isset( $nfw_['nfw_options']['logo'] ) ) {
1572 $tmp = str_replace(
1573 '%%NINJA_LOGO%%',
1574 "<img alt='NinjaFirewall' src='{$nfw_['nfw_options']['logo']}' />",
1575 $tmp
1576 );
1577 }
1578
1579 $tmp = str_replace('%%REM_ADDRESS%%', NFW_REMOTE_ADDR, $tmp );
1580
1581 NinjaFirewall_session::delete();
1582
1583 if (! headers_sent() ) {
1584 header("HTTP/1.1 {$http_codes[$nfw_['nfw_options']['ret_code']]}" );
1585 header("Status: {$http_codes[$nfw_['nfw_options']['ret_code']]}" );
1586 header('Pragma: no-cache');
1587 header('Cache-Control: no-cache, no-store, must-revalidate');
1588 header('Expires: 0');
1589 }
1590
1591 echo "<!DOCTYPE HTML PUBLIC '-//IETF//DTD HTML 2.0//EN'><html><head>".
1592 "<title>NinjaFirewall {$http_codes[$nfw_['nfw_options']['ret_code']]}</title>".
1593 "<style>body{font-family:sans-serif;font-size:13px;color:#000;}</style>".
1594 "<meta http-equiv='Content-Type' content='text/html; charset=utf-8'></head>".
1595 "<body bgcolor='white'>$tmp</body></html>";
1596 exit;
1597 }
1598
1599 // ===================================================================== 2023-05-16
1600 // Return the time using hrtime (PHP >= 7.3) or microtime.
1601
1602 function nfw_fc_metrics( $action = 'start') {
1603
1604 if ( function_exists('hrtime') ) {
1605 $metrics = 'hrtime';
1606 } else {
1607 $metrics = 'microtime';
1608 }
1609
1610 // Start the chrono
1611 if ( $action == 'start') {
1612 return $metrics(true);
1613 }
1614
1615 global $nfw_;
1616
1617 if ( empty( $nfw_['fw_starttime'] ) ) {
1618 return 0;
1619 }
1620
1621 // Stop the chrono and return the formatted elapsed time
1622 if ( $metrics == 'hrtime') {
1623 return number_format( ( $metrics(true) - $nfw_['fw_starttime'] ) / 1000000000, 5 );
1624 } else {
1625 return number_format( $metrics(true) - $nfw_['fw_starttime'], 5 );
1626 }
1627 }
1628
1629 // =====================================================================
1630
1631 function nfw_bfd($where) {
1632
1633 if ( defined('NFW_STATUS') ) { return; }
1634
1635 global $nfw_;
1636 $bf_conf_dir = $nfw_['log_dir'] . '/cache';
1637
1638 if (! is_file($bf_conf_dir . '/bf_conf.php') ) {
1639 return;
1640 }
1641
1642 $now = time();
1643 require($bf_conf_dir . '/bf_conf.php');
1644 if ( empty($bf_enable) ) {
1645 return;
1646 }
1647
1648 if ( $where == 2 && empty($bf_xmlrpc) ) {
1649 return;
1650 }
1651
1652 // NinjaFirewall <= 3.4.2:
1653 if (! isset( $auth_msgtxt ) ) {
1654 $auth_msgtxt = $auth_msg;
1655 $b64 = 0;
1656 // NinjaFirewall > 3.4.2:
1657 } else {
1658 $b64 = 1;
1659 }
1660 // NinjaFirewall < 3.5:
1661 if (! isset( $bf_allow_bot ) ) {
1662 $bf_allow_bot = 0;
1663 }
1664 if (! isset( $bf_type ) ) {
1665 $bf_type = 0;
1666 }
1667
1668 if ( $where == 1 && $bf_allow_bot == 0 ) {
1669 nfw_is_bot( 'wp-login.php' );
1670 }
1671
1672 if ( $where == 1 && isset( $_REQUEST['action'] ) && in_array( $_REQUEST['action'], ['postpass', 'logout', 'lostpassword', 'retrievepassword', 'resetpass', 'rp', 'register', 'confirmaction'] ) ) {
1673 return;
1674 }
1675
1676 if ( $bf_enable == 2 ) {
1677 nfw_check_auth($auth_name, $auth_pass, $auth_msgtxt, $bf_rand, $b64, $bf_allow_bot, $bf_type, $captcha_text, $bf_nosig);
1678 return;
1679 }
1680
1681
1682 if ( is_file($bf_conf_dir . '/bf_blocked' . $where . $_SERVER['SERVER_NAME'] . $bf_rand) ) {
1683
1684 $mtime = filemtime( $bf_conf_dir . '/bf_blocked' . $where . $_SERVER['SERVER_NAME'] . $bf_rand );
1685 if ( ($now - $mtime) < $bf_bantime * 60 ) {
1686
1687 nfw_check_auth($auth_name, $auth_pass, $auth_msgtxt, $bf_rand, $b64, $bf_allow_bot, $bf_type, $captcha_text, $bf_nosig);
1688 return;
1689 } else {
1690
1691 @unlink($bf_conf_dir . '/bf_blocked' . $where . $_SERVER['SERVER_NAME'] . $bf_rand);
1692 }
1693 }
1694
1695
1696 if ( strpos($bf_request, $_SERVER['REQUEST_METHOD']) === false ) {
1697 return;
1698 }
1699
1700
1701 if ( is_file($bf_conf_dir . '/bf_' . $where . $_SERVER['SERVER_NAME'] . $bf_rand ) ) {
1702 $tmp_log = file( $bf_conf_dir . '/bf_' . $where . $_SERVER['SERVER_NAME'] . $bf_rand, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
1703 if ( count( $tmp_log) >= $bf_attempt ) {
1704 if ( ($tmp_log[count($tmp_log) - 1] - $tmp_log[count($tmp_log) - $bf_attempt]) <= $bf_maxtime ) {
1705
1706 $bfdh = fopen( $bf_conf_dir . '/bf_blocked' . $where . $_SERVER['SERVER_NAME'] . $bf_rand, 'w');
1707 fclose( $bfdh );
1708
1709 unlink( $bf_conf_dir . '/bf_' . $where . $_SERVER['SERVER_NAME'] . $bf_rand );
1710 $nfw_['nfw_options']['ret_code'] = '401';
1711 if ($where == 1) {
1712 $where = 'wp-login.php';
1713 } else {
1714 $where = 'XML-RPC API';
1715 }
1716 if ( $bf_type == 0 ) {
1717
1718 $nfw_['incidentID'] = NinjaFirewall_log::write(
1719 'Brute-force attack detected on ' . $where,
1720 'enabling HTTP authentication for ' . $bf_bantime . 'mn',
1721 NFWLOG_CRITICAL, 0, $nfw_['nfw_options'], $nfw_['log_dir']
1722 );
1723 } else {
1724
1725 $nfw_['incidentID'] = NinjaFirewall_log::write(
1726 'Brute-force attack detected on ' . $where,
1727 'enabling CAPTCHA for ' . $bf_bantime . 'mn',
1728 NFWLOG_CRITICAL, 0, $nfw_['nfw_options'], $nfw_['log_dir']
1729 );
1730 }
1731 /**
1732 * Write to the AUTH log.
1733 */
1734 if (! empty( $bf_authlog ) ) {
1735 if (! defined('NFW_REMOTE_ADDR') ) {
1736 NinjaFirewall_IP::check_ip( $nfw_['nfw_options'] );
1737 }
1738 if ( defined('LOG_AUTHPRIV') ) {
1739 $tmp = LOG_AUTHPRIV;
1740 } else {
1741 $tmp = LOG_AUTH;
1742 }
1743 @ openlog('ninjafirewall', LOG_NDELAY|LOG_PID, $tmp);
1744 @ syslog(LOG_INFO, 'Possible brute-force attack from '. NFW_REMOTE_ADDR .
1745 ' on '. $_SERVER['SERVER_NAME'] .' ('. $where .'). Blocking access for ' . $bf_bantime . 'mn.');
1746 @ closelog();
1747 }
1748 nfw_check_auth($auth_name, $auth_pass, $auth_msgtxt, $bf_rand, $b64, $bf_allow_bot, $bf_type, $captcha_text, $bf_nosig);
1749 return;
1750
1751 }
1752 }
1753 $mtime = filemtime( $bf_conf_dir . '/bf_' . $where . $_SERVER['SERVER_NAME'] . $bf_rand );
1754 if ( ($now - $mtime) > $bf_bantime * 60 ) {
1755 unlink( $bf_conf_dir . '/bf_' . $where . $_SERVER['SERVER_NAME'] . $bf_rand );
1756 }
1757 }
1758
1759 @file_put_contents($bf_conf_dir . '/bf_' . $where . $_SERVER['SERVER_NAME'] . $bf_rand, $now . "\n", FILE_APPEND | LOCK_EX);
1760
1761 }
1762
1763 // ===================================================================== 2023-05-16
1764 // Block the request if a bot is detected.
1765
1766 function nfw_is_bot( $block = '') {
1767
1768 global $nfw_;
1769
1770 if ( empty( $_SERVER['HTTP_ACCEPT'] ) ||
1771 empty( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ||
1772 empty( $_SERVER['HTTP_USER_AGENT'] ) ||
1773 stripos( $_SERVER['HTTP_USER_AGENT'], 'Mozilla') === FALSE ) {
1774
1775 if (! empty( $block ) ) {
1776 // Whitelist server IP and private addresses calling admin-ajax.php
1777 if ( $block == 'admin-ajax.php') {
1778 if ( NFW_REMOTE_ADDR == $_SERVER['SERVER_ADDR'] ||
1779 NFW_REMOTE_ADDR_PRIVATE == true ) {
1780
1781 return true;
1782 }
1783 $block = 'Blocked access to admin-ajax.php';
1784
1785 // No whitelist needed for the login page:
1786 } else {
1787 $block = 'Blocked access to the login page';
1788 }
1789
1790 header('HTTP/1.0 404 Not Found');
1791 header('Pragma: no-cache');
1792 header('Cache-Control: no-cache, no-store, must-revalidate');
1793 header('Expires: 0');
1794 $nfw_['nfw_options']['ret_code'] = '404';
1795
1796 $nfw_['incidentID'] = NinjaFirewall_log::write(
1797 $block,
1798 'bot detection is enabled',
1799 NFWLOG_MEDIUM, 0, $nfw_['nfw_options'], $nfw_['log_dir']
1800 );
1801 NinjaFirewall_session::delete();
1802 exit('404 Not Found');
1803 }
1804
1805 return true;
1806 }
1807 return false;
1808 }
1809
1810 // =====================================================================
1811
1812 function nfw_check_auth( $auth_name, $auth_pass, $auth_msgtxt, $bf_rand, $b64, $bf_allow_bot, $bf_type, $captcha_text, $bf_nosig ) {
1813
1814 if ( defined('NFW_STATUS') ) { return; }
1815
1816 // Prevent favicon.ico 302 redirection to the login page
1817 // due to plugins that do not handle well the login page access:
1818 if ( isset( $_GET['redirect_to'] ) && strpos( $_GET['redirect_to'], 'favicon.ico' ) !== FALSE ) {
1819 exit;
1820 }
1821
1822 NinjaFirewall_session::start();
1823
1824 global $nfw_;
1825
1826 $nfw_bfd = NinjaFirewall_session::read('nfw_bfd');
1827 if ( isset( $nfw_bfd ) && $nfw_bfd == $bf_rand ) {
1828 return;
1829 }
1830
1831 if ( $bf_type == 0 ) {
1832 // Password protection:
1833 if (! empty($_REQUEST['u']) && ! empty($_REQUEST['p']) ) {
1834 if ( $_REQUEST['u'] === $auth_name && sha1($_REQUEST['p']) === $auth_pass ) {
1835 NinjaFirewall_session::write( ['nfw_bfd' => $bf_rand ] );
1836 return;
1837 }
1838 }
1839 } else {
1840 // Make sure the GD extension is loaded
1841 if ( function_exists( 'gd_info' ) ) {
1842 // Captcha protection
1843 $nfw_bfd_c = NinjaFirewall_session::read('nfw_bfd_c');
1844 if (! empty( $_REQUEST['c'] ) && isset( $nfw_bfd_c ) ) {
1845 if ( $nfw_bfd_c == strtolower( $_REQUEST['c'] ) ) {
1846 NinjaFirewall_session::write( ['nfw_bfd' => $bf_rand ] );
1847 NinjaFirewall_session::delete('nfw_bfd_c');
1848 return;
1849 }
1850 }
1851 } else {
1852 // Return in no GD extension:
1853 return;
1854 }
1855 }
1856
1857 NinjaFirewall_session::delete();
1858
1859 if ( $b64 ) { $auth_msgtxt = base64_decode( $auth_msgtxt ); }
1860
1861 header('HTTP/1.0 401 Unauthorized');
1862 header('X-Frame-Options: SAMEORIGIN');
1863 header('Pragma: no-cache');
1864 header('Cache-Control: no-cache, no-store, must-revalidate');
1865 header('Expires: 0');
1866 if ( empty( $bf_nosig ) ) {
1867 $bf_nosig = 'Brute-force protection by NinjaFirewall';
1868 } else {
1869 $bf_nosig = '';
1870 }
1871 if ( $bf_type == 0 ) {
1872 $message = '<html><head><title>'. $bf_nosig .'</title><link rel="stylesheet" href="./wp-includes/css/buttons.min.css" type="text/css"><link rel="stylesheet" href="./wp-admin/css/login.min.css" type="text/css"><link rel="stylesheet" href="./wp-admin/css/forms.min.css" type="text/css"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body class="login wp-core-ui" style="color:#444"><div id="login"><center><h2>' . $auth_msgtxt . '</h2><form method="post"><label>'. $bf_nosig .'</label><br><br><p><input class="input" type="text" name="u" placeholder="Username" autofocus></p><p><input class="input" type="password" name="p" placeholder="Password"></p><p align="right"><input type="submit" value="Login Page&nbsp;&#187;" class="button-secondary"></p><input type="hidden" name="reauth" value="1"></form></center></div></body></html>';
1873 } else {
1874 $captcha = nfw_get_captcha();
1875 if ( $captcha === false ) {
1876 return;
1877 }
1878 $message = '<html><head><title>'. $bf_nosig .'</title><link rel="stylesheet" href="./wp-includes/css/buttons.min.css" type="text/css"><link rel="stylesheet" href="./wp-admin/css/login.min.css" type="text/css"><link rel="stylesheet" href="./wp-admin/css/forms.min.css" type="text/css"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body class="login wp-core-ui" style="color:#444"><div id="login"><center><form method="post"><p><label>'. base64_decode( $captcha_text ) .'</label></p><br><p>' . $captcha . '</p><p><input class="input" type="text" name="c" autofocus></p><p align="right"><input type="submit" value="Login Page&nbsp;&#187;" class="button-secondary"></p><input type="hidden" name="reauth" value="1"></form><br><label>'. $bf_nosig .'</label></center></div></body></html>';
1879 }
1880 if ( $bf_allow_bot == 0 ) {
1881 if ( @ini_set('zlib.output_compression','Off') !== false ) {
1882 header('Content-Encoding: gzip');
1883 echo gzencode( $message, 1 );
1884 exit;
1885 }
1886 }
1887 header('Content-Type: text/html; charset=utf-8');
1888 echo $message;
1889 exit;
1890 }
1891
1892 // =====================================================================
1893 function nfw_get_captcha() {
1894
1895 if (! function_exists( 'imagettftext' ) ) {
1896 echo "<div id='login_error'>NinjaFirewall error: PHP imagettftext() function doesn't exist, the captcha can't be displayed. Make sure PHP is compiled with freetype support (--with-freetype-dir=DIR).</div>";
1897 return false;
1898 }
1899
1900 NinjaFirewall_session::start();
1901
1902 $characters = 'AaBbCcDdEeFfGgHhiIJjKkLMmNnPpRrSsTtUuVvWwXxYyZz123456789';
1903 $captcha = '';
1904 while( strlen( $captcha ) < 5 ) {
1905 $captcha .= substr( $characters, mt_rand() % strlen( $characters ), 1 );
1906 }
1907
1908 // Background image with dimensions
1909 $image = imagecreate( 200, 60 );
1910 // Background color:
1911 imagecolorallocate( $image, 255, 255, 255 );
1912 // Text color:
1913 $text_color = imagecolorallocate( $image, 77, 77, 77 );
1914 // Font:
1915 global $nfw_;
1916 if ( is_file( "{$nfw_['log_dir']}/font.ttf" ) ) {
1917 imagettftext( $image, 35, 0, 15, 45, $text_color, "{$nfw_['log_dir']}/font.ttf", $captcha );
1918 } else {
1919 imagettftext( $image, 35, 0, 15, 45, $text_color, __DIR__ . '/share/font.ttf', $captcha );
1920 }
1921
1922 ob_start();
1923 imagepng( $image );
1924 $img_content = ob_get_contents();
1925 ob_end_clean();
1926
1927 $res = '<img src="data:image/png;base64,'. base64_encode( $img_content ) .'" />';
1928
1929 NinjaFirewall_session::write( ['nfw_bfd_c' => strtolower( $captcha ) ] );
1930
1931 return $res;
1932 }
1933
1934 // ===================================================================== 2023-05-16
1935 // Handle HTTP response headers.
1936
1937 function nfw_response_headers() {
1938
1939 if ( defined('NFW_CUSTHEADERS') ) {
1940 nfw_custom_headers();
1941 }
1942
1943 if (! defined('NFW_RESHEADERS') ) {
1944 return;
1945 }
1946
1947 $NFW_RESHEADERS = NFW_RESHEADERS;
1948 // NFW_RESHEADERS:
1949 // 0000000000
1950 // ||||||||||_ SameSite[0-2]
1951 // |||||||||__ Referrer-Policy [0-8]
1952 // ||||||||___ Content-Security-Policy (backend) [0-1]
1953 // |||||||____ Content-Security-Policy (frontend) [0-1]
1954 // ||||||_____ Strict-Transport-Security (includeSubDomains) [0-1]
1955 // |||||______ Strict-Transport-Security [0-4]
1956 // ||||_______ X-XSS-Protection [0-3]
1957 // |||________ X-Frame-Options [0-2]
1958 // ||_________ X-Content-Type-Options [0-1]
1959 // |__________ HttpOnly cookies [0-1]
1960
1961 // Force HttpOnly and/or SameSite cookie
1962 if (! empty( $NFW_RESHEADERS[0] ) || ! empty( $NFW_RESHEADERS[9] ) ) {
1963 $rewrite = [];
1964 // Parse all response headers
1965 foreach (headers_list() as $header) {
1966 // Ignore it if it is not a cookie
1967 if ( strpos( $header, 'Set-Cookie:' ) === false ) { continue; }
1968 $extra = '';
1969 // HttpOnly
1970 if (! empty( $NFW_RESHEADERS[0] ) ) {
1971 // Does it have the HttpOnly flag on
1972 if ( stripos( $header, '; HttpOnly') === false) {
1973 $extra .= '; HttpOnly';
1974 }
1975 }
1976 // SameSite
1977 if (! empty( $NFW_RESHEADERS[9] ) ) {
1978 // Lax
1979 if ( $NFW_RESHEADERS[9] == 1
1980 && stripos( $header, '; SameSite=Lax' ) === false ) {
1981
1982 $extra .= '; SameSite=Lax';
1983 // Strict
1984 } elseif ( $NFW_RESHEADERS[9] == 2
1985 && stripos( $header, '; SameSite=Strict' ) === false ) {
1986
1987 $extra .= '; SameSite=Strict';
1988 }
1989 }
1990 // Save cookie
1991 $rewrite[] = "{$header}{$extra}";
1992 }
1993
1994 // Shall we rewrite cookies
1995 if (! empty( $rewrite ) ) {
1996 // Remove all original cookies
1997 header_remove('Set-Cookie');
1998 foreach( $rewrite as $cookie ) {
1999 // Inject ours instead
2000 header( $cookie, false );
2001 }
2002 }
2003 }
2004
2005 if (! empty( $NFW_RESHEADERS[1] ) ) {
2006 header('X-Content-Type-Options: nosniff');
2007 }
2008
2009 if (! empty( $NFW_RESHEADERS[2] ) ) {
2010 if ($NFW_RESHEADERS[2] == 1) {
2011 header('X-Frame-Options: SAMEORIGIN');
2012 } else {
2013 header('X-Frame-Options: DENY');
2014 }
2015 }
2016
2017 if ( empty( $NFW_RESHEADERS[3] ) ) {
2018 header('X-XSS-Protection: 0');
2019 } elseif ( $NFW_RESHEADERS[3] == 1 ) {
2020 header('X-XSS-Protection: 1; mode=block');
2021 } elseif ( $NFW_RESHEADERS[3] == 2 ) {
2022 header('X-XSS-Protection: 1');
2023 }
2024
2025 if (! empty( $NFW_RESHEADERS[6] ) &&
2026 strpos($_SERVER['SCRIPT_NAME'], '/wp-admin/') === FALSE ) {
2027
2028 header('Content-Security-Policy: ' . CSP_FRONTEND_DATA);
2029 }
2030 if (! empty( $NFW_RESHEADERS[7] ) &&
2031 strpos($_SERVER['SCRIPT_NAME'], '/wp-admin/') !== FALSE ) {
2032
2033 header('Content-Security-Policy: ' . CSP_BACKEND_DATA);
2034 }
2035
2036 if (! empty( $NFW_RESHEADERS[8] ) ) {
2037 if ( $NFW_RESHEADERS[8] == 1 ) {
2038 $rf = 'no-referrer';
2039 } elseif ( $NFW_RESHEADERS[8] == 2 ) {
2040 $rf = 'no-referrer-when-downgrade';
2041 } elseif ( $NFW_RESHEADERS[8] == 3 ) {
2042 $rf = 'origin';
2043 } elseif ( $NFW_RESHEADERS[8] == 4 ) {
2044 $rf = 'origin-when-cross-origin';
2045 } elseif ( $NFW_RESHEADERS[8] == 5 ) {
2046 $rf = 'strict-origin';
2047 } elseif ( $NFW_RESHEADERS[8] == 6 ) {
2048 $rf = 'strict-origin-when-cross-origin';
2049 } elseif ( $NFW_RESHEADERS[8] == 7 ) {
2050 $rf = 'same-origin';
2051 } else {
2052 $rf = 'unsafe-url';
2053 }
2054 header("Referrer-Policy: $rf");
2055 }
2056
2057 // Stop here if no more headers
2058 if ( empty($NFW_RESHEADERS[4] ) ) {
2059 return;
2060 }
2061
2062 // We don't send HSTS headers over HTTP
2063 if (! defined('NFW_IS_HTTPS') ) {
2064 nfw_is_https();
2065 }
2066 if ( NFW_IS_HTTPS == false ) {
2067 return;
2068 }
2069
2070 if ($NFW_RESHEADERS[4] == 1) {
2071 // 1 month
2072 $max_age = 'max-age=2628000';
2073 } elseif ($NFW_RESHEADERS[4] == 2) {
2074 // 6 months
2075 $max_age = 'max-age=15768000';
2076 } elseif ($NFW_RESHEADERS[4] == 3) {
2077 // 12 months
2078 $max_age = 'max-age=31536000';
2079 } elseif ($NFW_RESHEADERS[4] == 4) {
2080 // Send an empty max-age to signal the UA to
2081 // cease regarding the host as a known HSTS Host
2082 $max_age = 'max-age=0';
2083 } else {
2084 // 24 months
2085 $max_age = 'max-age=63072000';
2086 }
2087 if (! empty( $NFW_RESHEADERS[5] ) ) {
2088 if ( $NFW_RESHEADERS[5] == 1 ) {
2089 $max_age .= '; includeSubDomains';
2090 } elseif ( $NFW_RESHEADERS[5] == 2 ) {
2091 $max_age .= '; preload';
2092 } else {
2093 $max_age .= '; includeSubDomains; preload';
2094 }
2095 }
2096 header('Strict-Transport-Security: '. $max_age);
2097 }
2098
2099 // ===================================================================== 2023-05-16
2100
2101 function nfw_custom_headers() {
2102
2103 $headers = json_decode( NFW_CUSTHEADERS, true );
2104 if (! empty( $headers ) ) {
2105 foreach( $headers as $key => $value ) {
2106 header( "$key: $value" );
2107 }
2108 }
2109 }
2110
2111 // =====================================================================
2112 // EOF
2113