| @@ -56,10 +56,17 @@ | ||
| 56 | 56 | // of a switch that looks on while the class that reads it never runs. |
| 57 | 57 | $xmlrpc_mode = self::resolve_xmlrpc_mode( $this->settings ); |
| 58 | 58 | |
| 59 | 59 | if ( 'full' === $xmlrpc_mode ) { |
| 60 | + // Answer the XML-RPC endpoint here, before core gets as far as | |
| 61 | + // building the server object. Until 2.9.9 this pointed the | |
| 62 | + // wp_xmlrpc_server_class filter at a class that does not exist, | |
| 63 | + // which is not a block but an uncaught Error: xmlrpc.php answered | |
| 64 | + // 500 with an empty body and wrote a PHP fatal to the log on every | |
| 65 | + // hit, and that endpoint is one of the most hammered by bots. | |
| 66 | + $this->block_xmlrpc_request(); | |
| 67 | + | |
| 60 | 68 | add_filter( 'xmlrpc_enabled', '__return_false' ); |
| 61 | - add_filter( 'wp_xmlrpc_server_class', array( $this, 'disable_xmlrpc_server' ) ); | |
| 62 | 69 | remove_action( 'wp_head', 'rsd_link' ); |
| 63 | 70 | remove_action( 'wp_head', 'wlwmanifest_link' ); |
| 64 | 71 | } elseif ( 'pingback' === $xmlrpc_mode ) { |
| 65 | 72 | add_filter( 'xmlrpc_methods', array( $this, 'disable_pingback_methods' ) ); |
| @@ -152,14 +159,34 @@ | ||
| 152 | 159 | return 'full'; |
| 153 | 160 | } |
| 154 | 161 | |
| 155 | 162 | /** |
| 156 | - * Replace the XML-RPC server class with one that answers nothing. | |
| 163 | + * Answer an XML-RPC request with a plain 403 and stop | |
| 157 | 164 | * |
| 158 | - * @return string | |
| 165 | + * Does nothing outside an XML-RPC request, so it is safe to call while the | |
| 166 | + * module is wiring its hooks. XMLRPC_REQUEST is defined at the top of | |
| 167 | + * xmlrpc.php, before wp-load.php, so it is already there by the time | |
| 168 | + * plugins load. | |
| 169 | + * | |
| 170 | + * The modules are built on init priority 1, which is inside init, so the | |
| 171 | + * translation functions are safe to use here. | |
| 172 | + * | |
| 173 | + * @since 2.9.9 | |
| 159 | 174 | */ |
| 160 | - public function disable_xmlrpc_server() { | |
| 161 | - return 'wp_xmlrpc_server_disabled'; | |
| 175 | + private function block_xmlrpc_request() { | |
| 176 | + if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) { | |
| 177 | + return; | |
| 178 | + } | |
| 179 | + | |
| 180 | + if ( ! headers_sent() ) { | |
| 181 | + status_header( 403 ); | |
| 182 | + nocache_headers(); | |
| 183 | + $charset = sanitize_text_field( (string) get_option( 'blog_charset', 'UTF-8' ) ); | |
| 184 | + header( 'Content-Type: text/plain; charset=' . ( '' !== $charset ? $charset : 'UTF-8' ) ); | |
| 185 | + } | |
| 186 | + | |
| 187 | + echo esc_html__( 'XML-RPC services are disabled on this site.', 'vigilante' ); | |
| 188 | + exit; | |
| 162 | 189 | } |
| 163 | 190 | |
| 164 | 191 | public function disable_pingback_methods( $methods ) { |
| 165 | 192 | unset( $methods['pingback.ping'] ); |