前言
CSRF to RCE 这个标题是原作者在博文中的写法,其实颇有些标题党的意味。通篇分析下来我们会发现这个漏洞其实就是一个存储型XSS,而RCE是因为获取管理权限后在WordPress后台可以直接编辑php文件写马,也就能实现RCE…
很多地方包括面试会提到XSS与CSRF的区别,其实从这个漏洞就能看出,XSS是实现CSRF的一种方式。可以说两者是不同维度的东西,XSS强调攻击的代码实现,而CSRF着重于攻击达成的结果。
分析
WordPress引入了Nonce安全机制,nonce值与用户的身份以及操作的属性有关,因此不同用户或不同操作都会产生不同的nonce值。由于校验nonce并阻止请求会影响评论处的某些功能,WP选择根据nonce值进行不同的过滤,比较直观的概括出来就是:
- 管理员权限 + nonce值校验成功 = 无任何过滤
- 管理员权限 + nonce值校验失败 =
wp_filter_post_kses()
过滤 - 普通用户 =
wp_filter_kses()
过滤
关于nonce检验的关键代码在/wp-includes/comment.php
第3240行,wp_handle_comment_submission()
函数中:
当创建评论的是具备unfiltered_html
属性的管理员且nonce值正确时,不做任何处理;当nonce校验失败后,进入kses_init_filters()
函数:
发现nonce校验失败则添加wp_filter_post_kses()
过滤器来过滤评论,否则使用wp_filter_kses()
过滤。而两者的严格程度不同:
此处可以看到传入的第二参数不同,来看看wp_filter_kses()
的current_filter()
:
跟进$wp_current_filter()
:
//comment.php:1943
function wp_filter_comment( $commentdata ) {
if ( isset( $commentdata['user_ID'] ) ) {
...
$commentdata['comment_content'] = apply_filters( 'pre_comment_content', $commentdata['comment_content'] );
pre_comment_content
会为<a>
标签中的rel
属性添加nofollow
属性值。注意以上这部分说的是wp_filter_kses()
函数,而wp_filter_post_kses()
函数会完全保留<a>
标签中的rel
属性,来看wp_filter_post_kses()
第二参数为post
的情况:
//kses.php:829
function wp_kses_allowed_html( $context = '' ) {
global $allowedposttags, $allowedtags, $allowedentitynames;
if ( is_array( $context ) ) {
return apply_filters( 'wp_kses_allowed_html', $context, 'explicit' );
}
switch ( $context ) {
case 'post':
/** This filter is documented in wp-includes/kses.php */
$tags = apply_filters( 'wp_kses_allowed_html', $allowedposttags, $context );
// 5.0.1 removed the `<form>` tag, allow it if a filter is allowing it's sub-elements `<input>` or `<select>`.
if ( ! CUSTOM_TAGS && ! isset( $tags['form'] ) && ( isset( $tags['input'] ) || isset( $tags['select'] ) ) ) {
$tags = $allowedposttags;
$tags['form'] = array(
'action' => true,
'accept' => true,
'accept-charset' => true,
'enctype' => true,
'method' => true,
'name' => true,
'target' => true,
);
/** This filter is documented in wp-includes/kses.php */
$tags = apply_filters( 'wp_kses_allowed_html', $tags, $context );
}
而$allowedposttags
将<a>
标签的rel
属性设置为了白名单:
接下来,WordPress为适配SEO会将<a>
标签的属性解析成一个关联数组:
之后再将原本的<a>
标签拼接起来:
当我们设置<a>
标签中的title='XSS " onmouseover=alert(1) id="'
时,有$name
值为title
,$value
值为XSS " onmouseover=alert(1) id="
,拆分到数组中再拼接到$html
,则
$html
= title="XSS " onmouseover=alert(1) id=""
则返回的是<a title="XSS" onmouseover=alert(1) id="" rel=\"$rel\">
这样也就实现了基于事件的存储型XSS攻击(不论rel
属性值是否被修改),我们可以执行任意JS脚本的攻击,对管理员的触发请求进行伪造即完成了原文所谓的CSRF,而伪造管理员身份写php文件也就能成功getshell,完成RCE。