wordpress主题dmeng存在XSS跨站

由于某编辑辛苦弄了一套主题。然后被某墨盒测试出来一个XSS漏洞。然后就丢给我。让我看看这个漏洞能不能修复。
首先找到问题所在,是在发送私信的地方存在对参数每一过滤好造成的。读取的时候也是没有处理。直接给打印出来了。
首先找到问题所在文件
themes\dmeng2.0\author.php 86行左右

	//~ 私信start
	$get_pm = isset($_POST['pm']) ? trim($_POST['pm']) : '';           //获取传递来的pm赋值给$get_pm 
	if( isset($_POST['pmNonce']) && $get_pm && is_user_logged_in() ){  //检测验证码,是否登录,以及PM是否存在
		if ( ! wp_verify_nonce( $_POST['pmNonce'], 'pm-nonce' ) ) {
			$message = __('安全认证失败,请重试!','dmeng');
		}else{
			$pm_title = json_encode(array(
				'pm' => $curauth->ID,
				'from' => $current_user->ID
			));
			if( add_dmeng_message( $curauth->ID, 'unrepm', '', $pm_title, $get_pm ) ){  //直接发送$get_pm
				//~ 发邮件通知
				if(is_email($curauth->dmeng_verify_email)){
					$m_headline = sprintf(__('%1$s给你发来了私信','dmeng'), $current_user->display_name);
					$m_content = '<h3>'.sprintf( __('%1$s,你好!','dmeng'), $curauth->display_name ).'</h3><p>'.sprintf( __('%1$s给你发来了<a href="%2$s" target="_blank">私信</a>,快去看看吧:<br> %3$s','dmeng'), $current_user->display_name, htmlspecialchars( add_query_arg('tab', 'message', get_author_posts_url( $curauth->ID )) ), $get_pm).'</p>'; //直接显示$get_pm
					dmeng_send_email( $curauth->dmeng_verify_email, $m_headline, $m_content );
				}
				$message = __('发送成功!','dmeng');
			}
		}
	}

可以看出来$get_pm在整个流程中并没有被转码或者其他的操作。直接带入数据库并显示出来了。所以,发信的内容为我们精心构造的XSS代码。就再你查看私信的时候就触发了。如果发信给管理员呢..
10
我们继续看下add_dmeng_message函数
在文件dmeng2.0\inc\message.php

function add_dmeng_message( $uid=0, $type='', $date='', $title='', $content='' ){

	$uid = intval($uid);
	$title = sanitize_text_field($title);
	
	if( !$uid || empty($title) ) return;

	$type = $type ? sanitize_text_field($type) : 'unread';
	$date = $date ? $date : current_time('mysql');
	$content = htmlspecialchars($content); //对content进行了htmlspecialchars处理。不过后面却使用了htmlspecialchars进行了还原。
	
	global $wpdb;
	$table_name = $wpdb->prefix . 'dmeng_message';

	if($wpdb->query( "INSERT INTO $table_name (user_id,msg_type,msg_date,msg_title,msg_content) VALUES ('$uid', '$type', '$date', '$title', '$content')" ))
		return 1;
	
	return 0;
	
}

在后台也有一个显示的

if($tab=='repm'){ 
	
	$table_name = $wpdb->prefix . 'dmeng_message';
	
	$sql_where = "FROM $table_name WHERE msg_type='repm' OR msg_type='unrepm'";
	
	$count =  $wpdb->get_var( "SELECT count(msg_id) ".$sql_where );
	
	$messages = $wpdb->get_results( "SELECT user_id,msg_date,msg_title,msg_content ".$sql_where." ORDER BY msg_id DESC LIMIT $offset,$limit" );
	if( $messages ){
		$output = '';
		foreach( $messages as $message ){
			$repm = json_decode($message->msg_title);
			$output .= '<p>'.sprintf( __('%1$s %2$s 给 %3$s 发私信:%4$s', 'dmeng'),
									$message->msg_date,
									'<a href="'.dmeng_get_user_url('message', $repm->from).'" target="_blank">'.get_the_author_meta('display_name', $repm->from).'</a>',
									'<a href="'.dmeng_get_user_url('message', $repm->pm).'" target="_blank">'.get_the_author_meta('display_name', $repm->pm).'</a>',
									htmlspecialchars_decode($message->msg_content)). //这里的呢~恩。入库使用的是htmlspecialchars,这里还原了。不是啥都没有做嘛~
									'</p>';
		}
	}
	
}

后来看到作者在群里面说修改的脚本
97行

			if( add_dmeng_message( $curauth->ID, 'unrepm', '', $pm_title, strip_tags($get_pm, '<a><br><p>') ) ){

自己本地测试了下

<?php
$get_pm='<a href=ja&#118;asc&#114;ipt:ale&#114t(document.cookie)>aaa</a>';


echo strip_tags($get_pm, '<a><br><p>');
?>

xss
针对作者自己写的过滤,小小的突破了下。本地测试效果还是不错的
xss
感觉这样的修复,实在是。简单的写了一个过滤$get_pm = htmlspecialchars(addslashes(stripSlashes($get_pm)));

	//~ 私信start
	$get_pm = isset($_POST['pm']) ? trim($_POST['pm']) : '';           //获取传递来的pm赋值给$get_pm 
	$get_pm = htmlspecialchars(addslashes(stripSlashes($get_pm)));     //对$get_pm在进入数据之前进行处理
	if( isset($_POST['pmNonce']) && $get_pm && is_user_logged_in() ){  //检测验证码,是否登录,以及PM是否存在
		if ( ! wp_verify_nonce( $_POST['pmNonce'], 'pm-nonce' ) ) {
			$message = __('安全认证失败,请重试!','dmeng');
		}else{
			$pm_title = json_encode(array(
				'pm' => $curauth->ID,
				'from' => $current_user->ID
			));
			if( add_dmeng_message( $curauth->ID, 'unrepm', '', $pm_title, $get_pm ) ){  //直接发送$get_pm
				//~ 发邮件通知
				if(is_email($curauth->dmeng_verify_email)){
					$m_headline = sprintf(__('%1$s给你发来了私信','dmeng'), $current_user->display_name);
					$m_content = '<h3>'.sprintf( __('%1$s,你好!','dmeng'), $curauth->display_name ).'</h3><p>'.sprintf( __('%1$s给你发来了<a href="%2$s" target="_blank">私信</a>,快去看看吧:<br> %3$s','dmeng'), $current_user->display_name, htmlspecialchars( add_query_arg('tab', 'message', get_author_posts_url( $curauth->ID )) ), $get_pm).'</p>'; //直接显示$get_pm
					dmeng_send_email( $curauth->dmeng_verify_email, $m_headline, $m_content );
				}
				$message = __('发送成功!','dmeng');
			}
		}
	}

这里的过滤呐。还是不变化吧

if($tab=='repm'){ 
	
	$table_name = $wpdb->prefix . 'dmeng_message';
	
	$sql_where = "FROM $table_name WHERE msg_type='repm' OR msg_type='unrepm'";
	
	$count =  $wpdb->get_var( "SELECT count(msg_id) ".$sql_where );
	
	$messages = $wpdb->get_results( "SELECT user_id,msg_date,msg_title,msg_content ".$sql_where." ORDER BY msg_id DESC LIMIT $offset,$limit" );
	if( $messages ){
		$output = '';
		foreach( $messages as $message ){
			$repm = json_decode($message->msg_title);
			$output .= '<p>'.sprintf( __('%1$s %2$s 给 %3$s 发私信:%4$s', 'dmeng'),
									$message->msg_date,
									'<a href="'.dmeng_get_user_url('message', $repm->from).'" target="_blank">'.get_the_author_meta('display_name', $repm->from).'</a>',
									'<a href="'.dmeng_get_user_url('message', $repm->pm).'" target="_blank">'.get_the_author_meta('display_name', $repm->pm).'</a>',
									htmlspecialchars(addslashes(stripSlashes(htmlspecialchars_decode($message->msg_content)))). //这里的呢~恩。入库使用的是htmlspecialchars,这里还原了。不是啥都没有做嘛~于是继续给他xxx
									'</p>';
		}
	}
	
}

最新号外:后来作者使用了wp自带的过滤函数wp_strip_all_tags来过滤了。详情关注
wp-includes\formatting.php

function wp_strip_all_tags($string, $remove_breaks = false) {
	$string = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $string );
	$string = strip_tags($string);

	if ( $remove_breaks )
		$string = preg_replace('/[\r\n\t ]+/', ' ', $string);

	return trim( $string );
}

因此,当然,我个人比较喜欢记录出来全部的东西。这里是采用了替换的方式。个人喜好不一样而已。这里告一个段落...

发表评论