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