前言

这个漏洞在去年就已经批露,但是wordpress官方一直没有修复。攻击者可以通过该漏洞删除wp-config.php,进而重装wordpress充值admin密码来getshell。唯一的一点限制就是需要媒体文件的修改权限,这里只需要作者权限即可,不需要管理员。看完之后,就想起了不久前的铁三,被wordpress支配的恐惧。。。

漏洞利用

mark

首先上传一个图片文件,然后编辑这个图片。

mark

从源码中找到_wpnonce的值,然后使用curl或burp构造http请求。

mark

thumb参数为想删除的文件,接下来在编辑页面,点击永久删除。

mark

若成功删除,页面会直接跳转到安装界面。

漏洞分析

既然是任意文件删除漏洞,那我们就从删除功能入手,先来看wp-admin/post.php的246-268行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
case 'delete':
check_admin_referer('delete-post_' . $post_id);

if ( ! $post )
wp_die( __( 'This item has already been deleted.' ) );

if ( ! $post_type_object )
wp_die( __( 'Invalid post type.' ) );

if ( ! current_user_can( 'delete_post', $post_id ) )
wp_die( __( 'Sorry, you are not allowed to delete this item.' ) );

if ( $post->post_type == 'attachment' ) { //删除附件
$force = ( ! MEDIA_TRASH );
if ( ! wp_delete_attachment( $post_id, $force ) )
wp_die( __( 'Error in deleting.' ) );
} else {
if ( ! wp_delete_post( $post_id, true ) )
wp_die( __( 'Error in deleting.' ) );
}

wp_redirect( add_query_arg('deleted', 1, $sendback) );
exit();

由于我们删除的是图片附件,所以程序会进入wp_delete_attachment函数,跟进:

wp-include/post.php,函数太长,只截取关键部分,5061-5088行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
前面的代码基本都是准备工作,从数据库将图片的各种信息取出来,删除与图片相关的评论、缓存等。然后到达关键部分
*/

if ( ! empty($meta['thumb']) ) {
// Don't delete the thumb if another attachment uses it.
if (! $wpdb->get_row( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE meta_key = '_wp_attachment_metadata' AND meta_value LIKE %s AND post_id <> %d", '%' . $wpdb->esc_like( $meta['thumb'] ) . '%', $post_id)) ) {
$thumbfile = str_replace(basename($file), $meta['thumb'], $file);
/** This filter is documented in wp-includes/functions.php */
$thumbfile = apply_filters( 'wp_delete_file', $thumbfile );
@ unlink( path_join($uploadpath['basedir'], $thumbfile) );
}
}

// Remove intermediate and backup images if there are any.
if ( isset( $meta['sizes'] ) && is_array( $meta['sizes'] ) ) {
foreach ( $meta['sizes'] as $size => $sizeinfo ) {
$intermediate_file = str_replace( basename( $file ), $sizeinfo['file'], $file );
/** This filter is documented in wp-includes/functions.php */
$intermediate_file = apply_filters( 'wp_delete_file', $intermediate_file );
@ unlink( path_join( $uploadpath['basedir'], $intermediate_file ) );
}
}

if ( is_array($backup_sizes) ) {
foreach ( $backup_sizes as $size ) {
$del_file = path_join( dirname($meta['file']), $size['file'] );
/** This filter is documented in wp-includes/functions.php */
$del_file = apply_filters( 'wp_delete_file', $del_file );
@ unlink( path_join($uploadpath['basedir'], $del_file) );
}
}

if ( is_array($backup_sizes) ) {
foreach ( $backup_sizes as $size ) {
$del_file = path_join( dirname($meta['file']), $size['file'] );
/** This filter is documented in wp-includes/functions.php */
$del_file = apply_filters( 'wp_delete_file', $del_file );
@ unlink( path_join($uploadpath['basedir'], $del_file) );
}
}

wp_delete_file( $file );

整个wp_delete_attachment函数只有这几处调用了unlink函数,虽然表面上我们只上传了一张图片,但是wordpress后端为了其他功能将图片处理成了很多张图片。

mark

这几处中只有第一个,即删除thumbfile时,文件名是可能可控的。那么可控点在哪呢?还记得漏洞利用的第一步吗?现在我们就回到wp-admin/post.php看一下具体代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
//178-189行
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 ); //更新数据库中的信息

可以看到这里$_POST['thumb']没有经过任何过滤直接赋值,然后进入了数据库中。

mark

漏洞防御

  1. 过滤. \等关键字符
  2. $newmeta['thumb'] = $_POST['thumb'];改为$newmeta['thumb'] = basename($_POST['thumb']);

参考文章

wordpress任意文件删除漏洞分析

WARNING: WordPress File Delete to Code Execution