PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.2
4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 All 200 releases
← All changes | includes/Core/PluginInstaller.php +63 -24 4.4.14.9.2 View file →
@@ -53,31 +53,68 @@
53 53 if (empty($slug)) {
54 54 return new WP_Error('empty_arg', __('Argument should not be empty.', 'betterdocs'));
55 55 }
56 56
57 - $response = wp_remote_post(
58 - 'http://api.wordpress.org/plugins/info/1.0/',
59 - [
60 - 'body' => [
61 - 'action' => 'plugin_information',
62 - 'request' => serialize((object) [
63 - 'slug' => $slug,
64 - 'fields' => [
65 - 'version' => false,
66 - ],
67 - ]),
68 - ],
69 - ]
70 - );
57 + // Use core's plugins_api() instead of a hand-rolled request. The old code
58 + // POSTed to plaintext http://api.wordpress.org and passed the response
59 + // body straight to unserialize(), so anyone able to intercept that
60 + // connection could inject a PHP-object-injection payload or a malicious
61 + // download_link. plugins_api() talks to api.wordpress.org over HTTPS and
62 + // returns a decoded object — no plaintext transport, no unserialize().
63 + if (!function_exists('plugins_api')) {
64 + include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
65 + }
71 66
72 - if (is_wp_error($response)) {
73 - return $response;
67 + $response = plugins_api('plugin_information', [
68 + 'slug' => $slug,
69 + 'fields' => [
70 + 'version' => true,
71 + ],
72 + ]);
73 +
74 + if (is_wp_error($response) || !is_object($response)) {
75 + return is_wp_error($response) ? $response : new WP_Error('plugins_api_failed', __('Could not retrieve plugin information.', 'betterdocs'));
74 76 }
75 77
76 - return unserialize(wp_remote_retrieve_body($response));
78 + // Bind the package to the requested slug and to an https WordPress.org
79 + // host before anything installs it.
80 + if (isset($response->slug) && $response->slug !== $slug) {
81 + return new WP_Error('slug_mismatch', __('Plugin information did not match the requested plugin.', 'betterdocs'));
82 + }
83 +
84 + if (isset($response->download_link) && !$this->is_allowed_package_url($response->download_link)) {
85 + return new WP_Error('bad_package_host', __('Plugin download URL is not an approved WordPress.org address.', 'betterdocs'));
86 + }
87 +
88 + return $response;
77 89 }
78 90
79 91 /**
92 + * Whether a package URL is safe to hand to the upgrader: https on a
93 + * WordPress.org host. Prevents a tampered response from redirecting the
94 + * install to an attacker-controlled archive.
95 + *
96 + * @param string $url
97 + * @return bool
98 + */
99 + protected function is_allowed_package_url($url)
100 + {
101 + if (!is_string($url) || $url === '') {
102 + return false;
103 + }
104 +
105 + $parts = wp_parse_url($url);
106 +
107 + if (empty($parts['scheme']) || strtolower($parts['scheme']) !== 'https' || empty($parts['host'])) {
108 + return false;
109 + }
110 +
111 + $host = strtolower($parts['host']);
112 +
113 + return in_array($host, ['downloads.wordpress.org', 'wordpress.org', 'www.wordpress.org'], true);
114 + }
115 +
116 + /**
80 117 * install_plugin
81 118 *
82 119 * @param mixed $slug
83 120 * @param bool $active
@@ -129,9 +166,9 @@
129 166 * @return mixed bool|WP_Error
130 167 */
131 168 public function upgrade_plugin($basename = '')
132 169 {
133 - if (empty($slug)) {
170 + if (empty($basename)) {
134 171 return new WP_Error('empty_arg', __('Argument should not be empty.', 'betterdocs'));
135 172 }
136 173
137 174 include_once ABSPATH . 'wp-admin/includes/file.php';
@@ -151,12 +188,13 @@
151 188 if(!current_user_can( 'install_plugins' )) {
152 189 wp_send_json_error(__('you are not allowed to do this action', 'betterdocs'));
153 190 }
154 191
155 - $slug = isset( $_POST['slug'] ) ? sanitize_text_field( $_POST['slug'] ) : '';
192 + $slug = isset( $_POST['slug'] ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : '';
156 193 $result = $this->install_plugin( $slug );
157 194
158 - if( isset( $_POST['promotype'] ) && 'eb-banner' === $_POST['promotype'] ) {
195 + $promotype = isset( $_POST['promotype'] ) ? sanitize_text_field( wp_unslash( $_POST['promotype'] ) ) : '';
196 + if ( 'eb-banner' === $promotype ) {
159 197 wp_remote_get( 'https://essential-addons.com/essential-blocks-install-gutenberg' );
160 198 }
161 199
162 200 if ( is_wp_error( $result ) ) {
@@ -173,9 +211,9 @@
173 211 if(!current_user_can( 'update_plugins' )) {
174 212 wp_send_json_error(__('you are not allowed to do this action', 'betterdocs'));
175 213 }
176 214
177 - $basename = isset( $_POST['basename'] ) ? sanitize_text_field( $_POST['basename'] ) : '';
215 + $basename = isset( $_POST['basename'] ) ? sanitize_text_field( wp_unslash( $_POST['basename'] ) ) : '';
178 216 $result = $this->upgrade_plugin( $basename );
179 217
180 218 if (is_wp_error($result)) {
181 219 wp_send_json_error($result->get_error_message());
@@ -192,9 +230,9 @@
192 230 if(!current_user_can( 'activate_plugins' )) {
193 231 wp_send_json_error(__('you are not allowed to do this action', 'betterdocs'));
194 232 }
195 233
196 - $basename = isset( $_POST['basename'] ) ? sanitize_text_field( $_POST['basename'] ) : '';
234 + $basename = isset( $_POST['basename'] ) ? sanitize_text_field( wp_unslash( $_POST['basename'] ) ) : '';
197 235 $result = activate_plugin( $basename, '', false, true );
198 236
199 237 if ( is_wp_error( $result ) ) {
200 238 wp_send_json_error( $result->get_error_message() );
@@ -213,9 +251,9 @@
213 251 if ( ! current_user_can( 'activate_plugins' ) ) {
214 252 wp_send_json_error( __( 'you are not allowed to do this action', 'betterdocs' ) );
215 253 }
216 254
217 - $basename = isset( $_POST['basename'] ) ? sanitize_text_field( $_POST['basename'] ) : '';
255 + $basename = isset( $_POST['basename'] ) ? sanitize_text_field( wp_unslash( $_POST['basename'] ) ) : '';
218 256 deactivate_plugins( $basename, true );
219 257
220 258 wp_send_json_success( __( 'Plugin is deactivated successfully!', 'betterdocs' ) );
221 259 }
@@ -222,9 +260,10 @@
222 260
223 261 public function ajax_auto_active_even_not_installed() {
224 262 check_ajax_referer( 'betterdocs-wpdeveloper-plugins', 'security' );
225 263
226 - if ( $this->get_local_plugin_data( $_POST['basename'] ) === false ) {
264 + $basename = isset( $_POST['basename'] ) ? sanitize_text_field( wp_unslash( $_POST['basename'] ) ) : '';
265 + if ( $this->get_local_plugin_data( $basename ) === false ) {
227 266 $this->ajax_install_plugin();
228 267 } else {
229 268 $this->ajax_activate_plugin();
230 269 }