First Impressions of the XRAY Vulnerability Scanner

Summary0x01 Preface A few days ago I obtained XRAY Advanced and tried combining XRAY with AWVS to see whether I could rediscover vulnerabilities in a CMS listed by CNVD. CNVD advisories provide few details, so the vulnerable code must be located from the vulnerability type. If no filename is disclosed, this amounts to auditing the whole system from scratch…

xrayVulnerability Discoverysrc

0x01 Preface

A few days ago I obtained XRAY Advanced and tried combining XRAY with AWVS to see whether I could rediscover vulnerabilities in a CMS listed by CNVD.

CNVD advisories do not disclose full details, so the vulnerable location must be found from the vulnerability type. A disclosed filename makes this easier; without one, the entire system must be audited from scratch. I therefore chose a small CMS for the demonstration:

1.png

This article uses vulnerability discovery in a CMS as a basis for sharing my experience with the tools.

0x02 Preparation

I will not repeat the XRAY + AWVS configuration here; the official documentation explains it clearly. See:

https://chaitin.github.io/xray/#/scenario/awvs

Two points require attention:

  • Ensure that the XRAY proxy address and the AWVS host can communicate
  • Ensure that the allowed scan domain in XRAY's configuration is*or the address you want to scan (I previously forgot to change the last test address and kept getting no data, so this is worth emphasizing)

You can then download and deploy the CMS. The setup is straightforward, so it is omitted here. The download is available at:

https://github.com/chilin89117/ED01-CMS

Once installation is complete, add a scan.

First, start XRAY locally with the following command:

TEXT
./xray webscan --listen 0.0.0.0:1111 --html-output awvs.html

You can choose the port yourself; I used 8888:

2.png

Then add the address of the deployed CMS:

3.png

Because this is a test environment, we can enable the login option:

4.png

It might uncover even more

The remaining configuration follows XRAY's official guide. The proxy-template port must match XRAY's listening port:

5.png

Select crawler-mode scanning. Once the task is created, scanning of the target can begin:

6.png

As shown below, XRAY quickly receives the URL and begins testing:

7.png

The final scan results are as follows:

# 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

The results above are simplified. A file may contain two or more vulnerable ParamKeys, but only one is retained here.

Because a single file can contain different vulnerabilities, the analysis is organized by file.

0x03 Vulnerability Discovery

1. aposts.php

According to the information provided by XRAY:

8.png

The u parameter is vulnerable. Locateaposts.php. The key code is:

PHP
if(isset($_GET['u'])) {
		$uname = mysqli_real_escape_string($con, $_GET['u']);

     ....

    if(!$posts) {
			$div_class = 'danger';
			$div_msg = 'Database error: ' . mysqli_error($con);
		} else {
			$post_count = mysqli_num_rows($posts);
			if($post_count == 0) {
				$page_count = 0;
				$div_class = 'danger';
				$div_msg = "Sorry, no posts found for user <strong>'$uname'</strong>.";
			} else {
				$page_count = ceil($post_count / 8);
				$div_class = 'success';
				$div_msg = " Showing published posts for user <strong>'$uname'</strong>.";
				$div_msg .= " <a href='index.php'>Show All</a>";
			}
		}
	}

After retrieving parameter u via GET, it passes throughmysqli_real_escape_stringassigns the function result to uname, then checks the posts parameter; if no result is found, it outputs uname

We know thatmysqli_real_escape_stringprimarily escapes special characters in SQL strings; it is not an XSS filter. Therefore, even the most basic payload works:<script>alert(0)</script>, and the XSS vulnerability can be triggered:

9.png

2. registration.php

According to the information provided by XRAY:

10.png

The username parameter is vulnerable. Locateregistration.phpfile. The key code is:

PHP
if(isset($_POST['submit'])) {
	// clean up inputs
	$username 		= mysqli_real_escape_string($con, $_POST['username']);

  ...


	<div class="form-group">
				<label for="username" class="sr-only">Choose a Username</label>
				<input type="text" name="username" class="form-control"
          value="<?php echo $username;?>" placeholder="Enter Desired Username *">
	</div>

The issue is similar to the previous file and uses onlymysqli_real_escape_stringis applied before the value is emitted inside an input element, so closing the input element is enough to trigger XSS:"><script>alert(0)</script>

11.png

The XSS vulnerabilities in the other files work in much the same way, so I will not repeat the analysis.

3. cposts.php

According to the information provided by XRAY:

12.png

The cid parameter is vulnerable. Locatecposts.phpfile. The key code is:

PHP
if(isset($_GET['cid'])) {
		$cid = mysqli_real_escape_string($con, $_GET['cid']);

		// find total number of posts to determine number of pages for pagination
		$q = "SELECT * FROM cms_posts where post_cat_id = $cid";
		$result = mysqli_query($con, $q);
		$total_posts = mysqli_num_rows($result);
		$total_pages = ceil($total_posts / POSTSPERPAGE);

		// if $total_pages is 0, set it to 1 so pagination will not look for page 0
		if($total_pages < 1) {
			$total_pages = 1;
		}

  ...

    $q1 = "SELECT cms_posts.*, cms_users.user_image FROM cms_posts
					INNER JOIN cms_users ON cms_posts.post_author = cms_users.user_uname
					WHERE post_cat_id = '$cid'
					AND post_status = 'Published'
					ORDER BY post_date DESC " . $limit_clause;

		// get category name from database to display in alert box
		$q2 = "SELECT cat_title FROM cms_categories WHERE cat_id = $cid";

		$result = mysqli_query($con, $q2);
		$cat_title = mysqli_fetch_array($result);

  ...

Again, only key characters in the injection string are escaped—but here is the crucial point

TEXT
		$q = "SELECT * FROM cms_posts where post_cat_id = $cid";

There is no need to close the single quote; simply pass in$cid variable

Testing with the payload supplied by XRAY gives:

13.png

The vulnerability is real. The following payload can retrieve account credentials:

TEXT
 union all select concat(0x7e,user_uname,user_pass,user()) from cms_users limit 1,1
14.png

4. cposts.php

According to the information provided by XRAY:

15.png

The vulnerable parameter is user_name. Locatecposts.phpfile:

PHP
<?php
if(isset($_GET['source'])) {
	$source = $_GET['source'];
} else {
	$source = "";
}

switch($source) {
	case 'add_user':
		include 'admin_includes/admin_add_user.php';
		break;
	case 'edit_user':
		include 'admin_includes/admin_edit_user.php';
		break;
	case 'c':
		echo 'c';
		break;
	default:
		include 'admin_includes/admin_view_all_users.php';
}

?>

becomesadmin_edit_user.phpfile; the relevant content is:

PHP
if(isset($_POST['updateusersubmit'])) {
		// get all input data
		$user_id = $_POST['user_id'];
		$user_uname = $_POST['user_uname'];

  ...


		if(empty($user_uname) || empty($user_email) || empty($user_pass1) || empty($user_pass2)) {
			$div_class = 'danger';
			$div_msg = 'Please fill in all required fields.';
		} elseif($user_pass1 !== $user_pass2) {
			$div_class = 'danger';
			$div_msg = 'Password fields do not match.';
		} elseif(!$user_email_val) {
			$div_class = 'danger';
			$div_msg = 'Please enter a valid email address.';
		} else {
			// encrypt password (see documentation on php.net)
			$options =['cost'=>HASHCOST];
			$user_pass = password_hash($user_pass1, PASSWORD_BCRYPT, $options);

			move_uploaded_file($image_tmp, "../images/$user_image");

			$q = "UPDATE cms_users SET user_uname = '$user_uname',
						user_pass = '$user_pass', user_fname = '$user_fname',
						user_lname = '$user_lname', user_email = '$user_email',
						user_image = '$user_image', user_role = '$user_role',
						user_status = '$user_status' WHERE user_id = $user_id";

			$result = mysqli_query($con, $q);

      ...

Retrieve via POSTuser_nameparameter is inserted directly into an UPDATE statement without filtering, resulting in UPDATE-based SQL injection. It is an authenticated injection in the admin area, although that area can be accessed through an authorization bypass.

I will not provide the injection payload here; interested readers can construct it themselves. The other SQL injections have similar root causes and are not analyzed further.

5. users.php

According to the information provided by XRAY:

image-20191222203032984.png

This was identified as S2-007 in the Struts family, but the application is written in PHP, so it is a false positive.

0x04 Comparative Analysis

After reproducing the issues, I also scanned the site with AWVS. Part of the vulnerability list is shown below:

16.png

By my count, AWVS found seven SQL injection vulnerabilities across six files, while XRAY found eleven across four files.

Files found by AWVS but missed by XRAY:index.phpaposts.phpandsposts.php

Files found by XRAY but missed by AWVS:users.php

Files found by both AWVS and XRAY:cposts.phppost.phpposts.php

Forindex.phpaposts.phpandsposts.phpfiles. I examined the reported p parameter; the core code in each file is shown below:

PHP
if(isset($_GET['p'])) {
			$page = mysqli_real_escape_string($con, $_GET['p']);

			// the 1st number in LIMIT is a multiple of POSTSPERPAGE starting at 0
			$first_limit = ($page - 1) * POSTSPERPAGE; // POSTSPERPAGE = 10
		} else {
			// $first_limit is needed for LIMIT clause, $page is needed for setting
			// active class of pagination buttons
			$first_limit = 0;
			$page = 1;
		}

		// create LIMIT clause
		$limit_clause = "LIMIT $first_limit, " . POSTSPERPAGE;

$first_limit = ($page - 1) * POSTSPERPAGE;This statement turnsfirst_limitforces it into a numeric value. In practice, we cannot control the injected statement. AWVS based its finding on the following information:

TEXT
URL encoded GET input p was set to \

Error message found:

You have an error in your SQL syntax

It classified the appearance of an SQL syntax error as injection, but the error was actually caused by the supplied value -10.

For parameter p, if the first character is not a digit, first_limit evaluates to -10. Appending -10 to the SQL statement produces the error shown below:

18.png

All three files above are AWVS false positives (XRAY wins this round).

For XSS, AWVS found eight vulnerabilities across five files, while XRAY found forty-six across seven files.

Files found by AWVS but missed by XRAY: none

Files found by XRAY but missed by AWVS:posts.phpusers.php

Files found by both AWVS and XRAY:aposts.phpcposts.phppost.phpsposts.phpregistration.php

For XSS, AWVS found eight vulnerabilities across five files, while XRAY found forty-six across seven files.

Files found by AWVS but missed by XRAY: none

Files found by XRAY but missed by AWVS:posts.phpusers.php

Files found by both AWVS and XRAY:aposts.phpcposts.phppost.phpsposts.phpregistration.php

0x05 Conclusion

For this CMS, XRAY has the advantage. Its passive approach enables deeper testing of paths that ordinary scanners cannot reach. XRAY is evolving quickly and Advanced-edition plugins are still being developed. Its effectiveness does, however, depend heavily on crawler coverage: the more pages visited, the greater the chance of finding vulnerabilities. XRAY is therefore worth trying.

Overall, AWVS is better suited to penetration testers writing assessment reports, while XRAY is better suited to researchers hunting vulnerabilities for SRC programs. Leaving XRAY's proxy enabled during testing may produce unexpected findings.