From 3a8e77323e705e4652076412a91f6eece41227c7 Mon Sep 17 00:00:00 2001 From: Mauro Torrez Date: Tue, 17 Sep 2019 18:28:38 -0300 Subject: [PATCH] initial import from https://downloads.wordpress.org/plugin/wp-fail2ban.4.2.5.zip --- .gitignore | 3 + admin/admin.php | 66 +++++++ admin/config.php | 69 +++++++ admin/config/block.php | 120 ++++++++++++ admin/config/logging.php | 255 +++++++++++++++++++++++++ admin/config/plugins.php | 185 +++++++++++++++++++ admin/config/remote-ips.php | 100 ++++++++++ admin/config/syslog.php | 159 ++++++++++++++++ admin/img/docs.svg | 6 + admin/lib/about.php | 143 ++++++++++++++ admin/lib/tab.php | 260 ++++++++++++++++++++++++++ feature/comments.php | 193 +++++++++++++++++++ feature/lib.php | 144 +++++++++++++++ feature/password.php | 37 ++++ feature/plugins.php | 233 +++++++++++++++++++++++ feature/spam.php | 60 ++++++ feature/user-enum.php | 104 +++++++++++ feature/user.php | 61 ++++++ feature/xmlrpc.php | 108 +++++++++++ feature/xmlrpc/log.php | 35 ++++ feature/xmlrpc/pingback.php | 40 ++++ filters.d/wordpress-extra.conf | 27 +++ filters.d/wordpress-hard.conf | 28 +++ filters.d/wordpress-soft.conf | 23 +++ lib/constants.php | 144 +++++++++++++++ lib/defaults.php | 79 ++++++++ lib/loader.php | 327 +++++++++++++++++++++++++++++++++ readme.txt | 280 ++++++++++++++++++++++++++++ wp-fail2ban-main.php | 254 +++++++++++++++++++++++++ wp-fail2ban.php | 105 +++++++++++ 30 files changed, 3648 insertions(+) create mode 100644 .gitignore create mode 100644 admin/admin.php create mode 100644 admin/config.php create mode 100644 admin/config/block.php create mode 100644 admin/config/logging.php create mode 100644 admin/config/plugins.php create mode 100644 admin/config/remote-ips.php create mode 100644 admin/config/syslog.php create mode 100644 admin/img/docs.svg create mode 100644 admin/lib/about.php create mode 100644 admin/lib/tab.php create mode 100644 feature/comments.php create mode 100644 feature/lib.php create mode 100644 feature/password.php create mode 100644 feature/plugins.php create mode 100644 feature/spam.php create mode 100644 feature/user-enum.php create mode 100644 feature/user.php create mode 100644 feature/xmlrpc.php create mode 100644 feature/xmlrpc/log.php create mode 100644 feature/xmlrpc/pingback.php create mode 100644 filters.d/wordpress-extra.conf create mode 100644 filters.d/wordpress-hard.conf create mode 100644 filters.d/wordpress-soft.conf create mode 100644 lib/constants.php create mode 100644 lib/defaults.php create mode 100644 lib/loader.php create mode 100644 readme.txt create mode 100644 wp-fail2ban-main.php create mode 100644 wp-fail2ban.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..adac7f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*~ +\#* +.#* diff --git a/admin/admin.php b/admin/admin.php new file mode 100644 index 0000000..6d33f6d --- /dev/null +++ b/admin/admin.php @@ -0,0 +1,66 @@ +Settings' ); + } + return $links; +} + +add_filter( + 'plugin_action_links', + __NAMESPACE__ . '\\plugin_action_links', + 10, + 2 +); \ No newline at end of file diff --git a/admin/config.php b/admin/config.php new file mode 100644 index 0000000..1a746b3 --- /dev/null +++ b/admin/config.php @@ -0,0 +1,69 @@ + +
+

+
+ + + +
+render(); + echo '

' . __( 'Note: The Free version of WP fail2ban is configured by defining constants in wp-config.php; these tabs display those values.
Upgrade to the Premium version to enable this interface.' ) . '

' ; + ?> +
+
+ ', checked( WP_FAIL2BAN_BLOCK_USER_ENUMERATION, true, false ) ); + } + + /** + * Blocked usernames + * + * @since 4.0.0 + */ + public function usernames() + { + + if ( defined( 'WP_FAIL2BAN_BLOCKED_USERS' ) ) { + + if ( is_array( WP_FAIL2BAN_BLOCKED_USERS ) ) { + $value = join( ', ', WP_FAIL2BAN_BLOCKED_USERS ); + } else { + $value = WP_FAIL2BAN_BLOCKED_USERS; + } + + } else { + $value = ''; + } + + printf( '', esc_attr( $value ) ); + } + +} +new TabBlock(); \ No newline at end of file diff --git a/admin/config/logging.php b/admin/config/logging.php new file mode 100644 index 0000000..a83f031 --- /dev/null +++ b/admin/config/logging.php @@ -0,0 +1,255 @@ +%s: %s', __( 'Use facility' ), $this->getLogFacilities( 'WP_FAIL2BAN_AUTH_LOG', true ) ); + } + + /** + * Comments. + * + * @since 4.0.0 + */ + public function comments() + { + add_filter( + 'wp_fail2ban_log_WP_FAIL2BAN_LOG_COMMENTS', + [ $this, 'commentsExtra' ], + 10, + 3 + ); + $this->log( + 'WP_FAIL2BAN_LOG_COMMENTS', + 'WP_FAIL2BAN_COMMENT_LOG', + '', + [ 'comments-extra', 'logging-comments-extra-facility' ] + ); + } + + /** + * Comments extra helper - checked. + * + * @since 4.0.0 + * + * @param int $value Value to check + */ + protected function commentExtraChecked( $value ) + { + if ( !defined( 'WP_FAIL2BAN_LOG_COMMENTS_EXTRA' ) ) { + return ''; + } + return checked( $value & WP_FAIL2BAN_LOG_COMMENTS_EXTRA, $value, false ); + } + + /** + * Comments extra helper - disabled. + * + * @since 4.0.0 + */ + protected function commentExtraDisabled() + { + return 'disabled="disabled'; + } + + /** + * Comments extra. + * + * @since 4.0.0 + * + * @param string $html HTML prefixed to output + * @param string $define_name Not used + * @param string $define_log Not used + * + * @return string + */ + public function commentsExtra( $html, $define_name, $define_log ) + { + $fmt = <<<___HTML___ + + + + + + + + + +
%s +
+
+
+
+
+ +
+
%s%s
+___HTML___; + return $html . sprintf( + $fmt, + parent::doc_link( 'WP_FAIL2BAN_LOG_COMMENTS_EXTRA', __( 'Also log:' ) ), + $this->commentExtraChecked( WPF2B_EVENT_COMMENT_NOT_FOUND ), + __( 'Post not found' ), + $this->commentExtraChecked( WPF2B_EVENT_COMMENT_CLOSED ), + __( 'Comments closed' ), + $this->commentExtraChecked( WPF2B_EVENT_COMMENT_TRASH ), + __( 'Trash post' ), + $this->commentExtraChecked( WPF2B_EVENT_COMMENT_DRAFT ), + __( 'Draft post' ), + $this->commentExtraChecked( WPF2B_EVENT_COMMENT_PASSWORD ), + __( 'Password-protected post' ), + parent::doc_link( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG', __( 'Use facility:' ) ), + $this->getLogFacilities( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG', false ) + ); + } + + /** + * Password request + * + * @since 4.0.0 + */ + public function passwordRequest() + { + $this->log( 'WP_FAIL2BAN_LOG_PASSWORD_REQUEST', 'WP_FAIL2BAN_PASSWORD_REQUEST_LOG' ); + } + + /** + * Pingbacks + * + * @since 4.0.0 + */ + public function pingbacks() + { + $this->log( 'WP_FAIL2BAN_LOG_PINGBACKS', 'WP_FAIL2BAN_PINGBACK_LOG' ); + } + + /** + * Spam + * + * @since 4.0.0 + */ + public function spam() + { + $this->log( 'WP_FAIL2BAN_LOG_SPAM', 'WP_FAIL2BAN_SPAM_LOG' ); + } + +} +new TabLogging(); \ No newline at end of file diff --git a/admin/config/plugins.php b/admin/config/plugins.php new file mode 100644 index 0000000..cc3ec62 --- /dev/null +++ b/admin/config/plugins.php @@ -0,0 +1,185 @@ +log( 'WP_FAIL2BAN_PLUGIN_LOG_AUTH', 'WP_FAIL2BAN_PLUGIN_AUTH_LOG' ); + } + + /** + * Comment + * + * @since 4.2.0 + */ + public function comment() + { + $this->log( 'WP_FAIL2BAN_PLUGIN_LOG_COMMENT', 'WP_FAIL2BAN_PLUGIN_COMMENT_LOG' ); + } + + /** + * Password + * + * @since 4.2.0 + */ + public function password() + { + $this->log( 'WP_FAIL2BAN_PLUGIN_LOG_PASSWORD', 'WP_FAIL2BAN_PLUGIN_PASSWORD_LOG' ); + } + + /** + * REST + * + * @since 4.2.0 + */ + public function rest() + { + $this->log( 'WP_FAIL2BAN_PLUGIN_LOG_REST', 'WP_FAIL2BAN_PLUGIN_REST_LOG' ); + } + + /** + * Spam + * + * @since 4.2.0 + */ + public function spam() + { + $this->log( 'WP_FAIL2BAN_PLUGIN_LOG_SPAM', 'WP_FAIL2BAN_PLUGIN_SPAM_LOG' ); + } + + /** + * XML-RPC + * + * @since 4.2.0 + */ + public function xmlrpc() + { + $this->log( 'WP_FAIL2BAN_PLUGIN_LOG_XMLRPC', 'WP_FAIL2BAN_PLUGIN_XMLRPC_LOG' ); + } + +} +new TabPlugins(); \ No newline at end of file diff --git a/admin/config/remote-ips.php b/admin/config/remote-ips.php new file mode 100644 index 0000000..fae12fa --- /dev/null +++ b/admin/config/remote-ips.php @@ -0,0 +1,100 @@ +', esc_html( $value ) ); + } + +} +new TabRemoteIPs(); \ No newline at end of file diff --git a/admin/config/syslog.php b/admin/config/syslog.php new file mode 100644 index 0000000..bc917a4 --- /dev/null +++ b/admin/config/syslog.php @@ -0,0 +1,159 @@ +syslog' ); + } + + /** + * {@inheritDoc} + * + * @since 4.0.0 + */ + public function admin_init() + { + // phpcs:disable Generic.Functions.FunctionCallArgumentSpacing + add_settings_section( + 'wp-fail2ban-connection', + __( 'Connection' ), + [ $this, 'sectionConnection' ], + 'wp-fail2ban-syslog' + ); + add_settings_field( + 'logging-connection', + parent::doc_link( 'WP_FAIL2BAN_OPENLOG_OPTIONS', __( 'Options' ) ), + [ $this, 'connection' ], + 'wp-fail2ban-syslog', + 'wp-fail2ban-connection' + ); + add_settings_section( + 'wp-fail2ban-workarounds', + __( 'Workarounds' ), + [ $this, 'sectionWorkarounds' ], + 'wp-fail2ban-syslog' + ); + add_settings_field( + 'logging-workarounds', + parent::doc_link( '../syslog', __( 'Options' ) ), + [ $this, 'workarounds' ], + 'wp-fail2ban-syslog', + 'wp-fail2ban-workarounds' + ); + // phpcs:enable + } + + /** + * {@inheritDoc} + * + * @since 4.0.0 + * + * @param array $settings {@inheritDoc} + * @param array $input {@inheritDoc} + * + * @return array {@inheritDoc} + */ + public function sanitize( array $settings, array $input = null ) + { + return $settings; + } + + /** + * Connection section blurb. + * + * @since 4.0.0 + */ + public function sectionConnection() + { + echo '' ; + } + + /** + * Connection. + * + * @since 4.0.0 + */ + public function connection() + { + $class = ''; + $fmt = <<<___STR___ +
+
+
+
+
+ +
+___STR___; + printf( + $fmt, + checked( WP_FAIL2BAN_OPENLOG_OPTIONS & LOG_CONS, LOG_CONS, false ), + checked( WP_FAIL2BAN_OPENLOG_OPTIONS & LOG_PERROR, LOG_PERROR, false ), + checked( WP_FAIL2BAN_OPENLOG_OPTIONS & LOG_PID, LOG_PID, false ), + __( 'default' ), + checked( WP_FAIL2BAN_OPENLOG_OPTIONS & LOG_NDELAY, LOG_NDELAY, false ), + __( 'default' ), + checked( WP_FAIL2BAN_OPENLOG_OPTIONS & LOG_ODELAY, LOG_ODELAY, false ) + ); + } + + /** + * Workarounds section blurb. + * + * @since 4.0.0 + */ + public function sectionWorkarounds() + { + echo '' ; + } + + /** + * Workarounds. + * + * @since 4.0.0 + */ + public function workarounds() + { + $fmt = <<<___STR___ +
+ +
+ +
+ +
+___STR___; + printf( + $fmt, + checked( @WP_FAIL2BAN_SYSLOG_SHORT_TAG, true, false ), + __( 'Short Tag' ), + checked( @WP_FAIL2BAN_HTTP_HOST, true, false ), + __( 'Specify Host' ), + checked( @WP_FAIL2BAN_TRUNCATE_HOST, true, false ), + __( 'Truncate Host' ) + ); + } + +} +new TabSyslog(); \ No newline at end of file diff --git a/admin/img/docs.svg b/admin/img/docs.svg new file mode 100644 index 0000000..28fe722 --- /dev/null +++ b/admin/img/docs.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/admin/lib/about.php b/admin/lib/about.php new file mode 100644 index 0000000..e8cbdda --- /dev/null +++ b/admin/lib/about.php @@ -0,0 +1,143 @@ + +
+ + +

WP fail2ban

+ +
+
+
+
+
+

Version 4.2.5

+
+
    +
  • Properly fix PHP 5.3 support; tested on CentOS 6. Does not support any UI or Premium features.
  • +
  • Fix potential issue with WP_FAIL2BAN_BLOCK_USER_ENUMERATION if calling REST API or XMLRPC from admin area.
  • +
+
+
+
+
+
+

Version 4.2.4

+
+
    +
  • Add filter for login failed message.
  • +
  • Fix logging spam comments from admin area.
  • +
  • Fix Settings link from Plugins page.
  • +
  • Update Freemius library.
  • +
+
+
+
+
+
+

Version 4.2.3

+
+
    +
  • Workaround for some versions of PHP 7.x that would cause define()s to be ignored.
  • +
  • Add config note to settings tabs.
  • +
  • Fix documentation links.
  • +
+
+
+
+
+
+

Version 4.2.2

+
+
    +
  • Fix 5.3 compatibility.
  • +
+
+
+
+
+
+

Version 4.2.1

+
+
    +
  • Completed support for WP_FAIL2BAN_COMMENT_EXTRA_LOG.
  • +
  • Add support for 3rd-party plugins; see Developers.
    +

    +
  • +
  • Change logging for known-user with incorrect password; previously logged as unknown user and matched by hard filters (due to limitations in older versions of WordPress), now logged as known user and matched by soft.
  • +
  • Bugfix for email-as-username - now logged correctly and matched by soft, not hard, filters.
  • +
  • Bugfix for regression in code to prevent Free/Premium conflict.
  • +
+
+
+
+
+
+
+
+

Getting Started

+ +
+
+

Getting Help

+
+
+
+
+
+
+   +
+
+tab_slug = $slug; + $this->tab_name = $name; + self::$tabs[$slug] = $this; + } + + /** + * Getter - slug + * + * @since 4.0.0 + * + * @return string Tab slug + */ + public function getSlug() + { + return $this->tab_slug; + } + + /** + * Getter - name + * + * @since 4.0.0 + * + * @return string Tab name + */ + public function getName() + { + return $this->tab_name; + } + + /** + * Render settings section + * + * @since 4.0.0 + */ + public function render() + { + do_settings_sections( 'wp-fail2ban-' . $this->tab_slug ); + } + + /** + * Helper - tab + * + * @since 4.0.0 + * + * @param string $slug Tab slug + * + * @return Tab Tab + */ + public static function getTab( $slug ) + { + return self::$tabs[$slug]; + } + + /** + * Helper - current tab + * + * @since 4.0.0 + * + * @param string $default Default slug + * + * @return Tab Tab + */ + public static function getActiveTab( $default = null ) + { + if ( !empty(self::$active_tab) ) { + return self::$active_tab; + } + return self::$active_tab = ( array_key_exists( @$_GET['tab'], self::$tabs ) ? self::$tabs[$_GET['tab']] : self::$tabs[$default] ); + } + + /** + * Helper - tab name + * + * @since 4.0.0 + * + * @param string $slug Tab slug + * + * @return string Tab name + */ + public static function getTabName( $slug ) + { + return self::getTab( $slug )->getName(); + } + + /** + * Link to documentation + * + * @since 4.2.0 + * + * @param string $define + * @param string $name + * + * @return string + */ + public static function doc_link( $define, $name ) + { + static $wp_f2b_ver ; + if ( empty($wp_f2b_ver) ) { + $wp_f2b_ver = substr( WP_FAIL2BAN_VER, 0, strrpos( WP_FAIL2BAN_VER, '.' ) ); + } + return sprintf( + ' %s', + $wp_f2b_ver, + $define, + $name + ); + } + + /** + * Helper - drop-down list of facilities + * + * @since 4.0.0 + * + * @param string $def Name of define for selected value + * @param bool $_enabled Enabled? + */ + protected function getLogFacilities( $def, $_enabled = false ) + { + $enabled = false; + $facilities = [ + LOG_AUTH => 'LOG_AUTH', + LOG_AUTHPRIV => 'LOG_AUTHPRIV', + LOG_CRON => 'LOG_CRON', + LOG_DAEMON => 'LOG_DAEMON', + LOG_KERN => 'LOG_KERN', + LOG_LOCAL0 => 'LOG_LOCAL0', + LOG_LOCAL1 => 'LOG_LOCAL1', + LOG_LOCAL2 => 'LOG_LOCAL2', + LOG_LOCAL3 => 'LOG_LOCAL3', + LOG_LOCAL4 => 'LOG_LOCAL4', + LOG_LOCAL5 => 'LOG_LOCAL5', + LOG_LOCAL6 => 'LOG_LOCAL6', + LOG_LOCAL7 => 'LOG_LOCAL7', + LOG_LPR => 'LOG_LPR', + LOG_MAIL => 'LOG_MAIL', + LOG_NEWS => 'LOG_NEWS', + LOG_SYSLOG => 'LOG_SYSLOG', + LOG_USER => 'LOG_USER', + LOG_UUCP => 'LOG_UUCP', + ]; + $default = constant( "DEFAULT_{$def}" ); + $value = ( defined( $def ) ? constant( $def ) : $default ); + $str = ''; + return $str; + } + + /** + * Log helper - enable/disable+facility + * + * @since 4.2.0 Moved to Tab + * @since 4.0.0 + * + * @param string $define_name Name of define to enable logging + * @param string $define_log Name of define for log facility + * @param string $description Description + * @param array $toggle Array of IDs to sync toggle state + */ + protected function log( + $define_name, + $define_log, + $description = '', + array $toggle = array() + ) + { + $enabled = defined( $define_name ) && true === constant( $define_name ); + $fmt = <<<___FMT___ +, + +

%s

+___FMT___; + $html = sprintf( + $fmt, + checked( $enabled, true, false ), + $this->getLogFacilities( $define_log ), + $description + ); + echo apply_filters( + "wp_fail2ban_log_{$define_name}", + $html, + $define_name, + $define_log + ) ; + } + +} \ No newline at end of file diff --git a/feature/comments.php b/feature/comments.php new file mode 100644 index 0000000..1bf994b --- /dev/null +++ b/feature/comments.php @@ -0,0 +1,193 @@ + 403, + ) ); + } + +} + +/** + * Compute remote IP address + * + * @return string + * + * @todo Test me! + * @codeCoverageIgnore + */ +function remote_addr() +{ + static $remote_addr = null ; + /** + * @since 4.0.0 + */ + + if ( is_null( $remote_addr ) ) { + if ( defined( 'WP_FAIL2BAN_PROXIES' ) ) { + + if ( array_key_exists( 'HTTP_X_FORWARDED_FOR', $_SERVER ) ) { + $ip = ip2long( $_SERVER['REMOTE_ADDR'] ); + /** + * PHP 7 lets you define an array + * @since 3.5.4 + */ + $proxies = ( is_array( WP_FAIL2BAN_PROXIES ) ? WP_FAIL2BAN_PROXIES : explode( ',', WP_FAIL2BAN_PROXIES ) ); + foreach ( $proxies as $proxy ) { + + if ( '#' == $proxy[0] ) { + continue; + } elseif ( 2 == count( $cidr = explode( '/', $proxy ) ) ) { + $net = ip2long( $cidr[0] ); + $mask = ~(pow( 2, 32 - $cidr[1] ) - 1); + } else { + $net = ip2long( $proxy ); + $mask = -1; + } + + if ( $net == ($ip & $mask) ) { + return ( false === ($len = strpos( $_SERVER['HTTP_X_FORWARDED_FOR'], ',' )) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : substr( $_SERVER['HTTP_X_FORWARDED_FOR'], 0, $len ) ); + } + } + } + + } + /** + * For plugins and themes that anonymise requests + * @since 3.6.0 + */ + $remote_addr = ( defined( 'WP_FAIL2BAN_REMOTE_ADDR' ) ? WP_FAIL2BAN_REMOTE_ADDR : $_SERVER['REMOTE_ADDR'] ); + } + + return $remote_addr; +} diff --git a/feature/password.php b/feature/password.php new file mode 100644 index 0000000..9356939 --- /dev/null +++ b/feature/password.php @@ -0,0 +1,37 @@ + ++$id, + 'name' => $name, + 'messages' => [], + ]; +} + +add_action( + 'wp_fail2ban_register_plugin', + __NAMESPACE__ . '\\register_plugin', + 1, + 2 +); +/** + * Check if plugin is registered. + * + * @since 4.2.0 + * + * @param string $plugin_slug + * + * @return bool + */ +function is_registered_plugin( $plugin_slug ) +{ + global $wp_fail2ban ; + return array_key_exists( $plugin_slug, $wp_fail2ban['plugins'] ); +} + +/** + * Register plugin message. + * + * @since 4.2.0 + * + * @param string $plugin_slug + * @param array $msg [ + * string slug: Message slug + * string fail: hard|soft|extra + * int facility: syslog facility + * int priority: syslog priority + * string event_class: Event Class + * int event_id: Event ID + * string message: Message with placeholders + * HOST: Remote IP + * USER: Current user name + * array vars: Array of [name => regex] pairs + */ +function register_message( $plugin_slug, array $msg ) +{ + global $wp_fail2ban ; + $event_classes = [ + 'auth' => WPF2B_EVENT_CLASS_AUTH, + 'comment' => WPF2B_EVENT_CLASS_COMMENT, + 'password' => WPF2B_EVENT_CLASS_PASSWORD, + 'rest' => WPF2B_EVENT_CLASS_REST, + 'spam' => WPF2B_EVENT_CLASS_SPAM, + 'xmlrpc' => WPF2B_EVENT_CLASS_XMLRPC, + 'other' => 0, + ]; + $args = []; + if ( !is_registered_plugin( $plugin_slug ) ) { + throw new \InvalidArgumentException( 'plugin not registered' ); + } + if ( !array_key_exists( 'slug', $msg ) ) { + throw new \InvalidArgumentException( "Missing 'slug'" ); + } + if ( !is_string( $msg['slug'] ) ) { + throw new \InvalidArgumentException( "'slug' must be string" ); + } + if ( !array_key_exists( 'fail', $msg ) ) { + throw new \InvalidArgumentException( "Missing 'fail'" ); + } + if ( !in_array( $msg['fail'], [ 'hard', 'soft', 'extra' ] ) ) { + throw new \UnexpectedValueException( "'fail' must be one of 'hard', 'soft', 'extra'" ); + } + $args['fail'] = $msg['fail']; + if ( !array_key_exists( 'priority', $msg ) ) { + throw new \InvalidArgumentException( "Missing 'priority'" ); + } + if ( !in_array( $msg['priority'], [ + LOG_CRIT, + LOG_ERR, + LOG_WARNING, + LOG_NOTICE, + LOG_INFO, + LOG_DEBUG + ] ) ) { + throw new \UnexpectedValueException( "Invalid 'priority'" ); + } + $args['priority'] = $msg['priority']; + if ( !array_key_exists( 'event_class', $msg ) ) { + throw new \InvalidArgumentException( "Missing 'event_class'" ); + } + if ( !array_key_exists( $event_class = strtolower( $msg['event_class'] ), $event_classes ) ) { + throw new \UnexpectedValueException( "Invalid 'event_class'" ); + } + $args['class'] = $event_class; + $event_class = $event_classes[$event_class]; + $log = sprintf( "WP_FAIL2BAN_%s_LOG", strtoupper( $event_class ) ); + if ( !array_key_exists( 'event_id', $msg ) ) { + throw new \InvalidArgumentException( "Missing 'event_id'" ); + } + if ( ($msg['event_id'] & 0xffff) !== $msg['event_id'] ) { + throw new \UnexpectedValueException( "Invalid 'event_id'" ); + } + $args['event_id'] = WPF2B_EVENT_TYPE_PLUGIN | $event_class | $msg['event_id']; + if ( !array_key_exists( 'message', $msg ) ) { + throw new \InvalidArgumentException( "Missing 'message'" ); + } + if ( !is_string( $msg['message'] ) ) { + throw new \UnexpectedValueException( "Invalid 'message'" ); + } + $args['message'] = $msg['message']; + if ( !array_key_exists( 'vars', $msg ) ) { + throw new \InvalidArgumentException( "Missing 'vars'" ); + } + if ( !is_array( $msg['vars'] ) ) { + throw new \UnexpectedValueException( "Invalid 'vars'" ); + } + $args['vars'] = $msg['vars']; + $wp_fail2ban['plugins'][$plugin_slug]['messages'][$msg['slug']] = $args; +} + +add_action( + 'wp_fail2ban_register_message', + __NAMESPACE__ . '\\register_message', + 1, + 2 +); +/** + * Check if message is registered. + * + * NB: Assumes plugin is registered. + * + * @since 4.2.0 + * + * @param string $plugin_slug + * @param string $message_slug + * + * @return bool + */ +function is_registered_plugin_message( $plugin_slug, $message_slug ) +{ + global $wp_fail2ban ; + return array_key_exists( $message_slug, $wp_fail2ban['plugins'][$plugin_slug]['messages'] ); +} + +/** + * Log plugin message. + * + * @since 4.2.0 + * + * @param string $plugin_slug Plugin slug for registered message + * @param string $message_slug Message slug for registered message + * @param array $vars Substitution vars + */ +function log_message( $plugin_slug, $message_slug = null, array $vars = array() ) +{ + global $wp_fail2ban ; + if ( !is_registered_plugin( $plugin_slug ) ) { + throw new \InvalidArgumentException( 'plugin not registered' ); + } + if ( !is_registered_plugin_message( $plugin_slug, $message_slug ) ) { + throw new \InvalidArgumentException( 'message not registered' ); + } + $args = $wp_fail2ban['plugins'][$plugin_slug]['messages'][$message_slug]; + $msg = $args['message']; + foreach ( $args['vars'] as $name => $regex ) { + if ( array_key_exists( $name, $vars ) ) { + $msg = str_replace( "___{$name}___", $vars[$name], $msg ); + } + } + openlog( sprintf( 'WP_FAIL2BAN_PLUGIN_%s_LOG', strtoupper( $args['class'] ) ) ); + syslog( $args['priority'], "({$plugin_slug}) {$msg}" ); + closelog(); + // @codeCoverageIgnoreEnd +} + +add_action( + 'wp_fail2ban_log_message', + __NAMESPACE__ . '\\log_message', + 1, + 3 +); \ No newline at end of file diff --git a/feature/spam.php b/feature/spam.php new file mode 100644 index 0000000..61e32a7 --- /dev/null +++ b/feature/spam.php @@ -0,0 +1,60 @@ +query_vars['author'] ) ) { + _log_bail_user_enum(); + } + return $query; + } + + add_filter( + 'parse_request', + __NAMESPACE__ . '\\parse_request', + 1, + 2 + ); +} + +/** + * @since 4.0.5 Guard + */ + +if ( !function_exists( __NAMESPACE__ . '\\rest_user_query' ) ) { + /** + * Catch RESTful user list + * + * @see \WP_REST_Users_Controller::get_items() + * + * @since 4.0.0 + * + * @param array $prepared_args + * @param \WP_REST_Request $request + * + * @return array|\WP_Error + */ + function rest_user_query( $prepared_args, $request ) + { + if ( !current_user_can( 'list_users' ) ) { + return _log_bail_user_enum( true ); + } + return $prepared_args; + } + + add_filter( + 'rest_user_query', + __NAMESPACE__ . '\\rest_user_query', + 10, + 2 + ); +} diff --git a/feature/user.php b/feature/user.php new file mode 100644 index 0000000..f3aee00 --- /dev/null +++ b/feature/user.php @@ -0,0 +1,61 @@ + 1 ) { + openlog(); + syslog( LOG_NOTICE, 'XML-RPC multicall authentication failure' ); + closelog(); + // @codeCoverageIgnoreEnd + bail(); + } else { + return $error; + } + + } + + add_action( + 'xmlrpc_login_error', + __NAMESPACE__ . '\\xmlrpc_login_error', + 10, + 2 + ); +} + +/** + * @since 4.0.5 Guard + */ + +if ( !function_exists( __NAMESPACE__ . '\\xmlrpc_pingback_error' ) ) { + /** + * Catch failed pingbacks + * + * @see \wp_xmlrpc_server::pingback_error() + * + * @since 4.0.0 Return $ixr_error + * @since 3.5.0 Refactored for unit testing + * @since 3.0.0 + * + * @param \IXR_Error $ixr_error + * + * @return \IXR_Error + * + * @wp-f2b-hard Pingback error .* generated + */ + function xmlrpc_pingback_error( $ixr_error ) + { + + if ( 48 !== $ixr_error->code ) { + openlog(); + syslog( LOG_NOTICE, 'Pingback error ' . $ixr_error->code . ' generated' ); + closelog(); + // @codeCoverageIgnoreEnd + } + + return $ixr_error; + } + + add_filter( 'xmlrpc_pingback_error', __NAMESPACE__ . '\\xmlrpc_pingback_error', 5 ); +} + +/** + * @since 4.0.0 Refactored + * @since 2.2.0 + */ +if ( defined( 'WP_FAIL2BAN_LOG_PINGBACKS' ) && true === WP_FAIL2BAN_LOG_PINGBACKS ) { + require_once 'xmlrpc/pingback.php'; +} +/** + * @since 4.0.0 Refactored + * @since 3.6.0 + */ +if ( defined( 'WP_FAIL2BAN_XMLRPC_LOG' ) && '' < WP_FAIL2BAN_XMLRPC_LOG ) { + require_once 'xmlrpc/log.php'; +} \ No newline at end of file diff --git a/feature/xmlrpc/log.php b/feature/xmlrpc/log.php new file mode 100644 index 0000000..13ea15c --- /dev/null +++ b/feature/xmlrpc/log.php @@ -0,0 +1,35 @@ += 0) + ? file_get_contents('php://input') + : $HTTP_RAW_POST_DATA; + + fprintf($fp, "# ---\n# Date: %s\n# IP: %s\n\n%s\n", date(DATE_ATOM), remote_addr(), $raw_data); + fclose($fp); +} diff --git a/feature/xmlrpc/pingback.php b/feature/xmlrpc/pingback.php new file mode 100644 index 0000000..d340f1e --- /dev/null +++ b/feature/xmlrpc/pingback.php @@ -0,0 +1,40 @@ +$ + ^%(__prefix_line)sComment post not found \d+ from $ + ^%(__prefix_line)sComments closed on post \d+ from $ + ^%(__prefix_line)sComment attempt on trash post \d+ from $ + ^%(__prefix_line)sComment attempt on draft post \d+ from $ + ^%(__prefix_line)sComment attempt on password-protected post \d+ from $ + ^%(__prefix_line)sPassword reset requested for .* from $ + +ignoreregex = + +# DEV Notes: +# Requires the 'WP fail2ban' plugin: +# https://wp-fail2ban.com/ +# +# Author: Charles Lecklider diff --git a/filters.d/wordpress-hard.conf b/filters.d/wordpress-hard.conf new file mode 100644 index 0000000..eb6ac15 --- /dev/null +++ b/filters.d/wordpress-hard.conf @@ -0,0 +1,28 @@ +# Fail2Ban filter for WordPress hard failures +# Auto-generated: 2019-07-15T18:00:14+00:00 +# + +[INCLUDES] + +before = common.conf + +[Definition] + +_daemon = (?:wordpress|wp) + +failregex = ^%(__prefix_line)sAuthentication attempt for unknown user .* from $ + ^%(__prefix_line)sREST authentication attempt for unknown user .* from $ + ^%(__prefix_line)sXML-RPC authentication attempt for unknown user .* from $ + ^%(__prefix_line)sSpam comment \d+ from $ + ^%(__prefix_line)sBlocked user enumeration attempt from $ + ^%(__prefix_line)sBlocked authentication attempt for .* from $ + ^%(__prefix_line)sXML-RPC multicall authentication failure from $ + ^%(__prefix_line)sPingback error .* generated from $ + +ignoreregex = + +# DEV Notes: +# Requires the 'WP fail2ban' plugin: +# https://wp-fail2ban.com/ +# +# Author: Charles Lecklider diff --git a/filters.d/wordpress-soft.conf b/filters.d/wordpress-soft.conf new file mode 100644 index 0000000..17b95e5 --- /dev/null +++ b/filters.d/wordpress-soft.conf @@ -0,0 +1,23 @@ +# Fail2Ban filter for WordPress soft failures +# Auto-generated: 2019-07-15T18:00:14+00:00 +# + +[INCLUDES] + +before = common.conf + +[Definition] + +_daemon = (?:wordpress|wp) + +failregex = ^%(__prefix_line)sAuthentication failure for .* from $ + ^%(__prefix_line)sREST authentication failure for .* from $ + ^%(__prefix_line)sXML-RPC authentication failure for .* from $ + +ignoreregex = + +# DEV Notes: +# Requires the 'WP fail2ban' plugin: +# https://wp-fail2ban.com/ +# +# Author: Charles Lecklider diff --git a/lib/constants.php b/lib/constants.php new file mode 100644 index 0000000..5fd7a39 --- /dev/null +++ b/lib/constants.php @@ -0,0 +1,144 @@ + $cast, + 'unset' => $unset, + 'field' => $field, + 'ndef' => !defined( $define ), + ); + if ( !defined( $define ) ) { + + if ( defined( "DEFAULT_{$define}" ) ) { + // we've got a default + define( $define, $cast( constant( "DEFAULT_{$define}" ) ) ); + } else { + // bah + define( $define, $cast( false ) ); + } + + } + } + + /** + * Validate IP list + * + * @since 4.0.0 + * + * @param array|string $value + * + * @return string + */ + function validate_ips( $value ) + { + return $value; + } + + // phpcs:disable Generic.Functions.FunctionCallArgumentSpacing + _load( + 'WP_FAIL2BAN_AUTH_LOG', + 'intval', + true, + array( 'logging', 'authentication', 'facility' ) + ); + _load( + 'WP_FAIL2BAN_LOG_COMMENTS', + 'boolval', + true, + array( 'logging', 'comments', 'enabled' ) + ); + _load( + 'WP_FAIL2BAN_LOG_COMMENTS_EXTRA', + 'intval', + true, + array( 'logging', 'comments', 'extra' ) + ); + _load( + 'WP_FAIL2BAN_COMMENT_LOG', + 'intval', + false, + array( 'logging', 'comments', 'facility' ) + ); + _load( + 'WP_FAIL2BAN_COMMENT_EXTRA_LOG', + 'intval', + false, + array( 'logging', 'comments-extra', 'facility' ) + ); + _load( + 'WP_FAIL2BAN_LOG_PASSWORD_REQUEST', + 'boolval', + true, + array( 'logging', 'password-request', 'enabled' ) + ); + _load( + 'WP_FAIL2BAN_PASSWORD_REQUEST_LOG', + 'intval', + false, + array( 'logging', 'password-request', 'facility' ) + ); + _load( + 'WP_FAIL2BAN_LOG_PINGBACKS', + 'boolval', + true, + array( 'logging', 'pingback', 'enabled' ) + ); + _load( + 'WP_FAIL2BAN_PINGBACK_LOG', + 'intval', + false, + array( 'logging', 'pingback', 'facility' ) + ); + _load( + 'WP_FAIL2BAN_LOG_SPAM', + 'boolval', + true, + array( 'logging', 'spam', 'enabled' ) + ); + _load( + 'WP_FAIL2BAN_SPAM_LOG', + 'intval', + false, + array( 'logging', 'spam', 'facility' ) + ); + _load( + 'WP_FAIL2BAN_OPENLOG_OPTIONS', + 'intval', + true, + array( 'syslog', 'connection' ) + ); + _load( + 'WP_FAIL2BAN_SYSLOG_SHORT_TAG', + 'boolval', + true, + array( 'syslog', 'workaround', 'short_tag' ) + ); + _load( + 'WP_FAIL2BAN_HTTP_HOST', + 'boolval', + true, + array( 'syslog', 'workaround', 'http_host' ) + ); + _load( + 'WP_FAIL2BAN_TRUNCATE_HOST', + 'boolval', + true, + array( 'syslog', 'workaround', 'truncate_host' ) + ); + _load( + 'WP_FAIL2BAN_BLOCK_USER_ENUMERATION', + 'boolval', + true, + array( 'block', 'user_enumeration' ) + ); + _load( + 'WP_FAIL2BAN_BLOCKED_USERS', + 'strval', + true, + array( 'block', 'users' ) + ); + _load( + 'WP_FAIL2BAN_PROXIES', + __NAMESPACE__ . '\\validate_ips', + true, + array( 'remote-ip', 'proxies' ) + ); + _load( + 'WP_FAIL2BAN_PLUGIN_LOG_AUTH', + 'boolval', + true, + array( + 'logging', + 'plugins', + 'auth', + 'enabled' + ) + ); + _load( + 'WP_FAIL2BAN_PLUGIN_LOG_COMMENT', + 'boolval', + true, + array( + 'logging', + 'plugins', + 'comment', + 'enabled' + ) + ); + _load( + 'WP_FAIL2BAN_PLUGIN_LOG_PASSWORD', + 'boolval', + true, + array( + 'logging', + 'plugins', + 'password', + 'enabled' + ) + ); + _load( + 'WP_FAIL2BAN_PLUGIN_LOG_REST', + 'boolval', + true, + array( + 'logging', + 'plugins', + 'rest', + 'enabled' + ) + ); + _load( + 'WP_FAIL2BAN_PLUGIN_LOG_SPAM', + 'boolval', + true, + array( + 'logging', + 'plugins', + 'spam', + 'enabled' + ) + ); + _load( + 'WP_FAIL2BAN_PLUGIN_LOG_XMLRPC', + 'boolval', + true, + array( + 'logging', + 'plugins', + 'xmlrpc', + 'enabled' + ) + ); + _load( + 'WP_FAIL2BAN_PLUGIN_AUTH_LOG', + 'intval', + false, + array( + 'logging', + 'plugins', + 'auth', + 'facility' + ) + ); + _load( + 'WP_FAIL2BAN_PLUGIN_COMMENT_LOG', + 'intval', + false, + array( + 'logging', + 'plugins', + 'comment', + 'facility' + ) + ); + _load( + 'WP_FAIL2BAN_PLUGIN_PASSWORD_LOG', + 'intval', + false, + array( + 'logging', + 'plugins', + 'password', + 'facility' + ) + ); + _load( + 'WP_FAIL2BAN_PLUGIN_REST_LOG', + 'intval', + false, + array( + 'logging', + 'plugins', + 'rest', + 'facility' + ) + ); + _load( + 'WP_FAIL2BAN_PLUGIN_SPAM_LOG', + 'intval', + false, + array( + 'logging', + 'plugins', + 'spam', + 'facility' + ) + ); + _load( + 'WP_FAIL2BAN_PLUGIN_XMLRPC_LOG', + 'intval', + false, + array( + 'logging', + 'plugins', + 'xmlrpc', + 'facility' + ) + ); + // phpcs:enable +} \ No newline at end of file diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..5b76cb6 --- /dev/null +++ b/readme.txt @@ -0,0 +1,280 @@ +=== WP fail2ban === +Contributors: invisnet +Donate link: https://paypal.me/invisnet/ +Author URI: https://charles.lecklider.org/ +Plugin URI: https://wp-fail2ban.com/ +Tags: fail2ban, login, security, syslog +Requires at least: 4.2 +Tested up to: 5.2 +Stable tag: 4.2.5 +Requires PHP: 5.3 +License: GPLv2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html + +Write a myriad of WordPress events to syslog for integration with fail2ban. + +== Description == + +[fail2ban](http://www.fail2ban.org/) is one of the simplest and most effective security measures you can implement to prevent brute-force attacks. + +*WP fail2ban* logs all login attempts - including via XML-RPC, whether successful or not, to syslog using LOG_AUTH. For example: + + Oct 17 20:59:54 foobar wordpress(www.example.com)[1234]: Authentication failure for admin from 192.168.0.1 + Oct 17 21:00:00 foobar wordpress(www.example.com)[2345]: Accepted password for admin from 192.168.0.1 + +*WPf2b* comes with three `fail2ban` filters: `wordpress-hard.conf`, `wordpress-soft.conf`, and `wordpress-extra.conf`. These are designed to allow a split between immediate banning (hard) and the traditional more graceful approach (soft), with extra rules for custom configurations. + += Features = + +* **NEW - Support for 3rd-party Plugins** + Version 4.2 introduces a simple API for authors to integrate their plugins with *WPf2b*, with 2 *experimental* add-ons: + * [Contact Form 7](https://wordpress.org/plugins/wp-fail2ban-addon-contact-form-7/) + * [Gravity Forms](https://wordpress.org/plugins/wp-fail2ban-addon-gravity-forms/) + **NB:** Requires PHP >= 5.6 + +* **CloudFlare and Proxy Servers** + *WPf2b* can be configured to work with CloudFlare and other proxy servers. For an overview see [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-proxies). + +* **Comments** + *WPf2b* can log comments (see [`WP_FAIL2BAN_LOG_COMMENTS`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-log-comments)) and attempted comments (see [`WP_FAIL2BAN_LOG_COMMENTS_EXTRA`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-log-comments-extra)). + +* **Pingbacks** + *WPf2b* logs failed pingbacks, and can log all pingbacks. For an overview see [`WP_FAIL2BAN_LOG_PINGBACKS`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-log-pingbacks). + +* **Spam** + *WPf2b* can log comments marked as spam. See [`WP_FAIL2BAN_LOG_SPAM`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-log-spam). + +* **Block User Enumeration** + *WPf2b* can block user enumeration. See [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-block-user-enumeration). + +* **Work-Arounds for Broken syslogd** + *WPf2b* can be configured to work around most syslogd weirdness. For an overview see [`WP_FAIL2BAN_SYSLOG_SHORT_TAG`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-syslog-short-tag) and [`WP_FAIL2BAN_HTTP_HOST`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-http-host). + +* **Blocking Users** + *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://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-blocked-users). + +* **`mu-plugins` Support** + *WPf2b* can easily be configured as a must-use plugin - see [Configuration](https://docs.wp-fail2ban.com/en/4.2/configuration.html#mu-plugins-support). + +== Installation == + +1. Install via the Plugin Directory, or upload to your plugins directory. +1. Activate the plugin through the 'Plugins' menu in WordPress. +1. Edit `wp-config.php` to suit your needs - see [Configuration](https://docs.wp-fail2ban.com/en/4.2/configuration.html). + +== Changelog == + += 4.2.5 = +* Properly fix PHP 5.3 support; tested on CentOS 6. Does not support any UI or Premium features. +* Fix potential issue with `WP_FAIL2BAN_BLOCK_USER_ENUMERATION` if calling REST API or XMLRPC from admin area. + += 4.2.4 = +* Add filter for login failed message. +* Fix logging spam comments from admin area. +* Fix Settings link from Plugins page. +* Update Freemius library + += 4.2.3 = +* Workaround for some versions of PHP 7.x that would cause `define()`s to be ignored. +* Add config note to settings tabs. +* Fix documentation links. + += 4.2.2 = +* Fix 5.3 compatibility. + += 4.2.1 = +* Completed support for [`WP_FAIL2BAN_COMMENT_EXTRA_LOG`](https://docs.wp-fail2ban.com/en/4.2/defines/WP_FAIL2BAN_COMMENT_EXTRA_LOG.html). +* Add support for 3rd-party plugins; see [Developers](https://docs.wp-fail2ban.com/en/4.2/developers.html). + * Add-on for [Contact Form 7](https://wordpress.org/plugins/wp-fail2ban-addon-contact-form-7/) (experimental). + * Add-on for [Gravity Forms](https://wordpress.org/plugins/wp-fail2ban-addon-gravity-forms/) (experimental). +* Change logging for known-user with incorrect password; previously logged as unknown user and matched by `hard` filters (due to limitations in older versions of WordPress), now logged as known user and matched by `soft`. +* Bugfix for email-as-username - now logged correctly and matched by `soft`, not `hard`, filters. +* Bugfix for regression in code to prevent Free/Premium conflict. + += 4.2.0 = +* Not released. + += 4.1.0 = +* Add separate logging for REST authentication. +* Fix conflict with earlier versions pre-installed in `mu-plugins`. See [Is *WPf2b* Already Installed?](https://docs.wp-fail2ban.com/en/4.1/installation.html#is-wp-fail2ban-already-installed). + += 4.0.5 = +* Add [`WP_FAIL2BAN_COMMENT_EXTRA_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_COMMENT_EXTRA_LOG.html). +* Add [`WP_FAIL2BAN_PINGBACK_ERROR_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PINGBACK_ERROR_LOG.html) (future functionality). +* Change `WP_FAIL2BAN_LOG_SPAM` to use `LOG_NOTICE`. +* Change `WP_FAIL2BAN_SPAM_LOG` to `LOG_AUTH`. +* Change `WP_FAIL2BAN_LOG_COMMENTS_EXTRA` events to use `LOG_NOTICE` by default. +* Fix conflict with 3.x in `mu-plugins`. + += 4.0.2 = +* Fix PHP 5.3 compatibility. +* Bugfix for `WP_FAIL2BAN_LOG_COMMENTS_EXTRA`. +* Bugfix for `WP_FAIL2BAN_REMOTE_ADDR` summary. + += 4.0.1 = +* Add extra features via Freemius. **This is entirely optional.** *WPf2b* works as before, including new features listed here. +* Add settings summary page (Settings -> WP fail2ban). +* Add [`WP_FAIL2BAN_PASSWORD_REQUEST_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PASSWORD_REQUEST_LOG.html). +* Add [`WP_FAIL2BAN_SPAM_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_SPAM_LOG.html). +* Add [`WP_FAIL2BAN_LOG_COMMENTS_EXTRA`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_LOG_COMMENTS_EXTRA.html) - enable logging for attempted comments on posts which are: + * not found, + * closed for commenting, + * in the trash, + * drafts, + * password protected +* Block user enumeration via REST API. + += 4.0.0 = +* Not released. + += 3.6.0 = +* The [filter files](https://docs.wp-fail2ban.com/en/4.1/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. +* Added [PHPUnit tests](https://docs.wp-fail2ban.com/en/4.1/tests.html). Almost 100% code coverage, with the exception of [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html) which is quite hard to test properly. +* Bugfix for [`wordpress-soft.conf`](https://docs.wp-fail2ban.com/en/4.1/filters.html#wordpress-soft-conf). +* Add [`WP_FAIL2BAN_XMLRPC_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_XMLRPC_LOG.html). +* Add [`WP_FAIL2BAN_REMOTE_ADDR`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_REMOTE_ADDR.html). +* [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html) now supports an array of IPs with PHP 7. +* Moved all documentation to [https://docs.wp-fail2ban.com/](https://docs.wp-fail2ban.com/). + += 3.5.3 = +* Bugfix for [`wordpress-hard.conf`](https://docs.wp-fail2ban.com/en/4.1/filters.html#wordpress-hard-conf). + += 3.5.1 = +* Bugfix for [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCK_USER_ENUMERATION.html). + += 3.5.0 = +* Add [`WP_FAIL2BAN_OPENLOG_OPTIONS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_OPENLOG_OPTIONS.html). +* Add [`WP_FAIL2BAN_LOG_COMMENTS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_LOG_COMMENTS.html) and [`WP_FAIL2BAN_COMMENT_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_COMMENT_LOG.html). +* Add [`WP_FAIL2BAN_LOG_PASSWORD_REQUEST`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_LOG_PASSWORD_REQUEST.html). +* Add [`WP_FAIL2BAN_LOG_SPAM`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_LOG_SPAM.html). +* Add [`WP_FAIL2BAN_TRUNCATE_HOST`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_TRUNCATE_HOST.html). +* [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCKED_USERS.html) now supports an array of users with PHP 7. + += 3.0.3 = +* Fix regex in [`wordpress-hard.conf`](https://docs.wp-fail2ban.com/en/4.1/filters.html#wordpress-hard-conf). + += 3.0.2 = +* Prevent double logging in WP 4.5.x for XML-RPC authentication failure + += 3.0.1 = +* Fix regex in [`wordpress-hard.conf`](https://docs.wp-fail2ban.com/en/4.1/filters.html#wordpress-hard-conf). + += 3.0.0 = +* Add [`WP_FAIL2BAN_SYSLOG_SHORT_TAG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_SYSLOG_SHORT_TAG.html). +* Add [`WP_FAIL2BAN_HTTP_HOST`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_HTTP_HOST.html). +* Log XML-RPC authentication failure. +* Add better support for MU deployment. + += 2.3.2 = +* Bugfix [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCKED_USERS.html). + += 2.3.0 = +* Bugfix in *experimental* [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html) code (thanks to KyleCartmell). + += 2.2.1 = +* Fix stupid mistake with [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCKED_USERS.html). + += 2.2.0 = +* Custom authentication log is now called [`WP_FAIL2BAN_AUTH_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_AUTH_LOG.html). +* Add logging for pingbacks; see [`WP_FAIL2BAN_LOG_PINGBACKS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_LOG_PINGBACKS.html). +* Custom pingback log is called [`WP_FAIL2BAN_PINGBACK_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PINGBACK_LOG.html). + += 2.1.1 = +* Minor bugfix. + += 2.1.0 = +* Add support for blocking user enumeration; see [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCK_USER_ENUMERATION.html). +* Add support for CIDR notation in [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html). + += 2.0.1 = +* Bugfix in *experimental* [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html) code. + += 2.0.0 = +* Add *experimental* support for X-Forwarded-For header; see [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html). +* Add *experimental* support for regex-based login blocking; see [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCKED_USERS.html). + += 1.2.1 = +* Update FAQ. + += 1.2 = +* Fix harmless warning. + += 1.1 = +* Minor cosmetic updates. + += 1.0 = +* Initial release. + +== Upgrade Notice == + += 4.2.5 = +This is a minor release. You do not need to update your filters from 4.1.0. + += 4.2.4 = +This is a minor release. You do not need to update your filters from 4.1.0. + += 4.2.3 = +This is a bugfix release. You do not need to update your filters from 4.1.0. + += 4.2.2 = +You do not need to update your filters from 4.1.0. + += 4.2.1 = +You do not need to update your filters from 4.1.0. + += 4.1.0 = +To take advantage of the new features you will need up update your `fail2ban` filters; existing filters will continue to work as before. + += 4.0.5 = +This is a security fix (Freemius SDK): all 4.x users are strongly advised to upgrade immediately. You do not need to update your filters from 4.0.1. + += 4.0.4 = +This is a bugfix. You do not need to update your filters from 4.0.1. + += 4.0.3 = +This is a bugfix. You do not need to update your filters from 4.0.1. + += 4.0.2 = +This is a bugfix. You do not need to update your filters from 4.0.1. + += 4.0.1 = +To take advantage of the new features you will need up update your `fail2ban` filters; existing filters will continue to work as before. + += 3.6.0 = +You will need up update your `fail2ban` filters. + += 3.5.3 = +You will need up update your `fail2ban` filters. + += 3.5.1 = +Bugfix: disable [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCK_USER_ENUMERATION.html) in admin area.... + += 3.5.0 = +You will need up update your `fail2ban` filters. + += 3.0.3 = +You will need up update your `fail2ban` filters. + += 3.0.0 = +BREAKING CHANGE: The `fail2ban` filters have been split into two files. You will need up update your `fail2ban` configuration. + += 2.3.0 = +Fix for [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html); if you're not using it you can safely skip this release. + += 2.2.1 = +Bugfix. + += 2.2.0 = +BREAKING CHANGE: `WP_FAIL2BAN_LOG` has been renamed to [`WP_FAIL2BAN_AUTH_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_AUTH_LOG.html). + +Pingbacks are getting a lot of attention recently, so *WPf2b* can now log them. +The `wordpress.conf` filter has been updated; you will need to update your `fail2ban` configuration. + += 2.1.0 = +The `wordpress.conf` filter has been updated; you will need to update your `fail2ban` configuration. + += 2.0.1 = +Bugfix in experimental code; still an experimental release. + += 2.0.0 = +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. diff --git a/wp-fail2ban-main.php b/wp-fail2ban-main.php new file mode 100644 index 0000000..ff983bd --- /dev/null +++ b/wp-fail2ban-main.php @@ -0,0 +1,254 @@ + $data ) { + + if ( 0 === strpos( $data['Name'], 'WP fail2ban' ) ) { + $wp_f2b_ver = substr( WP_FAIL2BAN_VER, 0, strrpos( WP_FAIL2BAN_VER, '.' ) ); + $wpf2b = 'WP fail2ban'; + $error_msg = sprintf( __( '

Cannot activate %s

' ), $wpf2b ); + $mu_file = WPMU_PLUGIN_DIR . '/' . $plugin; + + if ( is_link( $mu_file ) ) { + + if ( false === ($link = readlink( $mu_file )) || false === ($path = realpath( $mu_file )) ) { + $h3 = __( 'A broken symbolic link was found in mu-plugins:' ); + $error_msg .= <<<__ERROR__ +

{$h3}

+

{$mu_file}

+__ERROR__; + } elseif ( WP_FAIL2BAN_FILE == $path ) { + // OK, we're linking to ourself + } else { + $mu_file = str_replace( '/', '/', $mu_file ); + $mu_file = substr( $mu_file, strlen( WPMU_PLUGIN_DIR ) - 1 ); + $h3 = __( 'A conflicting symbolic link was found in mu-plugins:' ); + $error_msg .= <<<__ERROR__ +

{$h3}

+ + + + + + + + + + + + + +
{$mu_file}{$link}
{$path}
+__ERROR__; + } + + } else { + $mu_file = str_replace( '/', '/', $mu_file ); + $mu_file = substr( $mu_file, strlen( WPMU_PLUGIN_DIR ) - 1 ); + $h3 = __( 'A conflicting file was found in mu-plugins:' ); + $error_msg .= <<<__ERROR__ +

{$h3}

+

{$mu_file}

+__ERROR__; + } + + $error_msg .= sprintf( __( '

Please see the documentation for how to configure %s for mu-plugins.

' ), "https://docs.wp-fail2ban.com/en/{$wp_f2b_ver}/configuration.html#mu-plugins-support", $wpf2b ); + $error_msg .= sprintf( __( '

Click here to return to the plugins page.

' ), admin_url( 'plugins.php' ) ); + deactivate_plugins( plugin_basename( WP_FAIL2BAN_FILE ) ); + wp_die( $error_msg ); + } + + } +} ); +require __DIR__ . '/feature/lib.php'; +/** + * @since 4.2.5 + */ + +if ( version_compare( PHP_VERSION, '5.6.0', '>=' ) ) { + /** + * @since 4.2.0 + */ + global $wp_fail2ban ; + $wp_fail2ban['plugins'] = array(); + require __DIR__ . '/feature/plugins.php'; + if ( is_admin() ) { + require 'admin/admin.php'; + } +} elseif ( is_admin() ) { + require __DIR__ . '/admin/lib/about.php'; + add_action( 'admin_menu', function () { + add_menu_page( + 'WP fail2ban', + 'WP fail2ban', + 'manage_options', + 'wp-fail2ban', + __NAMESPACE__ . '\\about', + 'dashicons-analytics' + ); + } ); +} + +/** + * @since 4.0.5 + */ + +if ( !function_exists( __NAMESPACE__ . '\\wp_login' ) ) { + /** + * Hook: wp_login + * + * @since 4.1.0 Add REST support + * @since 3.5.0 Refactored for unit testing + * @since 1.0.0 + * + * @param string $user_login + * @param mixed $user + */ + function wp_login( $user_login, $user ) + { + global $wp_xmlrpc_server ; + openlog(); + syslog( LOG_INFO, "Accepted password for {$user_login}" ); + closelog(); + // @codeCoverageIgnoreEnd + } + + add_action( + 'wp_login', + __NAMESPACE__ . '\\wp_login', + 10, + 2 + ); +} + +/** + * @since 4.0.5 + */ + +if ( !function_exists( __NAMESPACE__ . '\\wp_login_failed' ) ) { + /** + * Hook: wp_login_failed + * + * @since 4.2.4 Add message filter + * @since 4.2.0 Change username check + * @since 4.1.0 Add REST support + * @since 3.5.0 Refactored for unit testing + * @since 1.0.0 + * + * @param string $username + * + * @wp-f2b-hard Authentication attempt for unknown user .* + * @wp-f2b-hard REST authentication attempt for unknown user .* + * @wp-f2b-hard XML-RPC authentication attempt for unknown user .* + * @wp-f2b-soft Authentication failure for .* + * @wp-f2b-soft REST authentication failure for .* + * @wp-f2b-soft XML-RPC authentication failure for .* + */ + function wp_login_failed( $username ) + { + global $wp_xmlrpc_server ; + + if ( defined( 'REST_REQUEST' ) ) { + $msg = 'REST a'; + $filter = '::REST'; + } elseif ( $wp_xmlrpc_server ) { + $msg = 'XML-RPC a'; + $filter = '::XML-RPC'; + } else { + $msg = 'A'; + $filter = ''; + } + + $username = trim( $username ); + $msg .= ( wp_cache_get( $username, 'useremail' ) || wp_cache_get( sanitize_user( $username ), 'userlogins' ) ? "uthentication failure for {$username}" : "uthentication attempt for unknown user {$username}" ); + $msg = apply_filters( "wp_fail2ban::wp_login_failed{$filter}", $msg ); + openlog(); + syslog( LOG_NOTICE, $msg ); + closelog(); + // @codeCoverageIgnoreEnd + } + + add_action( 'wp_login_failed', __NAMESPACE__ . '\\wp_login_failed' ); +} + +/** + * @since 4.2.5 + */ + +if ( !is_admin() ) { + /** + * User enumeration + * + * @since 4.0.0 Refactored + * @since 2.1.0 + */ + if ( defined( 'WP_FAIL2BAN_BLOCK_USER_ENUMERATION' ) && true === WP_FAIL2BAN_BLOCK_USER_ENUMERATION ) { + require_once __DIR__ . '/feature/user-enum.php'; + } + /** + * XML-RPC + * + * @since 4.0.0 Refactored + * @since 3.0.0 + */ + if ( defined( 'XMLRPC_REQUEST' ) && true === XMLRPC_REQUEST ) { + require_once __DIR__ . '/feature/xmlrpc.php'; + } +} + +/** + * Comments + * + * @since 4.0.0 Refactored + * @since 3.5.0 + */ +if ( defined( 'WP_FAIL2BAN_LOG_COMMENTS' ) && true === WP_FAIL2BAN_LOG_COMMENTS ) { + require_once __DIR__ . '/feature/comments.php'; +} +/** + * Password + * + * @since 4.0.0 Refactored + * @since 3.5.0 + */ +if ( defined( 'WP_FAIL2BAN_LOG_PASSWORD_REQUEST' ) && true === WP_FAIL2BAN_LOG_PASSWORD_REQUEST ) { + require_once __DIR__ . '/feature/password.php'; +} +/** + * Spam + * + * @since 4.0.0 Refactored + * @since 3.5.0 + */ +if ( defined( 'WP_FAIL2BAN_LOG_SPAM' ) && true === WP_FAIL2BAN_LOG_SPAM ) { + require_once __DIR__ . '/feature/spam.php'; +} +/** + * Users + * + * @since 4.0.0 Refactored + * @since 2.0.0 + */ +if ( defined( 'WP_FAIL2BAN_BLOCKED_USERS' ) && '' < WP_FAIL2BAN_BLOCKED_USERS ) { + require_once __DIR__ . '/feature/user.php'; +} \ No newline at end of file diff --git a/wp-fail2ban.php b/wp-fail2ban.php new file mode 100644 index 0000000..df89f3c --- /dev/null +++ b/wp-fail2ban.php @@ -0,0 +1,105 @@ +set_basename( false, __FILE__ ); + return; +} else { + /** + * Create a helper function for easy SDK access. + */ + function wf_fs() + { + global $wf_fs ; + + if ( !isset( $wf_fs ) ) { + // Include Freemius SDK. + require_once dirname( __FILE__ ) . '/vendor/freemius/wordpress-sdk/start.php'; + $wf_fs = fs_dynamic_init( array( + 'id' => '3072', + 'slug' => 'wp-fail2ban', + 'type' => 'plugin', + 'public_key' => 'pk_146d2c2a5bee3b157e43501ef8682', + 'is_premium' => false, + 'has_addons' => true, + 'has_paid_plans' => true, + 'trial' => array( + 'days' => 7, + 'is_require_payment' => false, + ), + 'menu' => array( + 'slug' => 'wp-fail2ban', + 'first-path' => 'admin.php?page=wp-fail2ban', + 'support' => false, + ), + 'is_live' => true, + ) ); + } + + return $wf_fs; + } + + // Init Freemius. + wf_fs(); + // Set currency to GBP + wf_fs()->add_filter( 'default_currency', function () { + return 'gbp'; + } ); + // Signal that SDK was initiated. + do_action( 'wf_fs_loaded' ); +} + +// @codeCoverageIgnoreEnd +/** + * Freemius insists on mangling the formatting of the main plugin file + * + * @since 4.0.0 Refactored + */ +require_once 'wp-fail2ban-main.php'; \ No newline at end of file