diff --git a/.wp-env.json b/.wp-env.json index 9d44921..3349438 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -2,9 +2,9 @@ "phpVersion": "7.4", "core": "WordPress/WordPress", "plugins": [ + ".", "https://downloads.wordpress.org/plugin/contact-form-7.zip", - "https://downloads.wordpress.org/plugin/flamingo.zip", - "." + "https://downloads.wordpress.org/plugin/flamingo.zip" ], "config": { "WP_DEBUG": true, diff --git a/admin/admin-customizations.php b/admin/admin-customizations.php index 939db33..6f68e41 100644 --- a/admin/admin-customizations.php +++ b/admin/admin-customizations.php @@ -398,7 +398,7 @@ public function cf7a_options_init() { 'cf7a_honeypot' ); - /* DNS Blacklist server list */ + /* honeypot input name */ add_settings_field( 'honeypot_input_names', __( 'Name for the honeypots inputs[*]', 'cf7-antispam' ), @@ -433,6 +433,32 @@ public function cf7a_options_init() { 'cf7a_honeyform' ); + /* Identity Protection */ + add_settings_section( + 'cf7a_identity_protection', + __( 'Identity Protection', 'cf7-antispam' ), + array( $this, 'cf7a_print_identity_protection' ), + 'cf7a-settings' + ); + + /* Enable identity_protection */ + add_settings_field( + 'identity_protection_user', + __( 'Enforce user protection', 'cf7-antispam' ), + array( $this, 'cf7a_identity_protection_user_callback' ), + 'cf7a-settings', + 'cf7a_identity_protection' + ); + + /* identity_protection position */ + add_settings_field( + 'identity_protection_wp', + __( 'Enforce Wordpress protection', 'cf7-antispam' ), + array( $this, 'cf7a_identity_protection_wp_callback' ), + 'cf7a-settings', + 'cf7a_identity_protection' + ); + /* Section b8 */ add_settings_section( 'cf7a_b8', @@ -763,6 +789,11 @@ public function cf7a_print_honeyform() { printf( '

%s

', esc_html__( "I'm actually going to propose the honey-form for the first time! Instead of serving the bot a form with trap fields we directly serve it a form that is entirely a trap", 'cf7-antispam' ) ); } + /** It prints the user protection info text */ + public function cf7a_print_identity_protection() { + printf( '

%s

', esc_html__( "After monitoring and analysing some bots, I noticed that it is necessary to block the way bots collect (user) data from the website, otherwise protecting the form may have no effect. This also blocks some registrations, spam comments and other attacks", 'cf7-antispam' ) ); + } + /** It prints the b8 info text */ public function cf7a_print_b8() { printf( '

%s

', esc_html__( 'Tells you whether a text is spam or not, using statistical text analysis of the text message', 'cf7-antispam' ) ); @@ -934,7 +965,7 @@ public function cf7a_sanitize_options( $input ) { /* auto-unban delay */ $schedule = wp_get_schedules(); - if ( ! empty( $input['unban_after'] ) && in_array( $input['unban_after'], array_keys( $schedule ) ) ) { + if ( ! empty( $input['unban_after'] ) && in_array( $input['unban_after'], array_keys( $schedule ), true ) ) { if ( $this->options['unban_after'] !== $input['unban_after'] ) { $new_input['unban_after'] = $input['unban_after']; /* delete previous scheduled events */ @@ -1006,6 +1037,10 @@ public function cf7a_sanitize_options( $input ) { $new_input['check_honeyform'] = isset( $input['check_honeyform'] ) ? 1 : 0; $new_input['honeyform_position'] = ! empty( $input['honeyform_position'] ) ? sanitize_title( $input['honeyform_position'] ) : 'wp_body_open'; + /* honeyform */ + $new_input['identity_protection_user'] = isset( $input['identity_protection_user'] ) ? 1 : 0; + $new_input['identity_protection_wp'] = isset( $input['identity_protection_wp'] ) ? 1 : 0; + /* b8 */ $new_input['enable_b8'] = isset( $input['enable_b8'] ) ? 1 : 0; $threshold = floatval( $input['b8_threshold'] ); @@ -1190,7 +1225,7 @@ public function cf7a_geoip_key_callback() { '', empty( $this->options['geoip_dbkey'] ) ? '' : 'value="' . esc_attr( $this->options['geoip_dbkey'] ) . '"', // phpcs:ignore WordPress.Security.EscapeOutput - empty( CF7ANTISPAM_GEOIP_KEY ) ? '' : 'disabled placeholder="KEY provided"' + empty( CF7ANTISPAM_GEOIP_KEY ) ? '' : 'disabled placeholder="' . esc_html__( 'KEY provided' ) . '"' ); } @@ -1362,6 +1397,22 @@ public function cf7a_honeyform_position_callback() { ); } + /** It creates a checkbox with the id of "cf7a_identity_protection_user_callback" */ + public function cf7a_identity_protection_user_callback() { + printf( + '', + ! empty( $this->options['identity_protection_user'] ) ? 'checked="true"' : '' + ); + } + + /** It creates a checkbox with the id of "cf7a_identity_protection_user_callback" */ + public function cf7a_identity_protection_wp_callback() { + printf( + '', + ! empty( $this->options['identity_protection_wp'] ) ? 'checked="true"' : '' + ); + } + /** It creates a checkbox with the id of "cf7a_enable_b8_callback" */ public function cf7a_enable_b8_callback() { printf( diff --git a/cf7-antispam.php b/cf7-antispam.php index 0e28190..14d5062 100644 --- a/cf7-antispam.php +++ b/cf7-antispam.php @@ -5,7 +5,7 @@ * Author: Codekraft * Text Domain: cf7-antispam * Domain Path: /languages/ - * Version: 0.4.2 + * Version: 0.4.3 * * @package cf7-antispam */ @@ -18,7 +18,7 @@ /* CONSTANTS */ define( 'CF7ANTISPAM_NAME', 'cf7-antispam' ); -define( 'CF7ANTISPAM_VERSION', '0.4.2' ); +define( 'CF7ANTISPAM_VERSION', '0.4.3' ); define( 'CF7ANTISPAM_PLUGIN', __FILE__ ); diff --git a/includes/cf7a-activator.php b/includes/cf7a-activator.php index 4bbd9b1..881ae7d 100644 --- a/includes/cf7a-activator.php +++ b/includes/cf7a-activator.php @@ -55,6 +55,8 @@ public static function init_vars() { 'check_refer' => true, 'check_honeypot' => true, 'check_honeyform' => false, + 'identity_protection_user' => false, + 'identity_protection_wp' => false, 'enable_geoip_download' => false, 'geoip_dbkey' => false, 'check_language' => false, diff --git a/includes/cf7a-core.php b/includes/cf7a-core.php index 9a0f0c4..c3562c4 100644 --- a/includes/cf7a-core.php +++ b/includes/cf7a-core.php @@ -305,6 +305,18 @@ private function load_frontend() { $this->loader->add_filter( 'the_content', $plugin_frontend, 'cf7a_honeyform', 99 ); } + /* Checking if the user has selected the option to protect the user's identity. If they have, it will call the function + to protect the user's identity. */ + if ( isset( $this->options['identity_protection_user'] ) && intval( $this->options['identity_protection_user'] ) === 1 ) { + $plugin_frontend->cf7a_protect_user(); + } + + /* It removes the WordPress version from the header, removes the REST API link from the header, + removes headers that disposes information */ + if ( isset( $this->options['identity_protection_wp'] ) && intval( $this->options['identity_protection_wp'] ) === 1 ) { + $this->loader->add_filter( 'wp_headers', $plugin_frontend, 'cf7a_protect_wp', 999 ); + } + /* It adds a CSS style to the page that hides the honeypot field */ if ( ( isset( $this->options['check_honeypot'] ) && 1 === intval( $this->options['check_honeypot'] ) ) || ( isset( $this->options['check_honeyform'] ) && 1 === intval( $this->options['check_honeyform'] ) ) diff --git a/includes/cf7a-frontend.php b/includes/cf7a-frontend.php index 9a2c9e0..9654224 100644 --- a/includes/cf7a-frontend.php +++ b/includes/cf7a-frontend.php @@ -57,7 +57,8 @@ public function cf7a_honeypot_add( $form_elements ) { try { $html = new DOMDocument( '1.0', 'UTF-8' ); libxml_use_internal_errors( true ); - $html->loadHTML( $form_elements, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ); + // mb_convert_encoding is needed for non-latin font sets / LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD avoids auto-fixes for corrupted html code + $html->loadHTML( mb_convert_encoding( $form_elements, 'HTML-ENTITIES', 'UTF-8' ), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ); $xpath = new DOMXpath( $html ); $inputs = $xpath->query( '//input' ); @@ -73,6 +74,16 @@ public function cf7a_honeypot_add( $form_elements ) { $options = get_option( 'cf7a_options', array() ); $input_names = get_honeypot_input_names( $options['honeypot_input_names'] ); $input_class = sanitize_html_class( $this->options['cf7a_customizations_class'] ); + /** + * Controls the maximum number of honeypots. + * + * @example add_filter( 'cf7a_additional_max_honeypots', function() {return 42}); + * + * @returns int $max_replacements - replacement count number + * + * @since 0.4.3 + */ + $max_replacements = intval( apply_filters( 'cf7a_additional_max_honeypots', 5 ) ); /* get the inputs data */ if ( $inputs && $inputs->length > 0 ) { @@ -101,7 +112,7 @@ public function cf7a_honeypot_add( $form_elements ) { /* duplicate the inputs into honeypots */ $parent->insertBefore( $clone, $sibling ); - if ( $i > 0 ) { + if ( $i > $max_replacements ) { return $html->saveHTML(); } } @@ -324,6 +335,65 @@ public function cf7a_append_on_submit( $fields ) { ); } + /** + * It disables the XML-RPC protocol and the REST API endpoints for users + * + */ + public function cf7a_protect_user() { + /* disables the XML-RPC */ + add_filter( 'xmlrpc_enabled', '__return_false' ); + + /** + * Remove Rest user endpoints + * @return array $endpoints the value of the variable $endpoints. + */ + if (!is_user_logged_in()) add_filter('rest_endpoints', function($endpoints) { + if ( isset( $endpoints['/wp/v2/users'] ) ) { + unset( $endpoints['/wp/v2/users'] ); + } + if ( isset( $endpoints['/wp/v2/users/(?P[\d]+)'] ) ) { + unset( $endpoints['/wp/v2/users/(?P[\d]+)'] ); + } + return $endpoints; + }); + } + + /** + * It removes the WordPress version from the header, removes the REST API link from the header, removes the X-Pingback + * header, removes the X-Powered-By header, and adds a random server header + * + * @param array $headers The headers array that is passed to the function. + * + * @return array The headers are being returned. + */ + public function cf7a_protect_wp($headers) { + + /* removes version number (WordPress/WooCommerce) */ + remove_action( 'wp_head', 'wp_generator' ); + remove_action('wp_head', 'woo_version'); + + remove_action( 'wp_head', 'rest_output_link_wp_head'); + remove_action( 'template_redirect', 'rest_output_link_header', 20); + + unset( $headers['X-Pingback'] ); + unset( $headers['X-Powered-By'] ); + + if ( empty( $headers['X-Frame-Options'] )) { + $headers['X-Frame-Options'] = 'SAMEORIGIN'; + } + if ( empty( $headers['X-Content-Type-Options'] )) { + $headers['X-Content-Type-Options'] = 'nosniff'; + } + if ( empty( $headers['X-XSS-Protection'] )) { + $headers['X-XSS-Protection'] = '1; mode=block'; + } + if ( empty( $headers['Strict-Transport-Security'] )) { + $headers['Strict-Transport-Security'] = 'max-age=31536000'; + } + + return $headers; + } + /** * Register the JavaScript for the admin area. * diff --git a/package.json b/package.json index c1859ce..cc18be2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "cf7-antispam", "author": "Erik Golinelli", "license": "GPL-2.0-only", - "version": "0.4.2", + "version": "0.4.3", "description": "AntiSpam for Contact Form 7", "files": [ "admin/*", diff --git a/readme.txt b/readme.txt index 9b3397b..7c1dc89 100644 --- a/readme.txt +++ b/readme.txt @@ -4,40 +4,44 @@ Tags: anti-spam, antispam, mail, blacklist, form, security, honeypot, geoip, Requires at least: 5.4 Tested up to: 6.1 Requires PHP: 5.6 -Stable tag: 0.4.2 +Stable tag: 0.4.3 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html A trustworthy antispam plugin for Contact Form 7. Simple but effective. == Description == -Antispam for Contact Form 7 is a free plugin for Contact Form 7, that without boring you with configurations, block bots from flood your email inbox. -CF7A use several in and off page bots traps and an auto-learning mechanism based on a statistical "Bayesian" spam filter called B8. -CF7-AntiSpam adds some functionalities also to [Flamingo](https://wordpress.org/plugins/flamingo/): if both are installed Flamingo will be used as interface for the antispam system and some convenient features will be added, such a dashboard widget or a function to resend emails. +The antispam you're using isn't working well, is it? Maybe because it's not using the correct method*** to stop the type of bot that's attacking you, but I think I have a solution! +Antispam for Contact Form 7 is a free plugin for Contact Form 7 that blocks bots from flooding your mailbox, without tedious configuration and without captcha (which usually causes loss of conversions and sometimes are blocking for real users). +To do this we use different in and off page bots traps and an auto-learning mechanism based on a statistical "Bayesian" spam filter called B8. +CF7-AntiSpam works well and adds some functionalities to [Flamingo](https://wordpress.org/plugins/flamingo/). If both are installed Flamingo will gain some additional controls and a dashboard widget will be added in order to show spam and ham mail. == SETUP == -**Basic** - install & go! no action required to get the standard protection. In this case only some protections may be enabled like fingerprinting, language checks and honeypots. +**Basic** - install & go! No Configuration / keys / registrations required to get the antispam protection. In this case only some protections may be enabled like fingerprinting, language checks and honeypots. **Advanced** - CF7A needs to parse the input message field of your form to analyze properly the email content with its dictionary. -So the only thing you need to do is add to (for each contact form) 'flamingo_message: "[your-message]"' in the same way you do for [flamingo](https://contactform7.com/save-submitted-messages-with-flamingo/). -This is **required for advanced text statistical analysis**, without this B8 filter will couldn't be enabled. +So you need to add a "marker" to "notify" the antispam to check this field (you need to do this for each contact form of your website) +so you need to add 'flamingo_message: "[your-message]"' for each additional settings panel of each contact form you need to secure. The method is the same as you use with [Flamingo](https://contactform7.com/save-submitted-messages-with-flamingo/). +I know, this is boring but is **required for advanced text statistical analysis**, without this B8 filter will couldn't be enabled. +**GeoIP** - (optional) Enable this functionality if you need to restrict which countries (or languages) can email you and which cannot. +In order to enable GeoIp you need to agree GeoLite2 End User License Agreement and sign up GeoLite2 Downloadable Databases, in this way you will obtain the key requested to download the database. +To find out more, read the information in the dedicated section of the cf7-antispam plugin settings and follow the steps. ==Antispam Available Tests== ✅ Browser Fingerprinting -✅ Language checks (Geo-ip, http headers and browser - crosschecked) +✅ Language checks (Geo-ip, http headers and browser - cross-checked) ✅ Honeypot -✅ Honeyform* +⚠️Honeyform* ✅ DNS Blacklists ✅ Blacklists (with automatic ban after N failed attempts, user defined ip exclusion list) ✅ Hidden fields with encrypted unique hash ✅ Time elapsed (with min/max values) ✅ Prohibited words in message/email and user agent ✅ B8 statistical "Bayesian" spam filter +🆕 Identity protection -But why are there so many tests? Because there are so many types of bots, e.g. the phantom-based bot fails with fingerprinting but is clever with honeypots or the python-written bot fails honeypots but is proficient with metadata forgery! - -==Install Flamingo to unlock the spam manager!== -Not using Flamingo? well I suggest you to install it, even if it is not essential. In this way from your wordpress installation you will be able to review emails and "re-teach" B8 what is spam and what is not (might be useful in the first times if some mail spam pass through). -And if you already use Flamingo? Even better! But remember, to add 'flamingo_message: "[your-message]"' to advanced settings (as you do for the other flamingo labels) before activation. +==Extends Flamingo and turns it into a spam manager!== +In this way you will be able to review emails and "teach" to B8 what is spam and what is not (might be useful in the first times if some mail spam pass through). +And if you already use Flamingo? Even better! But remember, to add 'flamingo_message: "[your-message]"' to advanced settings (as you do for the other flamingo labels) before activation (or checkuot advanced options "rebuild dictionary"). While activating CF7A all previous collected mail will be parsed and B8 will learn and build its vocabulary. In this way you will start with a pre-trained algorithm. Super cool! Notes: - On the right side of Flamingo inbound page I've added a new column that show the mail spamminess level @@ -45,13 +49,26 @@ Notes: - Before activate this plugin please be sure to mark all spam mail as spam in flamingo inbound, in this way the B8 algorithm will be auto-trained - Don't delete a spam message from ham if you receive it, rather put it in spam to teach B8 how to recognise the difference! +==B8 statistical "Bayesian" Filter== +Originally created by [Gary Robinson](https://en.wikipedia.org/wiki/Gary_Robinson) [b8 is a statistical "Bayesian"](https://www.linuxjournal.com/article/6467) spam filter implemented in PHP. +The filter tells you whether a text is spam or not, using statistical text analysis. What it does is: you give b8 a text and it returns a value between 0 and 1, saying it's ham when it's near 0 and saying it's spam when it's near 1. See [How does it work?](https://nasauber.de/opensource/b8/readme.html#how-does-it-work) for details about this. +To be able to distinguish spam and ham (non-spam), b8 first has to learn some spam and some ham texts. If it makes mistakes when classifying unknown texts or the result is not distinct enough, b8 can be told what the text actually is, getting better with each learned text. +This takes place on your own server without relying on third-party services. +More info: [nasauber.de](https://nasauber.de/opensource/b8/) + +=Identity protection= +To fully protect the forms, it may be necessary to enable a couple of additional controls, because bots use the public data of the website to spam on it. +- The first is user related and denies those who are not logged in the possibility of asking (sensitive) information about the user via wp-api and the protection for the xmlrpc exploit wordpress. +- The second one is the WordPress protection that will obfuscate sensitive WordPress and server data, adding some headers in order to enhance security against xss and so on. +Will be hidden the WordPress and WooCommerce version (wp_generator, woo_version), pingback (X-Pingback), server (nginx|apache|...) and php version (X-Powered-By), enabled xss protection headers (X-XSS-Protection), removes rest api link from header (but it will only continue to work if the link is not made public). + == Privacy Notices == AntiSpam for Contact Form 7 only process the ip but doesn't store any personal data, but anyway it creates a dictionary of spam and ham words in the wordpress database. This database may contain words that are in the e-mail message, so can contain also personal data. This data can be "degenerated" that means the words that were in the e-mail might have been changed. The purpose of this word collecting is to build a dictionary used for the spam detection. == Installation == -1. Upload the entire `contact-form-7-antispam` folder to the `/wp-content/plugins/` directory. +1. Upload the entire `cf7-antispam` folder to the `/wp-content/plugins/` directory. 2. Activate the plugin through the 'Plugins' menu in WordPress, you MUST have Contact Form 7 installed and enabled. 3. Setup advanced settings in Contact Form 7 in the same way you do for flamingo, but add also 'flamingo_message: "[your-message]"' - reference https://contactform7.com/save-submitted-messages-with-flamingo/ 4. The configuration page for this plugin is located in the submenu "Antispam" under the Contact Form 7 menu @@ -80,6 +97,16 @@ if you want to help me, [GitHub](https://github.com/erikyo/contact-form-7-antisp NO, nobody can guarantee that, and anyone who tells you that is lying. But luckily, bots are limited by the fact that they don't use a real browser and they use fairly repetitive routes which can be recognised. +=Why I need to install Flamingo to get the full AntiSpam manager functionalities?= + +Contact form 7 is made this way, the main plugin is made to be extended with other modules and this has resulted in many 3rd party plugins like mine! There is already a module for handling received emails, why should I redo it? And, in this way I can focus on my plugin, I believe the "power" of cf7 is just that and I invite you to check how many other nice and free extensions there are! + +=Why are there so many antispam-tests?= + +Because there are so many types of bots in this way detect them all! + +Phantom-based bots fail with fingerprinting but are proficient with honeypots, while bots written in python fail with honeypots but are proficient with metadata forgery! + =How spam score works= The system used to evaluate the e-mail is a non-proportional scoring system and each test have a different score (and can be customised with the advanced settings). When the mail score is equal to or greater than 1 it is considered spam. @@ -88,9 +115,11 @@ The system used to evaluate the e-mail is a non-proportional scoring system and Some standard test are Elapsed time, Auto-Blacklisting, Prohibited IP/strings and, in addition, we got some advanced test like HoneyPots, HoneyForms and the browser FingerPrinting. -=HoneyForm, or you mean Honeypot?= +=*HoneyForm, or you mean Honeypot?= -No, I mean HoneyForm! This is a hidden, bogus form that bots cannot help but fill in, as it is part of the page code for them and they rarely check the visibility of an element. This form is completely a trap and when the bot fills it in, it is banned. +No, I mean HoneyForm! This is a hidden, bogus form that bots will fill, as it is part of the page code for them and they rarely check the visibility of an element. While honeypots can be easily spotted by some bots, these forms are not because they have the same characteristics as a 'normal' form, and it is impossible to distinguish them without truly visiting the page. + +This is the first time they have been used, at the moment they seem to work and be effective, but consider this an experimental feature! (ps let me know your feedback about) =But the standard Honeypot?= @@ -127,6 +156,13 @@ Enable **extended debug mode** ("CF7ANTISPAM_DEBUG" has to be enabled) - disable == Changelog == += 0.4.3 = +* Fixes an issue with honeypot placeholder (thanks to @ardsoms and @edodemo for the report) +* User enumeration protection +* Xmlrpc bruteforce protection +* Http headers obfuscation +* Add a new filter (cf7a_additional_max_honeypots) to limit the number of automatic honeypots (default: 5) + = 0.4.2 = * Dashboard widget updated (adds a new filter 'cf7a_dashboard_max_mail_count' to limit the maximum value of displayed mail, default 25) * UI enhancements - labels in the flamingo inbound page and the blacklist table