WordPress 任意文件删除漏洞分析与防御插图

前言

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

漏洞利用

mark

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

mark

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

mark

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

mark

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

漏洞分析

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

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行。

/*
前面的代码基本都是准备工作,从数据库将图片的各种信息取出来,删除与图片相关的评论、缓存等。然后到达关键部分
*/

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看一下具体代码。

//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']);