Visitar URL original
Introduce more WPCS rules · szepeviktor/waf4wordpress@b0c6d39 · GitHub
Skip to content

Commit b0c6d39

Browse files
committed
Introduce more WPCS rules
1 parent d7bf9f1 commit b0c6d39

5 files changed

Lines changed: 182 additions & 116 deletions

File tree

‎block-bad-requests/README.md‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Bans commonly used user names (hardcoded in a property), blocks non-static requests from CDN,
44
prevents author sniffing and examines every HTTP header in a login request.
5-
Then triggers fail2ban accordingly.
5+
Then triggers Fail2ban accordingly.
66

77
To install copy `wp-fail2ban-bad-request-instant.inc.php` beside your `wp-config.php` and put this line in top of `wp-config.php`:
88

@@ -96,16 +96,17 @@ Constant list
9696
- (boolean) `O1_BAD_REQUEST_ALLOW_TWO_CAPS` allow user names like `JohnDoe`
9797
- (boolean) `O1_BAD_REQUEST_DISALLOW_TOR_LOGIN` to block logins from Tor exit nodes
9898

99-
Detect visitor IP address
99+
Detect client IP address
100100

101-
- HTTP_X_FORWARDED_FOR
101+
- HTTP_CF_CONNECTING_IP (Cloudflare)
102+
- HTTP_X_SUCURI_CLIENTIP (Sucuri)
103+
- HTTP_INCAP_CLIENT_IP (Incapsula)
104+
- HTTP_X_FORWARDED_FOR (Amazon CloudFront, can be comma delimited list of IP addresses)
102105
- HTTP_X_FORWARDED
103106
- HTTP_X_REAL_IP
104-
- HTTP_X_SUCURI_CLIENTIP
105-
- HTTP_CF_CONNECTING_IP
106-
- HTTP_INCAP_CLIENT_IP
107-
- HTTP_FORWARDED
108107
- HTTP_CLIENT_IP
108+
- HTTP_FORWARDED_FOR
109+
- HTTP_FORWARDED
109110
- REMOTE_ADDR
110111

111112
### Experimental upload traffic analysis

‎block-bad-requests/wp-fail2ban-bad-request-instant.inc.php‎

Lines changed: 85 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/*
33
Plugin Name: Block Bad Requests (required from wp-config or MU plugin)
4-
Version: 2.20.0
4+
Version: 2.21.0
55
Description: Stop various HTTP attacks and trigger Fail2ban.
66
Plugin URI: https://github.com/szepeviktor/wordpress-fail2ban
77
License: The MIT License (MIT)
@@ -38,17 +38,17 @@
3838
*/
3939
final class Bad_Request {
4040

41-
private $prefix = 'Malicious traffic detected: ';
42-
private $prefix_instant = 'Break-in attempt detected: ';
41+
private $prefix = 'Malicious traffic detected: ';
42+
private $prefix_instant = 'Break-in attempt detected: ';
4343
private $instant_trigger = true;
4444
// Default rest_url_prefix value
45-
private $rest_url_prefix = '/wp-json/';
45+
private $rest_url_prefix = '/wp-json/';
4646
private $max_login_request_size = 4000;
47-
private $is_wplogin = false;
48-
private $is_xmlrpc = false;
49-
private $is_rest = false;
50-
private $is_options_method = false;
51-
private $names2ban = array(
47+
private $is_wplogin = false;
48+
private $is_xmlrpc = false;
49+
private $is_rest = false;
50+
private $is_options_method = false;
51+
private $names2ban = array(
5252
'access',
5353
'admin',
5454
'administrator',
@@ -84,7 +84,7 @@ final class Bad_Request {
8484
'username',
8585
'webmaster',
8686
);
87-
private $blacklist = array(
87+
private $blacklist = array(
8888
'../', // Path traversal
8989
'/..', // Path traversal
9090
'wp-config', // WP configuration
@@ -101,7 +101,7 @@ final class Bad_Request {
101101
'/administrator', // Joomla Administrator
102102
'connector.asp', // Joomla FCKeditor 2.x File Manager Connector for ASP
103103
'/HNAP1', // D-Link routers
104-
'() { ', // Shell shock () { :;};
104+
'() { ', // Shell shock, Bash script: () { :;};
105105
'/cgi-bin/', // CGI folder
106106
'error_log', // Default PHP error log
107107
'error-log', // PHP error log
@@ -117,26 +117,39 @@ final class Bad_Request {
117117
'+--+', // SQL comment
118118
'%20--%20', // SQL comment
119119
);
120-
private $relative_request_uri = '';
120+
private $relative_request_uri = '';
121121
private $cdn_headers;
122-
private $allow_registration = false;
123-
private $allow_ie8_login = false;
124-
private $allow_old_proxies = false;
122+
private $allow_registration = false;
123+
private $allow_ie8_login = false;
124+
private $allow_old_proxies = false;
125125
private $allow_connection_empty = false;
126126
private $allow_connection_close = false;
127-
private $allow_two_capitals = false;
128-
private $disallow_tor_login = false;
129-
private $result = false;
127+
private $allow_two_capitals = false;
128+
private $disallow_tor_login = false;
129+
private $result = false;
130130

131131
/**
132132
* Set up options, run check and trigger fail2ban on malicous HTTP request.
133133
*/
134134
public function __construct() {
135135

136+
if ( empty( $_SERVER['SERVER_ADDR'] )
137+
|| empty( $_SERVER['REMOTE_ADDR'] )
138+
|| empty( $_SERVER['REMOTE_PORT'] )
139+
|| empty( $_SERVER['REQUEST_METHOD'] )
140+
|| empty( $_SERVER['REQUEST_URI'] )
141+
) {
142+
$this->prefix = 'Server configuration error: ';
143+
$this->instant_trigger = false;
144+
$this->result = 'bad_request_superglobal';
145+
$this->trigger();
146+
exit;
147+
}
148+
136149
// Don't run on local access and on install or upgrade
137150
// WP_INSTALLING is available even before wp-config.php
138151
if ( 'cli' === php_sapi_name()
139-
|| $_SERVER['REMOTE_ADDR'] === $_SERVER['SERVER_ADDR']
152+
|| $_SERVER['REMOTE_ADDR'] === $_SERVER['SERVER_ADDR'] // WPCS: input var okay.
140153
|| ( defined( 'WP_INSTALLING' ) && WP_INSTALLING )
141154
) {
142155
return;
@@ -147,18 +160,19 @@ public function __construct() {
147160
$this->instant_trigger = false;
148161
}
149162

150-
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
151-
$this->relative_request_uri = $_SERVER['REQUEST_URI'];
152-
}
163+
$this->relative_request_uri = $_SERVER['REQUEST_URI'];
153164
// O1_BAD_REQUEST_PROXY_HOME_URL should not contain a trailing slash
154165
if ( defined( 'O1_BAD_REQUEST_PROXY_HOME_URL' ) ) {
155166
$home_url_length = strlen( O1_BAD_REQUEST_PROXY_HOME_URL );
156167
if ( O1_BAD_REQUEST_PROXY_HOME_URL === substr( $_SERVER['REQUEST_URI'], 0, $home_url_length ) ) {
157168
$this->relative_request_uri = substr( $_SERVER['REQUEST_URI'], $home_url_length );
158-
// Fix request URI
159-
// https://core.trac.wordpress.org/ticket/39586
160-
// Uncomment next line
161-
//$_SERVER['REQUEST_URI'] = $this->relative_request_uri;
169+
/**
170+
* Fix request URI
171+
*
172+
* @see https://core.trac.wordpress.org/ticket/39586
173+
*
174+
* $_SERVER['REQUEST_URI'] = $this->relative_request_uri;
175+
*/
162176
}
163177
}
164178

@@ -243,14 +257,14 @@ function apache_request_headers() {
243257
}
244258

245259
// Request methods
246-
$request_method = strtoupper( $_SERVER['REQUEST_METHOD'] );
247-
$wp_methods = array( 'HEAD', 'GET', 'POST' );
260+
$request_method = strtoupper( $_SERVER['REQUEST_METHOD'] );
261+
$wp_methods = array( 'HEAD', 'GET', 'POST' );
248262
$wp_login_methods = array( 'GET', 'POST' );
249-
$wp_rest_methods = array( 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS' );
250-
$write_methods = array( 'POST', 'PUT', 'DELETE' );
263+
$wp_rest_methods = array( 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS' );
264+
$write_methods = array( 'POST', 'PUT', 'DELETE' );
251265

252266
// Dissect request URI
253-
$request_path = parse_url( $this->relative_request_uri, PHP_URL_PATH );
267+
$request_path = parse_url( $this->relative_request_uri, PHP_URL_PATH );
254268
$request_query = isset( $_SERVER['QUERY_STRING'] )
255269
? $_SERVER['QUERY_STRING']
256270
: parse_url( $this->relative_request_uri, PHP_URL_QUERY );
@@ -282,7 +296,7 @@ function apache_request_headers() {
282296
);
283297
$this->enhanced_error_log( 'HTTP headers: ' . $this->esc_log( $cdn_combined_headers ) );
284298
// Work-around to prevent edge server banning
285-
$this->prefix = 'Attack through CDN: ';
299+
$this->prefix = 'Attack through CDN: ';
286300
$this->instant_trigger = false;
287301
return 'bad_request_cdn_attack';
288302
}
@@ -311,18 +325,18 @@ function apache_request_headers() {
311325
// https://tools.ietf.org/html/rfc2616#section-9.2
312326
if ( ! $this->is_rest && 'OPTIONS' === $request_method ) {
313327
$this->is_options_method = true;
314-
$this->instant_trigger = false;
328+
$this->instant_trigger = false;
315329
return 'bad_request_http_options_method';
316330
}
317331
if ( ! $this->is_wplogin && ! $this->is_rest
318-
&& false === in_array( $request_method, $wp_methods )
332+
&& false === in_array( $request_method, $wp_methods, true )
319333
) {
320334
return 'bad_request_http_method';
321335
}
322-
if ( $this->is_wplogin && false === in_array( $request_method, $wp_login_methods ) ) {
336+
if ( $this->is_wplogin && false === in_array( $request_method, $wp_login_methods, true ) ) {
323337
return 'bad_request_wplogin_http_method';
324338
}
325-
if ( $this->is_rest && false === in_array( $request_method, $wp_rest_methods ) ) {
339+
if ( $this->is_rest && false === in_array( $request_method, $wp_rest_methods, true ) ) {
326340
return 'bad_request_rest_http_method';
327341
}
328342

@@ -347,9 +361,11 @@ function apache_request_headers() {
347361
// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
348362
// "#" removed
349363
// "%" added
364+
// Look for "http:" in request path
350365
if ( substr_count( $_SERVER['REQUEST_URI'], '?' ) > 1
351366
|| false !== strpos( $_SERVER['REQUEST_URI'], '#' )
352367
|| 1 === preg_match( "/[^%:\/?\[\]@!$&'()*+,;=A-Za-z0-9._~-]/", $_SERVER['REQUEST_URI'] )
368+
|| 1 === preg_match( '/(http|https|data):/i', $request_path )
353369
) {
354370
$this->instant_trigger = false;
355371
return 'bad_request_uri_encoding';
@@ -369,7 +385,11 @@ function apache_request_headers() {
369385
// http://httpd.apache.org/docs/current/custom-error.html#variables
370386
if ( isset( $_SERVER['REDIRECT_URL'] )
371387
&& false !== stripos( $_SERVER['REDIRECT_URL'], '.php' )
372-
// For old mod_fastcgi setup && $_SERVER['SCRIPT_NAME'] !== $_SERVER['REDIRECT_URL']
388+
/**
389+
* For old mod_fastcgi setup
390+
*
391+
* && $_SERVER['SCRIPT_NAME'] !== $_SERVER['REDIRECT_URL']
392+
*/
373393
) {
374394
return 'bad_request_nonexistent_php';
375395
}
@@ -391,7 +411,7 @@ function apache_request_headers() {
391411
}
392412

393413
// Check write-type method requests only
394-
if ( false === in_array( $request_method, $write_methods ) ) {
414+
if ( false === in_array( $request_method, $write_methods, true ) ) {
395415
// Not a write-type method
396416
return false;
397417
}
@@ -523,7 +543,7 @@ function apache_request_headers() {
523543
$username = trim( $_POST['log'] );
524544

525545
// Banned usernames
526-
if ( in_array( strtolower( $username ), $this->names2ban ) ) {
546+
if ( in_array( strtolower( $username ), $this->names2ban, true ) ) {
527547
return 'bad_request_wplogin_username_banned';
528548
}
529549

@@ -615,9 +635,9 @@ function apache_request_headers() {
615635

616636
// Tor network exit node detection
617637
if ( $this->disallow_tor_login ) {
618-
$exitlist_tpl = '%s.80.%s.ip-port.exitlist.torproject.org';
619-
$remote_rev = implode( '.', array_reverse( explode( '.', $_SERVER['REMOTE_ADDR'] ) ) );
620-
$server_rev = implode( '.', array_reverse( explode( '.', $_SERVER['SERVER_ADDR'] ) ) );
638+
$exitlist_tpl = '%s.80.%s.ip-port.exitlist.torproject.org';
639+
$remote_rev = implode( '.', array_reverse( explode( '.', $_SERVER['REMOTE_ADDR'] ) ) );
640+
$server_rev = implode( '.', array_reverse( explode( '.', $_SERVER['SERVER_ADDR'] ) ) );
621641
$exitlist_response = gethostbyname( sprintf( $exitlist_tpl, $remote_rev, $server_rev ) );
622642
if ( false !== strpos( $exitlist_response, '127.0.0' ) ) {
623643
return 'bad_request_wplogin_tor';
@@ -636,6 +656,7 @@ private function trigger() {
636656
// Trigger miniban
637657
if ( class_exists( '\Miniban' ) && $this->instant_trigger ) {
638658
if ( true !== \Miniban::ban() ) {
659+
// @codingStandardsChangeSetting WordPress.PHP.DevelopmentFunctions exclude error_log
639660
error_log( 'Miniban operation failed.' );
640661
}
641662
}
@@ -692,15 +713,15 @@ private function disable_options_method() {
692713

693714
private function fake_wplogin() {
694715

695-
$server_name = isset( $_SERVER['SERVER_NAME'] )
716+
$server_name = isset( $_SERVER['SERVER_NAME'] )
696717
? $_SERVER['SERVER_NAME']
697718
: $_SERVER['HTTP_HOST'];
698-
$username = trim( $_POST['log'] );
699-
$expire = time() + 3600;
700-
$token = substr( hash_hmac( 'sha256', rand(), 'token' ), 0, 43 );
701-
$hash = hash_hmac( 'sha256', rand(), 'hash' );
702-
$auth_cookie = $username . '|' . $expire . '|' . $token . '|' . $hash;
703-
$authcookie_name = 'wordpress_' . md5( 'authcookie' );
719+
$username = trim( $_POST['log'] );
720+
$expire = time() + 3600;
721+
$token = substr( hash_hmac( 'sha256', rand(), 'token' ), 0, 43 );
722+
$hash = hash_hmac( 'sha256', rand(), 'hash' );
723+
$auth_cookie = $username . '|' . $expire . '|' . $token . '|' . $hash;
724+
$authcookie_name = 'wordpress_' . md5( 'authcookie' );
704725
$loggedincookie_name = 'wordpress_logged_in_' . md5( 'cookiehash' );
705726

706727
header( 'Cache-Control: max-age=0, private, no-store, no-cache, must-revalidate' );
@@ -744,7 +765,7 @@ private function fake_xmlrpc() {
744765
',
745766
$server_name,
746767
$server_name
747-
);
768+
); // WPCS: XSS ok.
748769
}
749770

750771
/**
@@ -764,9 +785,9 @@ private function enhanced_error_log( $message = '', $level = 'error' ) {
764785
*/
765786

766787
// Add entry point, correct when `auto_prepend_file` is empty
767-
$included_files = get_included_files();
788+
$included_files = get_included_files();
768789
$first_included_file = reset( $included_files );
769-
$error_msg = sprintf( '%s <%s',
790+
$error_msg = sprintf( '%s <%s',
770791
$message,
771792
$this->esc_log( sprintf( '%s:%s', $_SERVER['REQUEST_METHOD'], $first_included_file ) )
772793
);
@@ -792,6 +813,7 @@ private function enhanced_error_log( $message = '', $level = 'error' ) {
792813
);
793814
}
794815

816+
// @codingStandardsChangeSetting WordPress.PHP.DevelopmentFunctions exclude error_log
795817
error_log( $error_msg );
796818
}
797819

@@ -804,7 +826,7 @@ private function enhanced_error_log( $message = '', $level = 'error' ) {
804826
*/
805827
private function parse_query( $query_string ) {
806828

807-
$query = array();
829+
$query = array();
808830
$names_values_array = explode( '&', $query_string );
809831

810832
foreach ( $names_values_array as $name_value ) {
@@ -831,7 +853,7 @@ private function parse_query( $query_string ) {
831853
*/
832854
private function esc_log( $string ) {
833855

834-
$escaped = serialize( $string );
856+
$escaped = json_encode( $string );
835857
// Limit length
836858
$escaped = mb_substr( $escaped, 0, 500, 'utf-8' );
837859
// New lines to "|"
@@ -876,14 +898,15 @@ private function get_leafs( $array ) {
876898
return $leafs;
877899
}
878900

879-
$array_iterator = new \RecursiveArrayIterator( $array );
901+
$array_iterator = new \RecursiveArrayIterator( $array );
880902
$iterator_iterator = new \RecursiveIteratorIterator( $array_iterator, \RecursiveIteratorIterator::LEAVES_ONLY );
881903
foreach ( $iterator_iterator as $key => $value ) {
882-
$keys = array();
883-
for ( $i = 0; $i < $iterator_iterator->getDepth(); $i++ ) {
904+
$keys = array();
905+
$depth = $iterator_iterator->getDepth();
906+
for ( $i = 0; $i < $depth; $i++ ) {
884907
$keys[] = $iterator_iterator->getSubIterator( $i )->key();
885908
}
886-
$keys[] = $key;
909+
$keys[] = $key;
887910
$leaf_key = implode( ' ', $keys );
888911

889912
$leafs[ $leaf_key ] = $value;
@@ -921,13 +944,13 @@ private function is_ie( $ua ) {
921944
private function rebuild_query( $request_query ) {
922945

923946
$rebuilt_query = array();
924-
$query_lenght = strlen( $request_query );
925-
$queries = $this->parse_query( $request_query );
947+
$query_lenght = strlen( $request_query );
948+
$queries = $this->parse_query( $request_query );
926949

927950
foreach ( $queries as $key => $value ) {
928951
$rebuilt_query[] = sprintf( '%s=%s',
929-
urlencode( urldecode( $key ) ),
930-
urlencode( urldecode( $value ) )
952+
rawurlencode( urldecode( $key ) ),
953+
rawurlencode( urldecode( $value ) )
931954
);
932955
}
933956

0 commit comments

Comments
 (0)