Check the closed vulnerability and get four new CVEs.

    I was asked to continue the cycle of articles about closing vulnerabilities and how they are closed (our first article can be read here ). Last time, we found out that even if a manufacturer reports about closing a vulnerability, then in reality it may not be so.

    Selection criteria:

    The selection criteria for the considered vulnerabilities were the same this time (with the exception that this time I wanted to look at other types of vulnerabilities):

    • there must be an exploit - we want to see that before the upgrade everything was well maintained poorly, and then it became good;
    • Vulnerability should be critical (ideally RCE) and with a high score;
    • product must be open source;
    • the product must not be abandoned and actively used;
    • vulnerability must be relatively new;
    • as always, the main thing is that we ourselves should be interested.

    What and how I chose:

    I went to and asked to show all exploits with over the past couple of weeks. This time, almost all exploits in the “web” category were authored by Ihsan Sencan, but due to the fact that they most often refer to sql injections in old unsupported applications and plugins, I removed them. Of the remaining products, only ProjeQtOr Project Management Tool 7.2.5 with the CVE-2018-18924 vulnerability fell into the category “not abandoned and actively developing”.
    This vulnerability met all the selection criteria:

    • there is an exploit ;
    • RCE vulnerability (although it requires the user to be authorized);
    • the product is quite an open source;
    • the product was not abandoned, there were 28 releases for 2018 and there were 702 downloads from only (with the majority of downloads updating the CVE problem, which most likely indicates that people saw the CVE and began to update);
    • CVE dated November 4th, an exploit dated October 25th, it meets the requirements of novelty;
    • I looked at the problem and its solution, it became interesting to me (this will be further).

    Understanding the ProjeQtOr Project Management Tool

    We read the description of the exploit and the description of CVE on , we understand that version 7.2.5 is only vulnerable for an authorized user. And also that as an image you can upload a file of .shtml format, to which, though the error “This file is not a valid image” will be displayed , the file will still be saved to the image on the server and available via a direct link to host / files / images / image_name .

    Accessibility through a direct link is good, but here you have to guess the name with which the file will be loaded. We are lucky, and it is not random, but is generated from the current time in the format: year, month, day, hour, minute, second. The result is the number 20181114140320. Next, the user ID goes through the underscore, and then the original file name. There are a lot of unknowns left:

    • time zone on the server;
    • whether the clock is not knocked on the server;
    • User ID.

    And once again we are lucky: if we load a valid image, then all these parameters will be reported to us. It is not difficult to go further through several options of links (several, since there are seconds, and it's difficult to get into them at once).

    In general, getting the file name is not a problem. Moving on. And we think, why not just download a php-script? We try to load it, the same window pops up, but the file does not appear in the directory. It's time to look at the code!

    For uploading an image to the server, the uploadImage.php script in version 7.2.5 is responsible for lines from 100 to 117.

    if (substr($ext,0,3)=='php'or substr($ext,0,4)=='phtm') {
        if(@!getimagesize($uploadedFile['tmp_name'])) {
        } else {
          traceHack("Try to upload php file as image in CKEditor");
      } else {
        if ( ! move_uploaded_file($uploadedFile['tmp_name'], $uploadfile)) {
          $error = htmlGetErrorMessage(i18n('errorUploadFile','hacking ?'));
          errorLog(i18n('errorUploadFile','hacking ?'));
    if (!$error) {
      if(@!getimagesize($uploadfile)) {

    Line 100 is responsible for checking the file extension: if it is php or phtm, then the file is discarded and not saved. Therefore, php files do not appear in the “files / images /” directory. Line 115 creates an error that we see, but does nothing with the file.

    Well, let's not move away from the exploit and fill the file with the .shtml extension. There is to make a small digression and tell you what .shtml is and what it is eaten with.

    SHTML and SSI

    Wikipedia definition:

    SSI (Server Side Includes - server-side inclusions) is a simple language for dynamically assembling web pages on the server from individual components and issuing the received HTML document to the client. Implemented in an Apache web server using the mod_include module. The option included in the default settings of the web server allows you to include HTML files, so the file must end with the .shtml, .stm or .shtm extension to use the instructions.

    In your own words:

    SHTML is HTML that can execute command sets on the server side. From the useful there is an exec function that executes arbitrary commands on the server (yes, we can download the file in HTML code and run it).

    Here is a sample code to run arbitrary code:

    <!--#exec cmd=”ls” -->

    The good news is that this functionality is not enabled by default on the Apache2 server, and in order to enable it you need to dance with a tambourine. For a couple of hours picking the config, I was able to make the return of environment variables work, but not the commands. Here is my SSI code:

      	  Today is <!--#echo var="DATE_LOCAL" --> 
    	<!--#exec cmd="ls" -->
    Его вывод:
    Today is Wednesday, 14-Nov-201817:29:14 MSK [an error occurred while processing this directive]

    If someone tells you what to write in the config so that it works correctly, I would gladly read it.

    We exploit the vulnerability

    If the stars come together, you can download the shtml file and execute arbitrary commands (or like me, see the time on the server).

    Watch the patch

    The next version is 7.2.6, but there are no changes in it regarding the vulnerability of interest to us ( deceived again).

    We look at version 7.2.7 and there it seems that everything is fixed (the developers themselves say that everything is fixed just in this version). There are two key changes:

    1. Among the forbidden extensions, “shtm” was added (if the first 4 characters of these are, then shtml also falls here):

    if (substr($ext,0,3)=='php'or substr($ext,0,4)=='phtm'or substr($ext,0,4)=='shtm') {

    2. Files that are not images are now deleted:

    if(@!getimagesize($uploadfile)) {

    It would seem that you can disperse, because non-pictures are deleted, and shtml does not even try to save. But I always didn’t like it if they tried to solve some kind of problem with blacklists. For example, in some countries, companies prohibit social networks. This leads users to start using the “mirrors” of social networks, where logins and passwords are stolen from them. Passwords they coincide with corporate passwords, well, and then there may be far greater problems than the employee, looking through instagramm over a cup of coffee.

    In web programming and its security, blacklists are also evil.

    We go around the black list from ProjeQtOr

    Well, everything is simple. First of all, we look at what files with the default settings Apache2 + PHP can interpret (everything was installed on ubuntu 16.04 with an updated repository). The ability to interpret files is the responsibility of the “FilesMatch” directive. Do a search for it with the command “grep -r" <FilesMatch "/ etc / apache2” and here's the result:

    /etc/apache2/mods-available/php7.0.conf:<FilesMatch ".+\.ph(p[3457]?|t|tml)$">
    /etc/apache2/mods-available/php7.0.conf:<FilesMatch ".+\.phps$">
    /etc/apache2/mods-available/php7.0.conf:<FilesMatch "^\.ph(p[3457]?|t|tml|ps)$">
    /etc/apache2/sites-available/default-ssl.conf:		<FilesMatch "\.(cgi|shtml|phtml|php)$">
    /etc/apache2/apache2.conf:<FilesMatch "^\.ht">

    In the default-ssl.conf config file, all extensions are simply listed in their entirety: cgi, shtml, phtml, php. Alas, but everything except cgi is filtered in ProjeQtOr.

    Much more interesting is the php7.0.conf config, in which extensions are specified by a regular expression. We get:
    ExpansionWhat is filtered
    php substr ($ ext, 0,3) == 'php'
    php3 substr ($ ext, 0,3) == 'php'
    php4 substr ($ ext, 0,3) == 'php'
    php5 substr ($ ext, 0,3) == 'php'
    php7 substr ($ ext, 0,3) == 'php'
    pht ANYTHING
    phtml3 substr ($ ext, 0,4) == 'phtm'

    Great, a file extension that is not filtered is found. We check that it is really interpreted.

    Create a test.pht file with the following contents:


    Go to this file in the browser and see the information about the installed php. Remarkably, the black list was bypassed, while at the default settings for some reason other extensions for interpretation could be allowed.

    We ship our test file to the ProjeQtOr Project Management Tool. Of course, we get an error, because this is not a picture (in the version before 7.2.7 we already have code execution on the server, since changing phpinfo to command execution is easy). In version 7.2.7, the file is deleted and the code is not executed.

    But we are not upset and we go around checking the picture.

    Picture with PHP

    Checking whether the downloaded file is a picture in the ProjeQtOr Project Management Tool is performed by the getimagesize function, which simply looks at the title of the transferred file.

    Using the fact that there can be any garbage in the php file, and the interpretation of the php code begins only with the characters “<? Php”, we write a small code that first writes a picture, and then the php code we need. As the picture we will send a screenshot of the error window. 3 lines of python code and you're done:

    data = open ('test.png','rb').read()
    data += open ('test.pht','rb').read()
    open ('new_pht_png.pht','wb').write(data)

    Probably, it was possible to simply write a valid image header to the beginning of the file, but this is simpler, and the more so the image is displayed in any viewers.

    We upload this creation to the server and, oh, a miracle, it loads (and is displayed in the viewer as a picture), and it’s also nice that we show the full name with which this file was uploaded.

    Go to the downloaded file localhost / files / images / 20181114171730_1_new_pht_png.pht and see the text of the downloaded image, and under it the output of phpinfo. It is clear that replacing phpinfo with a simple web shell is easy. For example: <? Php system ($ _ GET ['cmd']);

    Once they started picking up file downloads, you need to finish the job and see where else there is a download of files with or without blacklists.

    Still downloading files

    Watch will be in the latest available version. Assuming that the same function is used to download files as before, i.e. move_uploaded_file, we search for it in the directory with the projects “grep -r" move_uploaded_file "./”. We

    get the following 5 files: ./tool/uploadImage.php

    The uploadImage.php file has already been viewed.
    File saveDocumentVersion.php - loads versions of documents (as the name implies). We try to load the document and look at it (for the beginning we will always load the picture). After loading, we see that the extension .1 has been added to the file. We look in the code how the name is obtained (this is done in line 229):

    $uploadfile = $dv->getUploadFileName();

    The getUploadFileName function is declared in the DocumentVersionMain.php file. There in line 227 we see that the “.” And the document ID are added to the returned name. Even the added point we can not quickly get around:

    return $uploaddir . $paramPathSeparator . $fileName . '.' . $this->id;

    The uploadPlugin.php file is available only to administrators and the fact that the plug-in can have bad code is quite logical and difficult to get rid of without entering validation of plug-ins (as popular CMS do). Of course, when you try to load something there, it loads successfully and then executes.

    The import.php file is also available only to administrators. When downloading a file, we are told that it should be a csv file or xlsx file. Of course, try downloading the php file and see the error:

    ERROR - do not match match

    Import aborted The bad

    luck is that, as in the initial bug from CVE, the file is not deleted, but remains available at localhost / files / attach / import / test.php .

    The saveAttachment file is used when loading any attachments (for example, when loading your own image). The PHP script doesn’t get through there, as there is a protection of the form:

    if (substr($ext,0,3)=='php'or substr($ext,0,4)=='phtm'or substr($ext,0,4)=='shtm') {

    it turns out the extension file ".projeqtor" is added to the php *, phtm *, shtm * extension files, that is, our pht file will obviously go through there (even without crawling images). We try, and at us everything turns out to the address localhost / files / attach / attachment_1 / test.pht .

    Total in the bottom line from the five quickly found file download locations:

    • managed to download php or pht script 4 times;
    • black list validation is in two;
    • white list validation is not available anywhere;
    • it was not possible to load the file once (it was possible to load, but not to execute), because the extension was changing

    Conclusions on the ProjeQtOr Project Management Tool and CVE-2018-18924

    • Reported vulnerability is virtually eliminated;
    • there are other vulnerabilities in the code (reported to the developers, and they even promised white lists of extensions);
    • from all you could save the competent setting of the Apache2 server (limit executable formats only necessary, prohibit the execution of scripts in user folders);
    • on it contains not the least vulnerable version.

    Mistress note

    • refuse blacklists wherever possible (I don’t know where it is impossible to do this);
    • be careful and attentive when processing downloaded files (it’s better to be in one place and not smeared over 5);
    • well-configured web server saves from many problems in the project code (it is important to write code and configure the server well).

    Developed response from developers

    The first answer from the developers was, something like “we fixed everything, look at the corrected code”. It was necessary to paint in great detail where there are some problems and how they can be exploited.

    Then I received a detailed answer: “yes, there are problems, and they will be fixed in version 7.3.0. Whitelists for images for both xlslx and csv will also be added. ”. They also wrote that they had a recommendation for adding the “attachments” and “documents” directory beyond the web access limits in the installation instructions.

    The developers allowed me to register CVE and write an article after the update (which was released and available for download ).


    As I wrote at the beginning, the update, decisive for CVE, has been downloaded by quite a few people (over 500 downloads), and it’s cool that people are updating their vulnerable software, but it’s sad that the software remains vulnerable.

    As a result, four CVEs were assigned to me and our company: CVE-2018-19307, CVE-2018-19308, CVE-2018-19309, CVE-2018-19310.

    Also popular now: