前几天白嫖了 xray 的高级版,因此试着利用 xray+AWVS 的形式来看看能不能找到 CNVD 上公布存在问题 CMS 的漏洞。
我们都知道 cnvd 发布漏洞公告的时候是没有详细细节的,因此只能根据漏洞的种类自己去寻找漏洞点,如果 cnvd 公告里有存在漏洞文件的名称,相对于简单一点,但是没有的话,就相当于从头自己去审计整套系统,于是就找到了一个小型 CMS 当做演示:
本文以 cms 的漏洞挖掘为基础,然后说说自己使用的感受。
xray+AWVS 如何配置的,我就不在这里详细阐述了,官网上已经写得很清楚了,有兴趣可以看:
https://chaitin.github.io/xray/#/scenario/awvs
需要注意两个点:
*
或者你要扫描的地址(之前就是因为自己测试上一个地址忘了修改导致一直没有数据,所以这里强调一下)然后就可以下载 CMS 进行搭建了,搭建过程也比较简单,因此不在赘述,下载地址可见:
https://github.com/chilin89117/ED01-CMS
安装好后就可以添加扫描了。
第一步是首先在本地运行起 xray,命令如下:
./xray webscan --listen 0.0.0.0:1111 --html-output awvs.html
这里的端口可以自己设定,比如我设定的就是 8888:
然后添加已经搭建好的 CMS 的地址:
因为是测试环境,所以我们可以开启登陆选项:
说不定挖掘的东西更多。
其他配置就和 xray 官方介绍的一样,代理模板的端口要和你 xray 监听的端口一致:
点击爬虫模式扫描,创建后,就可以正式扫描目标地址了:
如下图,xray 很快就接受到地址,并开始检测:
最终扫描结果如下:
# | Plugin / VulnType | Target | CreateTime |
---|---|---|---|
#1 | xss | http://192.168.52.1/testcms/aposts.php | 2019-12-22 17:51:05 |
#2 | xss | http://192.168.52.1/testcms/registration.php | 2019-12-22 17:51:08 |
#3 | xss | http://192.168.52.1/testcms/cposts.php | 2019-12-22 17:51:23 |
#4 | xss | http://192.168.52.1/testcms/post.php | 2019-12-22 17:51:38 |
#5 | xss | http://192.168.52.1/testcms/admin/users.php | 2019-12-22 17:51:59 |
#6 | xss | http://192.168.52.1/testcms/admin/posts.php | 2019-12-22 17:56:19 |
#7 | sqldet | http://192.168.52.1/testcms/cposts.php | 2019-12-22 17:51:28 |
#8 | sqldet | http://192.168.52.1/testcms/post.php | 2019-12-22 17:51:40 |
#9 | sqldet | http://192.168.52.1/testcms/admin/users.php | 2019-12-22 17:51:59 |
#10 | sqldet | http://192.168.52.1/testcms/admin/posts.php | 2019-12-22 17:56:09 |
#11 | struts / s2-007 | http://192.168.52.1/testcms/admin/users.php | 2019-12-22 17:55:32 |
以上是我简化的结果,有的文件中可能存两个或者两个以上的ParamKey存在漏洞但只保留一个。
由于同一个文件存在不同的漏洞,因此我们选择以文件分类来分析漏洞。
根据 xray 提供的信息:
u 参数存在问题,找到aposts.php
,关键代码如下:
1if(isset($_GET['u'])) {
2 $uname = mysqli_real_escape_string($con, $_GET['u']);
3
4 ....
5
6 if(!$posts) {
7 $div_class = 'danger';
8 $div_msg = 'Database error: ' . mysqli_error($con);
9 } else {
10 $post_count = mysqli_num_rows($posts);
11 if($post_count == 0) {
12 $page_count = 0;
13 $div_class = 'danger';
14 $div_msg = "Sorry, no posts found for user <strong>'$uname'</strong>.";
15 } else {
16 $page_count = ceil($post_count / 8);
17 $div_class = 'success';
18 $div_msg = " Showing published posts for user <strong>'$uname'</strong>.";
19 $div_msg .= " <a href='index.php'>Show All</a>";
20 }
21 }
22 }
23
GET 取得 u 参数后,通过mysqli_real_escape_string
函数赋值给 uname,然后判断 posts 参数,如果没有找到结果,输出 uname
我们知道mysqli_real_escape_string
函数主要是转义在 SQL 语句中字符串中的特殊字符,并非是 XSS 的过滤函数,因此运用最基本的语句:<script>alert(0)</script>
,即可触发 XSS 漏洞:
根据 xray 提供的信息:
username 参数存在问题,找到registration.php
文件,关键代码如下:
1if(isset($_POST['submit'])) {
2 // clean up inputs
3 $username = mysqli_real_escape_string($con, $_POST['username']);
4
5 ...
6
7
8 <div class="form-group">
9 <label for="username" class="sr-only">Choose a Username</label>
10 <input type="text" name="username" class="form-control"
11 value="<?php echo $username;?>" placeholder="Enter Desired Username *">
12 </div>
13
问题和上一个文件类似,仅用mysqli_real_escape_string
函数进行了过滤,最后在 input 标签内输出,因此只需要闭合 input 标签即可触发 XSS:"><script>alert(0)</script>
由于其他几个文件的 XSS 漏洞也是相似原理,因此不再赘述。
根据 xray 提供的信息:
cid 参数存在问题,找到cposts.php
文件,关键代码如下:
1if(isset($_GET['cid'])) {
2 $cid = mysqli_real_escape_string($con, $_GET['cid']);
3
4 // find total number of posts to determine number of pages for pagination
5 $q = "SELECT * FROM cms_posts where post_cat_id = $cid";
6 $result = mysqli_query($con, $q);
7 $total_posts = mysqli_num_rows($result);
8 $total_pages = ceil($total_posts / POSTSPERPAGE);
9
10 // if $total_pages is 0, set it to 1 so pagination will not look for page 0
11 if($total_pages < 1) {
12 $total_pages = 1;
13 }
14
15 ...
16
17 $q1 = "SELECT cms_posts.*, cms_users.user_image FROM cms_posts
18 INNER JOIN cms_users ON cms_posts.post_author = cms_users.user_uname
19 WHERE post_cat_id = '$cid'
20 AND post_status = 'Published'
21 ORDER BY post_date DESC " . $limit_clause;
22
23 // get category name from database to display in alert box
24 $q2 = "SELECT cat_title FROM cms_categories WHERE cat_id = $cid";
25
26 $result = mysqli_query($con, $q2);
27 $cat_title = mysqli_fetch_array($result);
28
29 ...
同样,仅对注入语句中的关键字符进行转义,但是关键点来了
$q = "SELECT * FROM cms_posts where post_cat_id = $cid";
压根不需要闭合单引号,直接传入了$cid
变量
根据 xray 提供的 payload 测试如下:
漏洞的确存在,可通过以下 payload 获取账号密码:
1 union all select concat(0x7e,user_uname,user_pass,user()) from cms_users limit 1,1
根据 xray 提供的信息:
存在问题参数的为 user_name,找到cposts.php
文件:
1<?php 2if(isset($_GET['source'])) { 3 $source = $_GET['source']; 4} else { 5 $source = ""; 6} 7 8switch($source) { 9 case 'add_user': 10 include 'admin_includes/admin_add_user.php'; 11 break; 12 case 'edit_user': 13 include 'admin_includes/admin_edit_user.php'; 14 break; 15 case 'c': 16 echo 'c'; 17 break; 18 default: 19 include 'admin_includes/admin_view_all_users.php'; 20} 21 22?>
转到admin_edit_user.php
文件,关键内容如下:
1if(isset($_POST['updateusersubmit'])) {
2 // get all input data
3 $user_id = $_POST['user_id'];
4 $user_uname = $_POST['user_uname'];
5 ...
6 if(empty($user_uname) || empty($user_email) || empty($user_pass1) || empty($user_pass2)) {
7 $div_class = 'danger';
8 $div_msg = 'Please fill in all required fields.';
9 } elseif($user_pass1 !== $user_pass2) {
10 $div_class = 'danger';
11 $div_msg = 'Password fields do not match.';
12 } elseif(!$user_email_val) {
13 $div_class = 'danger';
14 $div_msg = 'Please enter a valid email address.';
15 } else {
16 // encrypt password (see documentation on php.net)
17 $options =['cost'=>HASHCOST];
18 $user_pass = password_hash($user_pass1, PASSWORD_BCRYPT, $options);
19
20 move_uploaded_file($image_tmp, "../images/$user_image");
21
22 $q = "UPDATE cms_users SET user_uname = '$user_uname',
23 user_pass = '$user_pass', user_fname = '$user_fname',
24 user_lname = '$user_lname', user_email = '$user_email',
25 user_image = '$user_image', user_role = '$user_role',
26 user_status = '$user_status' WHERE user_id = $user_id";
27
28 $result = mysqli_query($con, $q);
29
30 ...
通过 POST 取得user_name
参数以后,未经任何过滤,直接带入 update 型sql 语句,因此导致了 update 型 sql 注入,不过此注入在后台,属于后台注入(虽然后台可以越权访问)
此处注入的 payload 就不给出了,有兴趣的朋友可以自己构造。其他几个 sql 注入,漏洞原理相似,也不过多分析。
根据 xray 提供的信息:
属于 struts 系列漏洞中的 s2-007 漏洞,但是这套程序是由 PHP 写的,因此属于误报。
复现完之后我同样用 awvs 扫描了一遍该站点,部分漏洞列表如下:
我统计了一下,AWVS 一共测试出7 个 SQL 注入漏洞,存在于6 个文件,xray 一共测试出11个 SQL 漏洞,存在于4 个文件中。
其中 xray 没有测试出来而 AWVS 测试出的文件为:index.php
、aposts.php
以及sposts.php
AWVS 没有测试出来而 xray 测试出的文件为:users.php
AWVS 和 xray 共同测试出的文件为:cposts.php
、post.php
、posts.php
对于index.php
、aposts.php
以及sposts.php
文件,我看了一下报问题的 p 参数,每个文件的核心代码如下:
1if(isset($_GET['p'])) {
2 $page = mysqli_real_escape_string($con, $_GET['p']);
3
4 // the 1st number in LIMIT is a multiple of POSTSPERPAGE starting at 0
5 $first_limit = ($page - 1) * POSTSPERPAGE; // POSTSPERPAGE = 10
6 } else {
7 // $first_limit is needed for LIMIT clause, $page is needed for setting
8 // active class of pagination buttons
9 $first_limit = 0;
10 $page = 1;
11 }
12
13 // create LIMIT clause
14 $limit_clause = "LIMIT $first_limit, " . POSTSPERPAGE;
$first_limit = ($page - 1) * POSTSPERPAGE;
这句话将first_limit
强制变成了数值型,实际上我们是没办法控制注入语句的,AWVS基于以下信息:
URL encoded GET input p was set to \
Error message found:
You have an error in your SQL syntax
发现 sql syntax直接判定为注入,但实际上是因为传入的内容为-10导致出现了这个问题。
对于 p 参数,如果传入的字符中第一个不是数字,那么返回的结果,first_limit 都是 -10,传入-10 拼接到 SQL 语句后报错,如下图:
以上三个文件均为 awvs 的误报(xray 牛逼)。
对于 XSS 漏洞,AWVS 一共测试出8 个漏洞,存在于5 个文件中,xray 一共测试出46 个漏洞,存在于7 个文件中
其中 xray 没有测试出来而 AWVS 测试出的文件为:无
AWVS 没有测试出来而 xray 测试出的文件为:posts.php
、users.php
AWVS 和 xray 共同测试出的文件为:aposts.php
、cposts.php
、post.php
、sposts.php
、registration.php
对于本 CMS 来说,相比之下,xray 更有优势,而且由于被动性的优势,xray 能够完成更深层次的测试,比如那些扫描器无法扫描到地址, 现在xray迭代更新比较快,高级版的插件也不断在开发中,另一方面,其实 xray 的能力很受爬虫性能(访问页面)的影响,爬取(访问)的页面越多,xray 挖掘出漏洞的可能越大,所以 xray 值得尝试。
总的来说,awvs 更适合那些去写渗透测报告的朋友,而 xray 更适合那些去挖 src 的朋友,当你在漏洞挖掘的过程中开着 xray 的代理,或许能够带给你意想不到的结果。