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:

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:
./xray webscan --listen 0.0.0.0:1111 --html-output awvs.html
You can choose the port yourself; I used 8888:

Then add the address of the deployed CMS:

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

It might uncover even more
The remaining configuration follows XRAY's official guide. The proxy-template port must match XRAY's listening port:

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

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

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:

The u parameter is vulnerable. Locateaposts.php. The key code is:
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:

2. registration.php
According to the information provided by XRAY:

The username parameter is vulnerable. Locateregistration.phpfile. The key code is:
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>

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:

The cid parameter is vulnerable. Locatecposts.phpfile. The key code is:
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
$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:

The vulnerability is real. The following payload can retrieve account credentials:
union all select concat(0x7e,user_uname,user_pass,user()) from cms_users limit 1,1

4. cposts.php
According to the information provided by XRAY:

The vulnerable parameter is user_name. Locatecposts.phpfile:
<?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:
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:

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:

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.php、aposts.phpandsposts.php
Files found by XRAY but missed by AWVS:users.php
Files found by both AWVS and XRAY:cposts.php、post.php、posts.php
Forindex.php、aposts.phpandsposts.phpfiles. I examined the reported p parameter; the core code in each file is shown below:
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:
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:

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.php、users.php
Files found by both AWVS and XRAY:aposts.php、cposts.php、post.php、sposts.php、registration.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.php、users.php
Files found by both AWVS and XRAY:aposts.php、cposts.php、post.php、sposts.php、registration.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.