WP fail2ban - Version 3.6.0

Version Description

  • The filter files are now generated from PHPDoc in the code. There were too many times when the filters were out of sync with the code (programmer error) - this should resolve that by bringing the patterns closer to the code that emits them.
  • Added PHPUnit tests. Almost 100% code coverage, with the exception of WP_FAIL2BAN_PROXIES which is quite hard to test properly.
  • Bugfix for wordpress-soft.conf.
  • Add WP_FAIL2BAN_XMLRPC_LOG.
  • Add WP_FAIL2BAN_REMOTE_ADDR.
  • WP_FAIL2BAN_PROXIES now supports an array of IPs with PHP 7.
  • Moved all documentation to https://wp-fail2ban.readthedocs.io/.
Download this release

Release Info

Developer invisnet
Plugin Icon 128x128 WP fail2ban
Version 3.6.0
Comparing to
See all releases

Code changes from version 3.5.3 to 3.6.0

Files changed (4) hide show
  1. filters.d/wordpress-hard.conf +27 -26
  2. filters.d/wordpress-soft.conf +22 -31
  3. readme.txt +189 -301
  4. wp-fail2ban.php +165 -112
filters.d/wordpress-hard.conf CHANGED
@@ -1,26 +1,27 @@
1
- # Fail2Ban filter for WordPress hard failures
2
- #
3
-
4
- [INCLUDES]
5
-
6
- before = common.conf
7
-
8
- [Definition]
9
-
10
- _daemon = (?:wordpress|wp)
11
-
12
- failregex = ^%(__prefix_line)sAuthentication attempt for unknown user .* from <HOST>$
13
- ^%(__prefix_line)sBlocked user enumeration attempt from <HOST>$
14
- ^%(__prefix_line)sBlocked authentication attempt for .* from <HOST>$
15
- ^%(__prefix_line)sPingback error .* generated from <HOST>$
16
- ^%(__prefix_line)sSpam comment \d+ from <HOST>$
17
- ^%(__prefix_line)sXML-RPC authentication attempt for unknown user .* from <HOST>$
18
- ^%(__prefix_line)sXML-RPC multicall authentication failure from <HOST>$
19
-
20
- ignoreregex =
21
-
22
- # DEV Notes:
23
- # Requires the 'WP fail2ban' plugin:
24
- # https://wordpress.org/plugins/wp-fail2ban/
25
- #
26
- # Author: Charles Lecklider
 
1
+ # Fail2Ban filter for WordPress hard failures
2
+ # Auto-generated: 2018-11-04T16:40:53+00:00
3
+ #
4
+
5
+ [INCLUDES]
6
+
7
+ before = common.conf
8
+
9
+ [Definition]
10
+
11
+ _daemon = (?:wordpress|wp)
12
+
13
+ failregex = ^%(__prefix_line)sBlocked authentication attempt for .* from <HOST>$
14
+ ^%(__prefix_line)sBlocked user enumeration attempt from <HOST>$
15
+ ^%(__prefix_line)sSpam comment \d+ from <HOST>$
16
+ ^%(__prefix_line)sXML-RPC multicall authentication failure from <HOST>$
17
+ ^%(__prefix_line)sPingback error .* generated from <HOST>$
18
+ ^%(__prefix_line)sAuthentication attempt for unknown user .* from <HOST>$
19
+ ^%(__prefix_line)sXML-RPC authentication attempt for unknown user .* from <HOST>$
20
+
21
+ ignoreregex =
22
+
23
+ # DEV Notes:
24
+ # Requires the 'WP fail2ban' plugin:
25
+ # https://github.com/invisnet/wp-fail2ban/
26
+ #
27
+ # Author: Charles Lecklider
filters.d/wordpress-soft.conf CHANGED
@@ -1,31 +1,22 @@
1
- # Fail2Ban configuration file
2
- #
3
- # Author: Charles Lecklider
4
- #
5
-
6
- [INCLUDES]
7
-
8
- # Read common prefixes. If any customizations available -- read them from
9
- # common.local
10
- before = common.conf
11
-
12
-
13
- [Definition]
14
-
15
- _daemon = (?:wordpress|wp)
16
-
17
- # Option: failregex
18
- # Notes.: regex to match the password failures messages in the logfile. The
19
- # host must be matched by a group named "host". The tag "<HOST>" can
20
- # be used for standard IP/hostname matching and is only an alias for
21
- # (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
22
- # Values: TEXT
23
- #
24
- failregex = ^%(__prefix_line)sAuthentication failure for .* from <HOST>$
25
- ^%(__prefix_line)sXML-RPC authentication failure from <HOST>$
26
-
27
- # Option: ignoreregex
28
- # Notes.: regex to ignore. If this regex matches, the line is ignored.
29
- # Values: TEXT
30
- #
31
- ignoreregex =
1
+ # Fail2Ban filter for WordPress soft failures
2
+ # Auto-generated: 2018-11-04T16:40:53+00:00
3
+ #
4
+
5
+ [INCLUDES]
6
+
7
+ before = common.conf
8
+
9
+ [Definition]
10
+
11
+ _daemon = (?:wordpress|wp)
12
+
13
+ failregex = ^%(__prefix_line)sAuthentication failure for .* from <HOST>$
14
+ ^%(__prefix_line)sXML-RPC authentication failure for .* from <HOST>$
15
+
16
+ ignoreregex =
17
+
18
+ # DEV Notes:
19
+ # Requires the 'WP fail2ban' plugin:
20
+ # https://github.com/invisnet/wp-fail2ban/
21
+ #
22
+ # Author: Charles Lecklider
 
 
 
 
 
 
 
 
 
readme.txt CHANGED
@@ -1,301 +1,189 @@
1
- === WP fail2ban ===
2
- Contributors: invisnet
3
- Author URI: https://charles.lecklider.org/
4
- Plugin URI: https://charles.lecklider.org/wordpress/wp-fail2ban/
5
- Tags: fail2ban, login, security, syslog
6
- Requires at least: 3.4.0
7
- Tested up to: 4.8.0
8
- Stable tag: 3.5.3
9
- License: GPLv2 or later
10
- License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
-
12
- Write a myriad of WordPress events to syslog for integration with fail2ban.
13
-
14
- == Description ==
15
-
16
- [fail2ban](http://www.fail2ban.org/) is one of the simplest and most effective security measures you can implement to prevent brute-force password-guessing attacks.
17
-
18
- *WP fail2ban* logs all login attempts - including via XML-RPC, whether successful or not, to syslog using LOG_AUTH. For example:
19
-
20
- Oct 17 20:59:54 foobar wordpress(www.example.com)[1234]: Authentication failure for admin from 192.168.0.1
21
- Oct 17 21:00:00 foobar wordpress(www.example.com)[2345]: Accepted password for admin from 192.168.0.1
22
-
23
- *WPf2b* comes with two `fail2ban` filters, `wordpress-hard.conf` and `wordpress-soft.conf`, designed to allow a split between immediate banning and the traditional more graceful approach.
24
-
25
- Requires PHP 5.3 or later.
26
-
27
- = Other Features =
28
-
29
- **CloudFlare and Proxy Servers**
30
-
31
- *WPf2b* can be configured to work with CloudFlare and other proxy servers. See `WP_FAIL2BAN_PROXIES` in the FAQ.
32
-
33
- **Comments**
34
-
35
- *WPf2b* can log comments. See `WP_FAIL2BAN_LOG_COMMENTS`.
36
-
37
- **Pingbacks**
38
-
39
- *WPf2b* logs failed pingbacks, and can log all pingbacks. See `WP_FAIL2BAN_LOG_PINGBACKS` in the FAQ.
40
-
41
- **Spam**
42
-
43
- *WPf2b* can log comments marked as spam. See `WP_FAIL2BAN_LOG_SPAM` in the FAQ.
44
-
45
- **User Enumeration**
46
-
47
- *WPf2b* can block user enumeration. See `WP_FAIL2BAN_BLOCK_USER_ENUMERATION` in the FAQ.
48
-
49
- **Work-Arounds for Broken syslogd**
50
-
51
- *WPf2b* can be configured to work around most syslogd weirdness. See `WP_FAIL2BAN_SYSLOG_SHORT_TAG` and `WP_FAIL2BAN_HTTP_HOST` in the FAQ.
52
-
53
- **Blocking Users**
54
-
55
- *WPf2b* can be configured to short-cut the login process when the username matches a regex. See `WP_FAIL2BAN_BLOCKED_USERS` in the FAQ.
56
-
57
-
58
-
59
- == Installation ==
60
-
61
- 1. Upload the plugin to your plugins directory
62
- 1. Activate the plugin through the 'Plugins' menu in WordPress
63
- 1. Copy `wordpress-hard.conf` and `wordpress-soft.conf` to your `fail2ban/filters.d` directory
64
- 1. Edit `jail.local` to include something like:
65
- ~~~
66
- [wordpress-hard]
67
- enabled = true
68
- filter = wordpress-hard
69
- logpath = /var/log/auth.log
70
- maxretry = 1
71
- port = http,https
72
-
73
- [wordpress-soft]
74
- enabled = true
75
- filter = wordpress-soft
76
- logpath = /var/log/auth.log
77
- maxretry = 3
78
- port = http,https
79
- ~~~
80
- 5. Reload or restart `fail2ban`
81
-
82
- You may want to set `WP_FAIL2BAN_BLOCK_USER_ENUMERATION`, `WP_FAIL2BAN_PROXIES` and/or `WP_FAIL2BAN_BLOCKED_USERS`; see the FAQ for details.
83
-
84
- == Frequently Asked Questions ==
85
-
86
- = wordpress-hard.conf vs wordpress-soft.conf =
87
-
88
- There are some things that are almost always malicious, e.g. blocked users and pingbacks with errors. `wordpress-hard.conf` is designed to catch these so that you can ban the IP immediately.
89
-
90
- Other things are relatively benign, like a failed login. You can't let people try forever, but banning the IP immediately would be wrong too. `wordpress-soft.conf` is designed to catch these so that you can set a higher retry limit before banning the IP.
91
-
92
- For the avoidance of doubt: you should be using *both* filters.
93
-
94
- = WP_FAIL2BAN_HTTP_HOST – what’s it for? =
95
-
96
- This is for some flavours of Linux where `WP_FAIL2BAN_SYSLOG_SHORT_TAG` isn't enough.
97
-
98
- If you configure your web server to set an environment variable named `WP_FAIL2BAN_SYSLOG_SHORT_TAG` on a per-virtual host basis, *WPf2b* will use that in the syslog tag. This allows you to configure a unique tag per site in a way that makes sense for your configuration, rather than some arbitrary truncation or hashing within the plugin.
99
-
100
- **NB:** This feature has not been tested as extensively as others. While I'm confident it works, FreeBSD doesn't have this problem so this feature will always be second-tier.
101
-
102
- = WP_FAIL2BAN_SYSLOG_SHORT_TAG – what’s it for? =
103
-
104
- Some flavours of Linux come with a `syslogd` that can't cope with the normal message format *WPf2b* uses; basically, they assume that the first part of the message (the tag) won't exceed some (small) number of characters, and mangle the message if it does. This breaks the regex in the *fail2ban* filter and so nothing gets blocked.
105
-
106
- Adding:
107
-
108
- define('WP_FAIL2BAN_SYSLOG_SHORT_TAG',true);
109
-
110
- to `functions.php` will make *WPf2b* use `wp` as the syslog tag, rather than the normal `wordpress`. This buys you 7 characters which may be enough to work around the problem, but if it's not enough you should look at `WP_FAIL2BAN_HTTP_HOST` or `WP_FAIL2BAN_TRUNCATE_HOST` too.
111
-
112
- = WP_FAIL2BAN_TRUNCATE_HOST =
113
-
114
- If you've set `WP_FAIL2BAN_SYSLOG_SHORT_TAG` and defining `WP_FAIL2BAN_HTTP_HOST` for each virtual host isn't appropriate, you can set `WP_FAIL2BAN_TRUNCATE_HOST` to whatever value you need to make syslog happy:
115
-
116
- define('WP_FAIL2BAN_TRUNCATE_HOST',8);
117
-
118
- This does exactly what the name suggests: truncates the host name to the length you specify. As a result there's no guarantee that what's left will be enough to identify the site.
119
-
120
- = WP_FAIL2BAN_BLOCKED_USERS what’s it all about? =
121
-
122
- The bots that try to brute-force WordPress logins aren't that clever (no doubt that will change), but they may only make one request per IP every few hours in an attempt to avoid things like `fail2ban`. With large botnets this can still create significant load.
123
-
124
- Based on a suggestion from *jmadea*, *WPf2b* now allows you to specify a regex that will shortcut the login process if the requested username matches.
125
-
126
- For example, putting the following in `wp-config.php`:
127
-
128
- define('WP_FAIL2BAN_BLOCKED_USERS','^admin$');
129
-
130
- will block any attempt to log in as `admin` before most of the core WordPress code is run. Unless you go crazy with it, a regex is usually cheaper than a call to the database so this should help keep things running during an attack.
131
-
132
- *WPf2b* doesn't do anything to the regex other than make it case-insensitive.
133
-
134
- If you're running PHP 7, you can now specify an array of users instead:
135
-
136
- define('WP_FAIL2BAN_BLOCKED_USERS',['admin','another','user']);
137
-
138
- = WP_FAIL2BAN_PROXIES – what’s it all about? =
139
-
140
- The idea here is to list the IP addresses of the trusted proxies that will appear as the remote IP for the request. When defined:
141
-
142
- * If the remote address appears in the `WP_FAIL2BAN_PROXIES` list, *WPf2b* will log the IP address from the `X-Forwarded-For` header
143
- * If the remote address does not appear in the `WP_FAIL2BAN_PROXIES` list, *WPf2b* will return a 403 error
144
- * If there's no X-Forwarded-For header, *WPf2b* will behave as if `WP_FAIL2BAN_PROXIES` isn't defined
145
-
146
- To set `WP_FAIL2BAN_PROXIES`, add something like the following to `wp-config.php`:
147
-
148
- define('WP_FAIL2BAN_PROXIES','192.168.0.42,192.168.42.0/24');
149
-
150
- *WPf2b* doesn't do anything clever with the list - beware of typos!
151
-
152
- = WP_FAIL2BAN_BLOCK_USER_ENUMERATION what’s it all about? =
153
-
154
- Brute-forcing WP requires knowing a valid username. Unfortunately, WP makes this all but trivial.
155
-
156
- Based on a suggestion from *geeklol* and a plugin by *ROIBOT*, *WPf2b* can now block user enumeration attempts. Just add the following to `wp-config.php`:
157
-
158
- define('WP_FAIL2BAN_BLOCK_USER_ENUMERATION',true);
159
-
160
- = WP_FAIL2BAN_LOG_PINGBACKS – what’s it all about? =
161
-
162
- Based on a suggestion from *maghe*, *WPf2b* can now log pingbacks. To enable this feature, add the following to `wp-config.php`:
163
-
164
- define('WP_FAIL2BAN_LOG_PINGBACKS',true);
165
-
166
- By default, *WPf2b* uses LOG_USER for logging pingbacks. If you'd rather it used a different facility you can change it by adding something like the following to `wp-config.php`:
167
-
168
- define('WP_FAIL2BAN_PINGBACK_LOG',LOG_LOCAL3);
169
-
170
- = WP_FAIL2BAN_LOG_COMMENTS =
171
-
172
- *WPf2b* can now log comments. To enable this feature, add the following to `wp-config.php`:
173
-
174
- define('WP_FAIL2BAN_LOG_COMMENTS',true);
175
-
176
- By default, *WPf2b* uses LOG_USER for logging comments. If you'd rather it used a different facility you can change it by adding something like the following to `wp-config.php`:
177
-
178
- define('WP_FAIL2BAN_COMMENT_LOG',LOG_LOCAL3);
179
-
180
- = WP_FAIL2BAN_LOG_SPAM =
181
-
182
- *WPf2b* can now log spam comments. To enable this feature, add the following to `wp-config.php`:
183
-
184
- define('WP_FAIL2BAN_LOG_SPAM',true);
185
-
186
- The comment ID and IP will be written to `WP_FAIL2BAN_AUTH_LOG` and matched by `wordpress-hard`.
187
-
188
- = WP_FAIL2BAN_AUTH_LOG what’s it all about? =
189
-
190
- By default, *WPf2b* uses LOG_AUTH for logging authentication success or failure. However, some systems use LOG_AUTHPRIV instead, but there's no good run-time way to tell. If your system uses LOG_AUTHPRIV you should add the following to `wp-config.php`:
191
-
192
- define('WP_FAIL2BAN_AUTH_LOG',LOG_AUTHPRIV);
193
-
194
- == Changelog ==
195
-
196
- = 3.5.3 =
197
- * Bugfix for `wordpress-hard.conf`.
198
-
199
- = 3.5.1 =
200
- * Bugfix for `WP_FAIL2BAN_BLOCK_USER_ENUMERATION`.
201
-
202
- = 3.5.0 =
203
- * Add `WP_FAIL2BAN_OPENLOG_OPTIONS`.
204
- * Add `WP_FAIL2BAN_LOG_COMMENTS` and `WP_FAIL2BAN_COMMENT_LOG`.
205
- * Add `WP_FAIL2BAN_LOG_PASSWORD_REQUEST`.
206
- * Add `WP_FAIL2BAN_LOG_SPAM`.
207
- * Add `WP_FAIL2BAN_TRUNCATE_HOST`.
208
- * `WP_FAIL2BAN_BLOCKED_USERS` now supports an array of users with PHP 7.
209
-
210
- = 3.0.3 =
211
- * Fix regex in `wordpress-hard.conf`
212
-
213
- = 3.0.2 =
214
- * Prevent double logging in WP 4.5.x for XML-RPC authentication failure
215
-
216
- = 3.0.1 =
217
- * Fix regex in `wordpress-hard.conf`
218
-
219
- = 3.0.0 =
220
- * Add `WP_FAIL2BAN_SYSLOG_SHORT_TAG`.
221
- * Add `WP_FAIL2BAN_HTTP_HOST`.
222
- * Log XML-RPC authentication failure.
223
- * Add better support for MU deployment.
224
-
225
- = 2.3.2 =
226
- * Bugfix `WP_FAIL2BAN_BLOCKED_USERS`.
227
-
228
- = 2.3.0 =
229
- * Bugfix in *experimental* `WP_FAIL2BAN_PROXIES` code (thanks to KyleCartmell).
230
-
231
- = 2.2.1 =
232
- * Fix stupid mistake with `WP_FAIL2BAN_BLOCKED_USERS`.
233
-
234
- = 2.2.0 =
235
- * Custom authentication log is now called `WP_FAIL2BAN_AUTH_LOG`
236
- * Add logging for pingbacks
237
- * Custom pingback log is called `WP_FAIL2BAN_PINGBACK_LOG`
238
-
239
- = 2.1.1 =
240
- * Minor bugfix.
241
-
242
- = 2.1.0 =
243
- * Add support for blocking user enumeration; see `WP_FAIL2BAN_BLOCK_USER_ENUMERATION`
244
- * Add support for CIDR notation in `WP_FAIL2BAN_PROXIES`.
245
-
246
- = 2.0.1 =
247
- * Bugfix in *experimental* `WP_FAIL2BAN_PROXIES` code.
248
-
249
- = 2.0.0 =
250
- * Add *experimental* support for X-Forwarded-For header; see `WP_FAIL2BAN_PROXIES`
251
- * Add *experimental* support for regex-based login blocking; see `WP_FAIL2BAN_BLOCKED_USERS`
252
-
253
- = 1.2.1 =
254
- * Update FAQ.
255
-
256
- = 1.2 =
257
- * Fix harmless warning.
258
-
259
- = 1.1 =
260
- * Minor cosmetic updates.
261
-
262
- = 1.0 =
263
- * Initial release.
264
-
265
- == Upgrade Notice ==
266
-
267
- = 3.5.3 =
268
- You will need up update your `fail2ban` filters.
269
-
270
- = 3.5.1 =
271
- Bugfix: disable `WP_FAIL2BAN_BLOCK_USER_ENUMERATION` in admin area....
272
-
273
- = 3.5.0 =
274
- You will need up update your `fail2ban` filters.
275
-
276
- = 3.0.3 =
277
- You will need up update your `fail2ban` filters.
278
-
279
- = 3.0.0 =
280
- BREAKING CHANGE: The `fail2ban` filters have been split into two files. You will need up update your `fail2ban` configuration.
281
-
282
- = 2.3.0 =
283
- Fix for `WP_FAIL2BAN_PROXIES`; if you're not using it you can safely skip this release.
284
-
285
- = 2.2.1 =
286
- Bugfix.
287
-
288
- = 2.2.0 =
289
- BREAKING CHANGE: `WP_FAIL2BAN_LOG` has been renamed to `WP_FAIL2BAN_AUTH_LOG`
290
-
291
- Pingbacks are getting a lot of attention recently, so *WPf2b* can now log them.
292
- The `wordpress.conf` filter has been updated; you will need to update your `fail2ban` configuration.
293
-
294
- = 2.1.0 =
295
- The `wordpress.conf` filter has been updated; you will need to update your `fail2ban` configuration.
296
-
297
- = 2.0.1 =
298
- Bugfix in experimental code; still an experimental release.
299
-
300
- = 2.0.0 =
301
- This is an experimental release. If your current version is working and you're not interested in the new features, skip this version - wait for 2.1.0. For those that do want to test this release, note that `wordpress.conf` has changed - you'll need to copy it to `fail2ban/filters.d` again.
1
+ === WP fail2ban ===
2
+ Contributors: invisnet
3
+ Author URI: https://charles.lecklider.org/
4
+ Plugin URI: https://github.com/invisnet/wp-fail2ban
5
+ Tags: fail2ban, login, security, syslog
6
+ Requires at least: 3.4
7
+ Tested up to: 4.9
8
+ Stable tag: 3.6.0
9
+ Requires PHP: 5.3
10
+ License: GPLv2 or later
11
+ License URI: http://www.gnu.org/licenses/gpl-2.0.html
12
+ Provides: FAIL2BAN
13
+
14
+ Write a myriad of WordPress events to syslog for integration with fail2ban.
15
+
16
+ == Description ==
17
+
18
+ [fail2ban](http://www.fail2ban.org/) is one of the simplest and most effective security measures you can implement to prevent brute-force password-guessing attacks.
19
+
20
+ *WP fail2ban* logs all login attempts - including via XML-RPC, whether successful or not, to syslog using LOG_AUTH. For example:
21
+
22
+ Oct 17 20:59:54 foobar wordpress(www.example.com)[1234]: Authentication failure for admin from 192.168.0.1
23
+ Oct 17 21:00:00 foobar wordpress(www.example.com)[2345]: Accepted password for admin from 192.168.0.1
24
+
25
+ *WPf2b* comes with two `fail2ban` filters, `wordpress-hard.conf` and `wordpress-soft.conf`, designed to allow a split between immediate banning and the traditional more graceful approach.
26
+
27
+ = Features =
28
+
29
+ **CloudFlare and Proxy Servers**
30
+
31
+ *WPf2b* can be configured to work with CloudFlare and other proxy servers. For an overview see [`WP_FAIL2BAN_PROXIES`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-proxies).
32
+
33
+ **Comments**
34
+
35
+ *WPf2b* can log comments. See [`WP_FAIL2BAN_LOG_COMMENTS`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-log-comments).
36
+
37
+ **Pingbacks**
38
+
39
+ *WPf2b* logs failed pingbacks, and can log all pingbacks. For an overview see [`WP_FAIL2BAN_LOG_PINGBACKS`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-log-pingbacks).
40
+
41
+ **Spam**
42
+
43
+ *WPf2b* can log comments marked as spam. See [`WP_FAIL2BAN_LOG_SPAM`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-log-spam).
44
+
45
+ **User Enumeration**
46
+
47
+ *WPf2b* can block user enumeration. See [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-block-user-enumeration).
48
+
49
+ **Work-Arounds for Broken syslogd**
50
+
51
+ *WPf2b* can be configured to work around most syslogd weirdness. For an overview see [`WP_FAIL2BAN_SYSLOG_SHORT_TAG`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-syslog-short-tag) and [`WP_FAIL2BAN_HTTP_HOST`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-http-host).
52
+
53
+ **Blocking Users**
54
+
55
+ *WPf2b* can be configured to short-cut the login process when the username matches a regex. For an overview see [`WP_FAIL2BAN_BLOCKED_USERS`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-blocked-users).
56
+
57
+ **`mu-plugins` Support**
58
+
59
+ *WPf2b* can easily be configured as a must-use plugin - see [Configuration](https://wp-fail2ban.readthedocs.io/en/3.6/configuration.html#mu-plugins-support).
60
+
61
+
62
+
63
+ == Installation ==
64
+
65
+ 1. Install via the Plugin Directory, or upload to your plugins directory.
66
+ 1. Activate the plugin through the 'Plugins' menu in WordPress.
67
+ 1. Edit `wp-config.php` to suit your needs - see [Configuration](https://wp-fail2ban.readthedocs.io/en/3.6/configuration.html).
68
+
69
+ == Changelog ==
70
+
71
+ = 3.6.0 =
72
+ * The [filter files](https://wp-fail2ban.readthedocs.io/en/3.6/filters.html) are now generated from PHPDoc in the code. There were too many times when the filters were out of sync with the code (programmer error) - this should resolve that by bringing the patterns closer to the code that emits them.
73
+ * Added [PHPUnit tests](https://wp-fail2ban.readthedocs.io/en/3.6/tests.html). Almost 100% code coverage, with the exception of [`WP_FAIL2BAN_PROXIES`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-proxies) which is quite hard to test properly.
74
+ * Bugfix for [`wordpress-soft.conf`](https://wp-fail2ban.readthedocs.io/en/3.6/filters.html#wordpress-soft-conf).
75
+ * Add [`WP_FAIL2BAN_XMLRPC_LOG`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-xmlrpc-log).
76
+ * Add [`WP_FAIL2BAN_REMOTE_ADDR`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-remote-addr).
77
+ * [`WP_FAIL2BAN_PROXIES`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-proxies) now supports an array of IPs with PHP 7.
78
+ * Moved all documentation to [https://wp-fail2ban.readthedocs.io/](https://wp-fail2ban.readthedocs.io/).
79
+
80
+ = 3.5.3 =
81
+ * Bugfix for [`wordpress-hard.conf`](https://wp-fail2ban.readthedocs.io/en/3.6/filters.html#wordpress-hard-conf).
82
+
83
+ = 3.5.1 =
84
+ * Bugfix for [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-block-user-enumeration).
85
+
86
+ = 3.5.0 =
87
+ * Add [`WP_FAIL2BAN_OPENLOG_OPTIONS`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-openlog-options).
88
+ * Add [`WP_FAIL2BAN_LOG_COMMENTS`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-log-comments) and [`WP_FAIL2BAN_COMMENT_LOG`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-comment-log).
89
+ * Add [`WP_FAIL2BAN_LOG_PASSWORD_REQUEST`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-log-password-request).
90
+ * Add [`WP_FAIL2BAN_LOG_SPAM`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-log-spam).
91
+ * Add [`WP_FAIL2BAN_TRUNCATE_HOST`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-truncate-host).
92
+ * [`WP_FAIL2BAN_BLOCKED_USERS`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-blocked-users) now supports an array of users with PHP 7.
93
+
94
+ = 3.0.3 =
95
+ * Fix regex in [`wordpress-hard.conf`](https://wp-fail2ban.readthedocs.io/en/3.6/filters.html#wordpress-hard-conf).
96
+
97
+ = 3.0.2 =
98
+ * Prevent double logging in WP 4.5.x for XML-RPC authentication failure
99
+
100
+ = 3.0.1 =
101
+ * Fix regex in [`wordpress-hard.conf`](https://wp-fail2ban.readthedocs.io/en/3.6/filters.html#wordpress-hard-conf).
102
+
103
+ = 3.0.0 =
104
+ * Add [`WP_FAIL2BAN_SYSLOG_SHORT_TAG`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-syslog-short-tag).
105
+ * Add [`WP_FAIL2BAN_HTTP_HOST`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-http-host).
106
+ * Log XML-RPC authentication failure.
107
+ * Add better support for MU deployment.
108
+
109
+ = 2.3.2 =
110
+ * Bugfix [`WP_FAIL2BAN_BLOCKED_USERS`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-blocked-users).
111
+
112
+ = 2.3.0 =
113
+ * Bugfix in *experimental* [`WP_FAIL2BAN_PROXIES`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-proxies) code (thanks to KyleCartmell).
114
+
115
+ = 2.2.1 =
116
+ * Fix stupid mistake with [`WP_FAIL2BAN_BLOCKED_USERS`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-blocked-users).
117
+
118
+ = 2.2.0 =
119
+ * Custom authentication log is now called [`WP_FAIL2BAN_AUTH_LOG`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-auth-log).
120
+ * Add logging for pingbacks; see [`WP_FAIL2BAN_LOG_PINGBACKS`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-log-pingbacks).
121
+ * Custom pingback log is called [`WP_FAIL2BAN_PINGBACK_LOG`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-pingback-log).
122
+
123
+ = 2.1.1 =
124
+ * Minor bugfix.
125
+
126
+ = 2.1.0 =
127
+ * Add support for blocking user enumeration; see [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-block-user-enumeration).
128
+ * Add support for CIDR notation in [`WP_FAIL2BAN_PROXIES`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-proxies).
129
+
130
+ = 2.0.1 =
131
+ * Bugfix in *experimental* [`WP_FAIL2BAN_PROXIES`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-proxies) code.
132
+
133
+ = 2.0.0 =
134
+ * Add *experimental* support for X-Forwarded-For header; see [`WP_FAIL2BAN_PROXIES`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-proxies).
135
+ * Add *experimental* support for regex-based login blocking; see [`WP_FAIL2BAN_BLOCKED_USERS`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-blocked-users).
136
+
137
+ = 1.2.1 =
138
+ * Update FAQ.
139
+
140
+ = 1.2 =
141
+ * Fix harmless warning.
142
+
143
+ = 1.1 =
144
+ * Minor cosmetic updates.
145
+
146
+ = 1.0 =
147
+ * Initial release.
148
+
149
+ == Upgrade Notice ==
150
+
151
+ = 3.6.0 =
152
+ You will need up update your `fail2ban` filters.
153
+
154
+ = 3.5.3 =
155
+ You will need up update your `fail2ban` filters.
156
+
157
+ = 3.5.1 =
158
+ Bugfix: disable [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-block-user-enumeration) in admin area....
159
+
160
+ = 3.5.0 =
161
+ You will need up update your `fail2ban` filters.
162
+
163
+ = 3.0.3 =
164
+ You will need up update your `fail2ban` filters.
165
+
166
+ = 3.0.0 =
167
+ BREAKING CHANGE: The `fail2ban` filters have been split into two files. You will need up update your `fail2ban` configuration.
168
+
169
+ = 2.3.0 =
170
+ Fix for [`WP_FAIL2BAN_PROXIES`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-proxies); if you're not using it you can safely skip this release.
171
+
172
+ = 2.2.1 =
173
+ Bugfix.
174
+
175
+ = 2.2.0 =
176
+ BREAKING CHANGE: `WP_FAIL2BAN_LOG` has been renamed to [`WP_FAIL2BAN_AUTH_LOG`](https://wp-fail2ban.readthedocs.io/en/3.6/defines.html#wp-fail2ban-auth-log).
177
+
178
+ Pingbacks are getting a lot of attention recently, so *WPf2b* can now log them.
179
+ The `wordpress.conf` filter has been updated; you will need to update your `fail2ban` configuration.
180
+
181
+ = 2.1.0 =
182
+ The `wordpress.conf` filter has been updated; you will need to update your `fail2ban` configuration.
183
+
184
+ = 2.0.1 =
185
+ Bugfix in experimental code; still an experimental release.
186
+
187
+ = 2.0.0 =
188
+ This is an experimental release. If your current version is working and you're not interested in the new features, skip this version - wait for 2.1.0. For those that do want to test this release, note that `wordpress.conf` has changed - you'll need to copy it to `fail2ban/filters.d` again.
189
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
wp-fail2ban.php CHANGED
@@ -1,18 +1,18 @@
1
  <?php
2
- /**
3
  * Plugin Name: WP fail2ban
4
- * Plugin URI: https://charles.lecklider.org/wordpress/wp-fail2ban/
5
- * Description: Write all login attempts to syslog for integration with fail2ban.
6
  * Text Domain: wp-fail2ban
7
- * Version: 3.5.3
8
  * Author: Charles Lecklider
9
  * Author URI: https://charles.lecklider.org/
10
  * License: GPL2
11
  * SPDX-License-Identifier: GPL-2.0
12
  */
13
 
14
- /**
15
- * Copyright 2012-16 Charles Lecklider (email : wordpress@charles.lecklider.org)
16
  *
17
  * This program is free software; you can redistribute it and/or modify
18
  * it under the terms of the GNU General Public License, version 2, as
@@ -36,16 +36,17 @@ namespace org\lecklider\charles\wordpress\wp_fail2ban;
36
  */
37
  global $wp_fail2ban;
38
  if (empty($wp_fail2ban) && defined('WP_FAIL2BAN')) return;
39
- define('WP_FAIL2BAN',true);
40
 
41
 
42
  /**
43
- * Allow custom openlog openssl_get_cert_locations.
44
- * e.g. you may not want the PID if logging remotely.
45
- * @since 3.5.0
 
46
  */
47
  if (!defined('WP_FAIL2BAN_OPENLOG_OPTIONS')) {
48
- define('WP_FAIL2BAN_OPENLOG_OPTIONS', LOG_PID);
49
  }
50
  /**
51
  * Make sure all custom logs are defined.
@@ -67,12 +68,12 @@ if (!defined('WP_FAIL2BAN_PINGBACK_LOG')) {
67
  */
68
  function openlog($log = 'WP_FAIL2BAN_AUTH_LOG')
69
  {
70
- $tag = (defined('WP_FAIL2BAN_SYSLOG_SHORT_TAG') && true === WP_FAIL2BAN_SYSLOG_SHORT_TAG)
71
- ? 'wp'
72
- : 'wordpress';
73
- $host = (array_key_exists('WP_FAIL2BAN_HTTP_HOST', $_ENV))
74
- ? $_ENV['WP_FAIL2BAN_HTTP_HOST']
75
- : $_SERVER['HTTP_HOST'];
76
  /**
77
  * Some varieties of syslogd have difficulty if $host is too long
78
  * @since 3.5.0
@@ -80,10 +81,10 @@ function openlog($log = 'WP_FAIL2BAN_AUTH_LOG')
80
  if (defined('WP_FAIL2BAN_TRUNCATE_HOST') && 1 < intval(WP_FAIL2BAN_TRUNCATE_HOST)) {
81
  $host = substr($host, 0, intval(WP_FAIL2BAN_TRUNCATE_HOST));
82
  }
83
- if (false === \openlog("$tag($host)", WP_FAIL2BAN_OPENLOG_OPTIONS, constant($log))) {
84
- error_log('WPf2b: Cannot open syslog', 0);
85
- } elseif (defined('WP_DEBUG') && true === WP_DEBUG) {
86
- error_log('WPf2b: Opened syslog', 0);
87
  }
88
  }
89
 
@@ -98,9 +99,9 @@ function syslog($level, $msg, $remote_addr = null)
98
  : $remote_addr;
99
 
100
  if (false === \syslog($level, $msg)) {
101
- error_log("WPf2b: Cannot write to syslog: '{$msg}'", 0);
102
- } elseif (defined('WP_DEBUG') && true === WP_DEBUG) {
103
- error_log("WPf2b: Wrote to syslog: '{$msg}'", 0);
104
  }
105
  \closelog();
106
 
@@ -117,38 +118,57 @@ function syslog($level, $msg, $remote_addr = null)
117
  */
118
  function bail()
119
  {
120
- wp_die('Forbidden', 'Forbidden', array('response' => 403));
121
  }
122
 
 
 
 
 
123
  function remote_addr()
124
  {
125
- if (defined('WP_FAIL2BAN_PROXIES')) {
126
- if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
127
- $ip = ip2long($_SERVER['REMOTE_ADDR']);
128
- foreach(explode(',', WP_FAIL2BAN_PROXIES) as $proxy) {
129
- if (2 == count($cidr = explode('/', $proxy))) {
130
- $net = ip2long($cidr[0]);
131
- $mask = ~ ( pow(2, (32 - $cidr[1])) - 1 );
132
- } else {
133
- $net = ip2long($proxy);
134
- $mask = -1;
135
- }
136
- if ($net == ($ip & $mask)) {
137
- return (false === ($len = strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',')))
138
- ? $_SERVER['HTTP_X_FORWARDED_FOR']
139
- : substr($_SERVER['HTTP_X_FORWARDED_FOR'], 0, $len);
140
- }
141
- }
142
- }
143
- }
144
-
145
- return $_SERVER['REMOTE_ADDR'];
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  }
147
 
148
 
149
  /**
150
  * @since 2.0.0
151
  * @since 3.5.0 Refactored for unit testing
 
 
152
  */
153
  function authenticate($user, $username, $password)
154
  {
@@ -168,9 +188,9 @@ function authenticate($user, $username, $password)
168
  }
169
 
170
  return $user;
171
- }
172
  if (defined('WP_FAIL2BAN_BLOCKED_USERS')) {
173
- add_filter('authenticate', __NAMESPACE__.'\authenticate', 1, 3);
174
  }
175
 
176
 
@@ -178,6 +198,8 @@ if (defined('WP_FAIL2BAN_BLOCKED_USERS')) {
178
  * @since 2.1.0
179
  * @since 3.5.0 Refactored for unit testing
180
  * @since 3.5.1 Check is_admin
 
 
181
  */
182
  if (defined('WP_FAIL2BAN_BLOCK_USER_ENUMERATION') && true === WP_FAIL2BAN_BLOCK_USER_ENUMERATION) {
183
  function parse_request($query)
@@ -190,23 +212,7 @@ if (defined('WP_FAIL2BAN_BLOCK_USER_ENUMERATION') && true === WP_FAIL2BAN_BLOCK_
190
 
191
  return $query;
192
  }
193
- add_filter('parse_request', __NAMESPACE__.'\parse_request', 1, 2);
194
- }
195
-
196
-
197
- /**
198
- * @since 2.2.0
199
- * @since 3.5.0 Refactored for unit testing
200
- */
201
- if (defined('WP_FAIL2BAN_LOG_PINGBACKS') && true === WP_FAIL2BAN_LOG_PINGBACKS) {
202
- function xmlrpc_call($call)
203
- {
204
- if ('pingback.ping' == $call) {
205
- openlog('WP_FAIL2BAN_PINGBACK_LOG');
206
- syslog(LOG_INFO, 'Pingback requested');
207
- }
208
- }
209
- add_action('xmlrpc_call', __NAMESPACE__.'\xmlrpc_call');
210
  }
211
 
212
 
@@ -227,27 +233,29 @@ if (defined('WP_FAIL2BAN_LOG_COMMENTS') && true === WP_FAIL2BAN_LOG_COMMENTS) {
227
 
228
  /**
229
  * @since 3.5.0
 
 
230
  */
231
  if (defined('WP_FAIL2BAN_LOG_SPAM') && true === WP_FAIL2BAN_LOG_SPAM) {
232
- function log_spam_comment($comment_id, $comment_status)
233
  {
234
- if ('spam' === $comment_status) {
235
- if (is_null($comment = get_comment($comment_id, ARRAY_A))) {
236
- /**
237
  * @todo: decide what to do about this
238
  */
239
- } else {
240
- $remote_addr = (empty($comment['comment_author_IP']))
241
- ? 'unknown'
242
- : $comment['comment_author_IP'];
243
-
244
- openlog();
245
- syslog(LOG_INFO, "Spam comment {$comment_id}", $remote_addr);
246
- }
247
- }
248
- };
249
- add_action('comment_post', __NAMESPACE__.'\log_spam_comment', 10, 2);
250
- add_action('wp_set_comment_status', __NAMESPACE__.'\log_spam_comment', 10, 2);
251
  }
252
 
253
 
@@ -260,7 +268,77 @@ if (defined('WP_FAIL2BAN_LOG_PASSWORD_REQUEST') && true === WP_FAIL2BAN_LOG_PASS
260
  openlog();
261
  syslog(LOG_NOTICE, "Password reset requested for {$user_login}");
262
  }
263
- add_action('retrieve_password', __NAMESPACE__.'\retrieve_password');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  }
265
 
266
 
@@ -279,6 +357,11 @@ add_action('wp_login', __NAMESPACE__.'\wp_login', 10, 2);
279
  /**
280
  * @since 1.0.0
281
  * @since 3.5.0 Refactored for unit testing
 
 
 
 
 
282
  */
283
  function wp_login_failed($username)
284
  {
@@ -295,33 +378,3 @@ function wp_login_failed($username)
295
  }
296
  add_action('wp_login_failed', __NAMESPACE__.'\wp_login_failed');
297
 
298
-
299
- /**
300
- * @since 3.0.0
301
- * @since 3.5.0 Refactored for unit testing
302
- */
303
- function xmlrpc_login_error($error, $user)
304
- {
305
- static $attempts = 0;
306
-
307
- if (++$attempts > 1) {
308
- openlog();
309
- syslog(LOG_NOTICE, 'XML-RPC multicall authentication failure');
310
- bail();
311
- }
312
- }
313
- add_action('xmlrpc_login_error', __NAMESPACE__.'\xmlrpc_login_error', 10, 2);
314
-
315
-
316
- /**
317
- * @since 3.0.0
318
- * @since 3.5.0 Refactored for unit testing
319
- */
320
- function xmlrpc_pingback_error($ixr_error)
321
- {
322
- if (48 === $ixr_error->code)
323
- return $ixr_error;
324
- openlog();
325
- syslog(LOG_NOTICE, 'Pingback error '.$ixr_error->code.' generated');
326
- }
327
- add_filter('xmlrpc_pingback_error', __NAMESPACE__.'\xmlrpc_pingback_error', 5);
1
  <?php
2
+ /*
3
  * Plugin Name: WP fail2ban
4
+ * Plugin URI: https://github.com/invisnet/wp-fail2ban
5
+ * Description: Write a myriad of WordPress events to syslog for integration with fail2ban.
6
  * Text Domain: wp-fail2ban
7
+ * Version: 3.6.0
8
  * Author: Charles Lecklider
9
  * Author URI: https://charles.lecklider.org/
10
  * License: GPL2
11
  * SPDX-License-Identifier: GPL-2.0
12
  */
13
 
14
+ /*
15
+ * Copyright 2012-18 Charles Lecklider (email : wordpress@charles.lecklider.org)
16
  *
17
  * This program is free software; you can redistribute it and/or modify
18
  * it under the terms of the GNU General Public License, version 2, as
36
  */
37
  global $wp_fail2ban;
38
  if (empty($wp_fail2ban) && defined('WP_FAIL2BAN')) return;
39
+ define('WP_FAIL2BAN', true);
40
 
41
 
42
  /**
43
+ * Allow custom openlog options.
44
+ * e.g. you may not want the PID if logging remotely.
45
+ * @since 3.5.0
46
+ * @since 3.6.0 Add LOG_NDELAY
47
  */
48
  if (!defined('WP_FAIL2BAN_OPENLOG_OPTIONS')) {
49
+ define('WP_FAIL2BAN_OPENLOG_OPTIONS', LOG_PID|LOG_NDELAY);
50
  }
51
  /**
52
  * Make sure all custom logs are defined.
68
  */
69
  function openlog($log = 'WP_FAIL2BAN_AUTH_LOG')
70
  {
71
+ $tag = (defined('WP_FAIL2BAN_SYSLOG_SHORT_TAG') && true === WP_FAIL2BAN_SYSLOG_SHORT_TAG)
72
+ ? 'wp' // @codeCoverageIgnore
73
+ : 'wordpress';
74
+ $host = (array_key_exists('WP_FAIL2BAN_HTTP_HOST', $_ENV))
75
+ ? $_ENV['WP_FAIL2BAN_HTTP_HOST'] // @codeCoverageIgnore
76
+ : $_SERVER['HTTP_HOST'];
77
  /**
78
  * Some varieties of syslogd have difficulty if $host is too long
79
  * @since 3.5.0
81
  if (defined('WP_FAIL2BAN_TRUNCATE_HOST') && 1 < intval(WP_FAIL2BAN_TRUNCATE_HOST)) {
82
  $host = substr($host, 0, intval(WP_FAIL2BAN_TRUNCATE_HOST));
83
  }
84
+ if (false === \openlog("$tag($host)", WP_FAIL2BAN_OPENLOG_OPTIONS, constant($log))) {
85
+ error_log('WPf2b: Cannot open syslog', 0); // @codeCoverageIgnore
86
+ } elseif (defined('WP_FAIL2BAN_TRACE')) {
87
+ error_log('WPf2b: Opened syslog', 0); // @codeCoverageIgnore
88
  }
89
  }
90
 
99
  : $remote_addr;
100
 
101
  if (false === \syslog($level, $msg)) {
102
+ error_log("WPf2b: Cannot write to syslog: '{$msg}'", 0); // @codeCoverageIgnore
103
+ } elseif (defined('WP_FAIL2BAN_TRACE')) {
104
+ error_log("WPf2b: Wrote to syslog: '{$msg}'", 0); // @codeCoverageIgnore
105
  }
106
  \closelog();
107
 
118
  */
119
  function bail()
120
  {
121
+ wp_die('Forbidden', 'Forbidden', array('response' => 403));
122
  }
123
 
124
+ /**
125
+ * @todo Test me!
126
+ * @codeCoverageIgnore
127
+ */
128
  function remote_addr()
129
  {
130
+ if (defined('WP_FAIL2BAN_PROXIES')) {
131
+ if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
132
+ $ip = ip2long($_SERVER['REMOTE_ADDR']);
133
+ /**
134
+ * PHP 7 lets you define an array
135
+ * @since 3.5.4
136
+ */
137
+ $proxies = (is_array(WP_FAIL2BAN_PROXIES))
138
+ ? WP_FAIL2BAN_PROXIES
139
+ : explode(',', WP_FAIL2BAN_PROXIES);
140
+ foreach ($proxies as $proxy) {
141
+ if (2 == count($cidr = explode('/', $proxy))) {
142
+ $net = ip2long($cidr[0]);
143
+ $mask = ~ ( pow(2, (32 - $cidr[1])) - 1 );
144
+ } else {
145
+ $net = ip2long($proxy);
146
+ $mask = -1;
147
+ }
148
+ if ($net == ($ip & $mask)) {
149
+ return (false === ($len = strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',')))
150
+ ? $_SERVER['HTTP_X_FORWARDED_FOR']
151
+ : substr($_SERVER['HTTP_X_FORWARDED_FOR'], 0, $len);
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ /**
158
+ * For plugins and themes that anonymise requests
159
+ * @since 3.6.0
160
+ */
161
+ return (defined('WP_FAIL2BAN_REMOTE_ADDR'))
162
+ ? WP_FAIL2BAN_REMOTE_ADDR
163
+ : $_SERVER['REMOTE_ADDR'];
164
  }
165
 
166
 
167
  /**
168
  * @since 2.0.0
169
  * @since 3.5.0 Refactored for unit testing
170
+ *
171
+ * @wp-f2b-hard Blocked authentication attempt for .*
172
  */
173
  function authenticate($user, $username, $password)
174
  {
188
  }
189
 
190
  return $user;
191
+ }
192
  if (defined('WP_FAIL2BAN_BLOCKED_USERS')) {
193
+ add_filter('authenticate', __NAMESPACE__.'\authenticate', 1, 3);
194
  }
195
 
196
 
198
  * @since 2.1.0
199
  * @since 3.5.0 Refactored for unit testing
200
  * @since 3.5.1 Check is_admin
201
+ *
202
+ * @wp-f2b-hard Blocked user enumeration attempt
203
  */
204
  if (defined('WP_FAIL2BAN_BLOCK_USER_ENUMERATION') && true === WP_FAIL2BAN_BLOCK_USER_ENUMERATION) {
205
  function parse_request($query)
212
 
213
  return $query;
214
  }
215
+ add_filter('parse_request', __NAMESPACE__.'\parse_request', 1, 2);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  }
217
 
218
 
233
 
234
  /**
235
  * @since 3.5.0
236
+ *
237
+ * @wp-f2b-hard Spam comment \d+
238
  */
239
  if (defined('WP_FAIL2BAN_LOG_SPAM') && true === WP_FAIL2BAN_LOG_SPAM) {
240
+ function log_spam_comment($comment_id, $comment_status)
241
  {
242
+ if ('spam' === $comment_status) {
243
+ if (is_null($comment = get_comment($comment_id, ARRAY_A))) {
244
+ /**
245
  * @todo: decide what to do about this
246
  */
247
+ } else {
248
+ $remote_addr = (empty($comment['comment_author_IP']))
249
+ ? 'unknown' // @codeCoverageIgnore
250
+ : $comment['comment_author_IP'];
251
+
252
+ openlog();
253
+ syslog(LOG_INFO, "Spam comment {$comment_id}", $remote_addr);
254
+ }
255
+ }
256
+ };
257
+ add_action('comment_post', __NAMESPACE__.'\log_spam_comment', 10, 2);
258
+ add_action('wp_set_comment_status', __NAMESPACE__.'\log_spam_comment', 10, 2);
259
  }
260
 
261
 
268
  openlog();
269
  syslog(LOG_NOTICE, "Password reset requested for {$user_login}");
270
  }
271
+ add_action('retrieve_password', __NAMESPACE__.'\retrieve_password');
272
+ }
273
+
274
+
275
+ if (defined('XMLRPC_REQUEST') && true === XMLRPC_REQUEST) {
276
+ /**
277
+ * @since 3.0.0
278
+ * @since 3.5.0 Refactored for unit testing
279
+ *
280
+ * @wp-f2b-hard XML-RPC multicall authentication failure
281
+ */
282
+ function xmlrpc_login_error($error, $user)
283
+ {
284
+ static $attempts = 0;
285
+
286
+ if (++$attempts > 1) {
287
+ openlog();
288
+ syslog(LOG_NOTICE, 'XML-RPC multicall authentication failure');
289
+ bail();
290
+ }
291
+ }
292
+ add_action('xmlrpc_login_error', __NAMESPACE__.'\xmlrpc_login_error', 10, 2);
293
+
294
+
295
+ /**
296
+ * @since 3.0.0
297
+ * @since 3.5.0 Refactored for unit testing
298
+ *
299
+ * @wp-f2b-hard Pingback error .* generated
300
+ */
301
+ function xmlrpc_pingback_error($ixr_error)
302
+ {
303
+ if (48 === $ixr_error->code) return $ixr_error; // @codeCoverageIgnore
304
+ openlog();
305
+ syslog(LOG_NOTICE, 'Pingback error '.$ixr_error->code.' generated');
306
+ }
307
+ add_filter('xmlrpc_pingback_error', __NAMESPACE__.'\xmlrpc_pingback_error', 5);
308
+
309
+
310
+ /**
311
+ * @since 2.2.0
312
+ * @since 3.5.0 Refactored for unit testing
313
+ */
314
+ if (defined('WP_FAIL2BAN_LOG_PINGBACKS') && true === WP_FAIL2BAN_LOG_PINGBACKS) {
315
+ function xmlrpc_call($call)
316
+ {
317
+ if ('pingback.ping' == $call) {
318
+ openlog('WP_FAIL2BAN_PINGBACK_LOG');
319
+ syslog(LOG_INFO, 'Pingback requested');
320
+ }
321
+ }
322
+ add_action('xmlrpc_call', __NAMESPACE__.'\xmlrpc_call');
323
+ }
324
+
325
+
326
+ /**
327
+ * Log XML-RPC requests
328
+ *
329
+ * It seems attackers are doing weird things with XML-RPC. This makes it easy to
330
+ * log them for analysis and future blocking.
331
+ *
332
+ * @since 3.6.0
333
+ */
334
+ if (defined('WP_FAIL2BAN_XMLRPC_LOG')) {
335
+ if (false === ($fp = fopen(WP_FAIL2BAN_XMLRPC_LOG, 'a+'))) {
336
+ // TODO: decided whether to log this
337
+ } else {
338
+ fprintf($fp, "# ---\n# Date: %s\n# IP: %s\n\n%s\n", date(DATE_ATOM), remote_addr(), $HTTP_RAW_POST_DATA);
339
+ fclose($fp);
340
+ }
341
+ }
342
  }
343
 
344
 
357
  /**
358
  * @since 1.0.0
359
  * @since 3.5.0 Refactored for unit testing
360
+ *
361
+ * @wp-f2b-hard Authentication attempt for unknown user .*
362
+ * @wp-f2b-hard XML-RPC authentication attempt for unknown user .*
363
+ * @wp-f2b-soft Authentication failure for .*
364
+ * @wp-f2b-soft XML-RPC authentication failure for .*
365
  */
366
  function wp_login_failed($username)
367
  {
378
  }
379
  add_action('wp_login_failed', __NAMESPACE__.'\wp_login_failed');
380