PluginProbe
NETSENSAI Shield / trunk
NETSENSAI Shield vtrunk
1.6.1 trunk 1.1 1.2 1.3 1.4 1.4.1 1.4.2 1.4.3 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.6.0
← All changes | includes/disable_xml_rpc.php +148 -67 1.2trunk View file →
@@ -1,67 +1,148 @@
1 -<?php
2 -// Exit if accessed directly.
3 -if ( ! defined( 'ABSPATH' ) ) {
4 - exit;
5 -}
6 -
7 -/**
8 - * Disable XML-RPC and block direct access to xmlrpc.php.
9 - *
10 - * If the "Disable XML-RPC" option is enabled, this function disables the XML-RPC functionality,
11 - * blocks direct access to xmlrpc.php by sending a 403 Forbidden header, and modifies the .htaccess file
12 - * to deny access to xmlrpc.php. If the option is disabled, any previously added block is removed.
13 - *
14 - * @return void
15 - */
16 -function ns_shield_disable_xml_rpc() {
17 - global $wp_filesystem;
18 -
19 - // Initialize the filesystem if not yet set up.
20 - if ( empty( $wp_filesystem ) ) {
21 - require_once ABSPATH . 'wp-admin/includes/file.php';
22 - WP_Filesystem();
23 - }
24 -
25 - // Check if the "Disable XML-RPC" switch is enabled.
26 - $is_xmlrpc_disabled = get_option( 'ns_shield_xml_rpc', false );
27 -
28 - if ( $is_xmlrpc_disabled ) {
29 - // Disable XML-RPC functionality.
30 - add_filter( 'xmlrpc_enabled', '__return_false' );
31 -
32 - // Block direct access to xmlrpc.php.
33 - add_action( 'init', function() {
34 - if ( isset( $_SERVER['REQUEST_URI'] ) && strpos( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ), 'xmlrpc.php' ) !== false ) {
35 - header( 'HTTP/1.1 403 Forbidden' );
36 - $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
37 - if ( stripos( $user_agent, 'curl' ) !== false ) {
38 - exit( 'Protected by Netsensai Shield' );
39 - } else {
40 - exit( 'XML-RPC is disabled on this site.' );
41 - }
42 - }
43 - } );
44 -
45 - // Edit .htaccess to block xmlrpc.php.
46 - $htaccess_file = ABSPATH . '.htaccess';
47 - $htaccess_code = "<Files xmlrpc.php>\n order deny,allow\n deny from all\n</Files>\n";
48 -
49 - if ( $wp_filesystem->exists( $htaccess_file ) && $wp_filesystem->is_writable( $htaccess_file ) ) {
50 - $htaccess_content = $wp_filesystem->get_contents( $htaccess_file );
51 - if ( strpos( $htaccess_content, 'xmlrpc.php' ) === false ) {
52 - $wp_filesystem->put_contents( $htaccess_file, $htaccess_code . $htaccess_content, FS_CHMOD_FILE );
53 - }
54 - }
55 - } else {
56 - // If the switch is disabled, remove any xmlrpc.php block from .htaccess.
57 - $htaccess_file = ABSPATH . '.htaccess';
58 - $htaccess_code = "<Files xmlrpc.php>\n order deny,allow\n deny from all\n</Files>\n";
59 -
60 - if ( $wp_filesystem->exists( $htaccess_file ) && $wp_filesystem->is_writable( $htaccess_file ) ) {
61 - $htaccess_content = $wp_filesystem->get_contents( $htaccess_file );
62 - $updated_content = str_replace( $htaccess_code, '', $htaccess_content );
63 - $wp_filesystem->put_contents( $htaccess_file, $updated_content, FS_CHMOD_FILE );
64 - }
65 - }
66 -}
67 -add_action( 'init', 'ns_shield_disable_xml_rpc' );
1 +<?php
2 +// Exit if accessed directly.
3 +if ( ! defined( 'ABSPATH' ) ) {
4 + exit;
5 +}
6 +
7 +/**
8 + * Return whether an option value represents a checked WordPress checkbox.
9 + *
10 + * @param mixed $value Option value.
11 + * @return bool
12 + */
13 +function ns_shield_htaccess_option_is_enabled( $value ) {
14 + return true === $value || 1 === $value || '1' === $value;
15 +}
16 +
17 +/**
18 + * Store and return a .htaccess synchronization result for later admin feedback.
19 + *
20 + * @param string $option Result option name.
21 + * @param bool $success Whether the operation succeeded.
22 + * @param string $code Machine-readable result code.
23 + * @return array
24 + */
25 +function ns_shield_htaccess_sync_result( $option, $success, $code ) {
26 + $result = array(
27 + 'success' => (bool) $success,
28 + 'code' => sanitize_key( $code ),
29 + 'time' => time(),
30 + );
31 +
32 + update_option( $option, $result, false );
33 +
34 + return $result;
35 +}
36 +
37 +/**
38 + * Synchronize one WordPress-managed .htaccess marker section.
39 + *
40 + * insert_with_markers() locks the file while updating its own marker section,
41 + * leaving WordPress, administrator, and other-plugin rules untouched.
42 + *
43 + * @param string $marker Marker name without BEGIN or END.
44 + * @param string[] $lines Lines to insert, or an empty array to clear them.
45 + * @param string $result_option Option used to store the diagnostic result.
46 + * @return array
47 + */
48 +function ns_shield_sync_htaccess_marker( $marker, $lines, $result_option ) {
49 + $htaccess_file = ABSPATH . '.htaccess';
50 +
51 + if ( ! file_exists( $htaccess_file ) ) {
52 + return ns_shield_htaccess_sync_result( $result_option, false, 'htaccess_missing' );
53 + }
54 +
55 + if ( ! wp_is_writable( $htaccess_file ) ) {
56 + return ns_shield_htaccess_sync_result( $result_option, false, 'htaccess_not_writable' );
57 + }
58 +
59 + if ( ! function_exists( 'insert_with_markers' ) ) {
60 + require_once ABSPATH . 'wp-admin/includes/misc.php';
61 + }
62 +
63 + if ( ! function_exists( 'insert_with_markers' ) ) {
64 + return ns_shield_htaccess_sync_result( $result_option, false, 'markers_unavailable' );
65 + }
66 +
67 + if ( ! insert_with_markers( $htaccess_file, $marker, $lines ) ) {
68 + return ns_shield_htaccess_sync_result( $result_option, false, 'htaccess_write_failed' );
69 + }
70 +
71 + return ns_shield_htaccess_sync_result( $result_option, true, 'updated' );
72 +}
73 +
74 +/**
75 + * Disable XML-RPC at runtime without touching .htaccess.
76 + *
77 + * @return void
78 + */
79 +function ns_shield_disable_xml_rpc() {
80 + if ( ! ns_shield_htaccess_option_is_enabled( get_option( 'ns_shield_xml_rpc', false ) ) ) {
81 + return;
82 + }
83 +
84 + add_filter( 'xmlrpc_enabled', '__return_false' );
85 +
86 + if ( isset( $_SERVER['REQUEST_URI'] ) && false !== strpos( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ), 'xmlrpc.php' ) ) {
87 + header( 'HTTP/1.1 403 Forbidden' );
88 + $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
89 + if ( false !== stripos( $user_agent, 'curl' ) ) {
90 + exit( 'Protected by Netsensai Shield' );
91 + }
92 +
93 + exit( 'XML-RPC is disabled on this site.' );
94 + }
95 +}
96 +add_action( 'init', 'ns_shield_disable_xml_rpc' );
97 +
98 +/**
99 + * Synchronize the XML-RPC marker section after an option save.
100 + *
101 + * @param mixed $value New option value.
102 + * @return array
103 + */
104 +function ns_shield_sync_xml_rpc_htaccess( $value ) {
105 + $lines = ns_shield_htaccess_option_is_enabled( $value )
106 + ? array(
107 + '<Files xmlrpc.php>',
108 + ' order deny,allow',
109 + ' deny from all',
110 + '</Files>',
111 + )
112 + : array();
113 +
114 + return ns_shield_sync_htaccess_marker(
115 + 'NETSENSAI SHIELD XML-RPC',
116 + $lines,
117 + 'ns_shield_xml_rpc_htaccess_sync_result'
118 + );
119 +}
120 +
121 +/**
122 + * Synchronize after the option is first added.
123 + *
124 + * @param string $option Option name.
125 + * @param mixed $value New option value.
126 + * @return void
127 + */
128 +function ns_shield_xml_rpc_option_added( $option, $value ) {
129 + ns_shield_sync_xml_rpc_htaccess( $value );
130 +}
131 +add_action( 'add_option_ns_shield_xml_rpc', 'ns_shield_xml_rpc_option_added', 10, 2 );
132 +
133 +/**
134 + * Synchronize only after the option value actually changes.
135 + *
136 + * @param mixed $old_value Previous option value.
137 + * @param mixed $value New option value.
138 + * @param string $option Option name.
139 + * @return void
140 + */
141 +function ns_shield_xml_rpc_option_updated( $old_value, $value, $option ) {
142 + if ( ns_shield_htaccess_option_is_enabled( $old_value ) === ns_shield_htaccess_option_is_enabled( $value ) ) {
143 + return;
144 + }
145 +
146 + ns_shield_sync_xml_rpc_htaccess( $value );
147 +}
148 +add_action( 'update_option_ns_shield_xml_rpc', 'ns_shield_xml_rpc_option_updated', 10, 3 );