SuiteCRM is a popular open-source Client Relations Manager (CRM). I took some time to review the code and basic implementation of the application within a vanilla Ubuntu Debian build. I found good security practices within the application itself, however, it seemed that the majority of configuration guides available would ultimately lead to security concerns in the long run. This post focuses on securing SuiteCRM on Apache using a defense in-depth model.

With that being said, I would like to go over some of the issues that I identified and how I went through fixing them.

If you install the SuiteCRM application on a default site configuration of Apache, it will lead to sensitive data leakage, insecure transports in use, and a lack of Defense in Depth (DiD). Also, you can use this walk-through to secure other websites that are deployed with Apache. One note, however, is that I did not focus on code issues within the application itself, rather how it was deployed.

Installation of Apache and SuiteCRM

There are multiple installation guides for SuiteCRM, however, they tend to ignore DiD issues. The basic steps to get a functional SuiteCRM application up and running are:

1. Download and install a Ubuntu or Debian server.

2. Download SuiteCRM (I downloaded the SuiteCRM 7.11.15 for my testing)

3. Install apache2 and PHP7.4

sudo apt install apache2 php7.4 libapache2-mod-php7.4 php7.4-common php7.4-mysql php7.4-gmp php7.4-curl php7.4-intl php7.4-mbstring php7.4-xmlrpc php7.4-gd php7.4-bcmath php7.4-imap php7.4-xml php7.4-cli php7.4-zip

4. Create a web root directory for SuiteCRM and copy the unzipped files there.

sudo mkdir /var/www/html/SuiteCRM
cd SuiteCRM-7.11.15 && sudo cp * /var/www/html/SuiteCRM/

5. Set the permissions for the SuiteCRM directory.

sudo chown -R www-data:www-data .
sudo chmod 755 .
sudo chmod -R 775 cache custom modules themes data upload
sudo chmod 775 config_override.php 2>/dev/null

6. Install and configure MariaDB.

sudo apt install mariadb-server mariadb-client
sudo systemctl stop mariadb.service
sudo systemctl start mariadb.service
sudo systemctl enable mariadb.service

sudo mysql_secure_installation

In the secure installation, just follow default recommendations.

7. Configure MariaDB to support SuiteCRM.

sudo mysql -u root -p
CREATE DATABASE suitecrm_db;
CREATE USER 'suitecrm_db_user'@'localhost' IDENTIFIED BY 'superS3cretP@$$w0rd';
GRANT ALL ON suitecrm_db.* TO 'suitecrm_db_user'@'localhost' IDENTIFIED BY 'superS3cretP@$$w0rd' WITH GRANT OPTION;

8. Walk through the SuiteCRM Setup Wizard.

Accept user agreement:

Checks System Environment for requirements:


Basic site configuration:

And we are up and running. However, let’s inspect the security posture of this default configuration.



Issue 1: Insecure Direct Object Reference (IDOR)

I uploaded a few test documents within the default application. SuiteCRM has a default naming standard for these type of uploads within the upload folder which is protected by a root .htaccess file:

  • Reports: IMPORT_AOR_Report_1
  • Notes: IMPORT_Note_1
  • Invoices: IMPORT_AOS_Invoices_1
  • Tasks: IMPORT_Task_1
  • Account: IMPORT_Account_1
  • Quotes: IMPORT_AOS_Quotes_1

There are more upload options, however this is a good sampling. Let’s see what happens if we browse directly to the sensitive documents in an unauthenticated browser.

As you can see, this is indeed an IDOR vulnerability. Let’s look at the application controls to prevent this.

According to the .htaccess file, the upload directory should be redirected to the 403 page. However, it did not work in the default configuration.


Fixing this is simple and should be done within the Apache site.conf file.

The first issue is that we are not calling the “AllowOverride” directive and by default, it is set to none. This means our server will ignore all .htaccess files.

We also want to add access control to our application. Adding the “Require all granted” setting will allow access to the expected entry of the application from all sources. We will also deny any source to the root directory on our server by setting the “Require all denied” in the “/” directory.

Let us add the below to the site’s configuration file and restart the Apache service:

<Directory "/">
    Require all denied

<Directory "/var/www/html/testcrm">
    AllowOverride All
    Require all granted
    DirectoryIndex index.php

Great! That is what we want to see.


Issue 2: Directory Listing

We are still able to directory-list parts of the application. This allows an attacker to discover more about the application implementation than they should be able to.


Let’s fix this with the “Options” directive control. “Options” enables features with a “+” and disables them with a “-”. In this instance, we need to disable Indexes and MultiViews, however, keep access to SymLinks. This will work with the following added to our site config:

Options -Indexes +FollowSymLinks -MultiViews

Let us see if it worked.


Issue 3: Insecure Transport

This application is not encrypted in transport. This means that if there was a man-in-the-middle situation, then the data in-transit could be compromised.


We will do a few things here. First, let’s add TLS support and configure it to only use high-rated cipher suites so that we are not vulnerable to birthday attacks like sweet32.

Let’s first enable the SSL module within Apache:

sudo a2enmod ssl

Now, let’s configure the site configuration file to redirect and use SSL for the main site.

As you can see, we added another virtual host with SSLEngine to ‘on’, mapped where the certificates are, and set cipher suite to high. In the non-tls virtual host, we just set a redirect to the TLS enabled host.


It has redirected to a secure channel.

Issue 4: Security Headers not in use.

Applying security headers to our application is a DiD exercise. Should there ever be a new vulnerability discovered within our application, this could help mitigate some of the risks of exploitation of that vulnerability. Currently, the application shows the following headers:

Not only are there no security headers, but the application is also disclosing what version of Apache we are running, and the underlying system type. Let’s see if we can clean that up too.

First, let’s enable the headers module within Apache:

<Directory "/var/www/html/SuiteCRM">
    Header set Strict-Transport-Security: 'max-age=31536000 ; includeSubDomains'
    Header set X-Frame-Options: 'sameorigin'
    Header set X-Content-Type-Options: 'nosniff'
    Header set Content-Security-Policy: "script-src 'self'"
    Header set X-Permitted-Cross-Domain-Policies: none
    Header set Referrer-Policy: same-origin
    Header set X-XSS-Protection: 0

Let’s now see if we can limit the information disclosed in the server header.

In the apache2.conf file let’s add the following lines:

ServerSignature Off
ServerTokens Prod

Lastly, we’ll add protections within the .htaccess file inside the application’s root:

# Disable server signature #
ServerSignature Off

Let’s take a look after we made those changes.


You may be wondering why the server header still says Apache. It’s because the Apache ServerToken (without leveraging third party security modules) will at minimum identify that it is running Apache.

Let’s discuss why we chose these headers.

HTTP Strict Transport Security (HSTS)

This header protects the application from downgrade attacks. This is where an attacker tries to communicate content over insecure methods. We did not have to add the “includeSubDomains” parameter in this example since we do not have any at the moment, however, it is good practice and will help if we ever expand the capabilities of the application and add a sub-domain.

Strict-Transport-Security: max-age=31536000 ; includeSubDomains


This header helps to protect against clickjacking. Clickjacking is when an attacker frames our application with an application that they control. This could enable them to steal credentials, host a drive-by download, or become a man-in-the-browser. We selected the parameter of “sameorigin” this only allows the application to frame itself.

X-Frame-Options: sameorigin


This one simply forces MIME types and will not load scripts unless it is one of the expected MIME types. An attacker could abuse MIME types to run content differently by adjusting the “Content-Type” directive.

X-Content-Type-Options: nosniff

Content-Security-Policy (CSP)

This header helps to protect against injection attacks by setting content sources. There has to be care taken when implementing this header. I chose to only allow content from ‘self’. This means that if your application attempts to load content from other domains or subdomains, this will break it. However, you can allow the specific domains and subdomains that you trust.

CSP helps to simplify protection from injection type attacks by explicitly identifying allowed sources. The capabilities of CSP are extensive and include a violation of policy reporting.

Strictly applications content:

Content-Security-Policy: default-src 'self'

Applications content and subdomains:

Content-Security-Policy: default-src 'self' *.suitecrm-demo.pwn

Multiple content sources:

Content-Security-Policy: default-src 'self'; img-src *; media-src; script-src

Cross-Domain Policy

This header simply identifies the allowed locations of cross-domain policy files. We are not required to add this header, however I’ve added it to show that you can control it from a response header. In SuiteCRM the crossdomain.xml within the webroot controls this policy.

This policy is currently set to “none”:

X-Permitted-Cross-Domain-Policies: none


Setting this header will control when the application will leverage this field. In this case, I only want it to provide information in this header when referred by the source application:

Referrer-Policy: same-origin


This header has been deprecated and could introduce additional vulnerabilities within our application. Because of this, we will explicitly disable it:

X-XSS-Protection: 0


Here is a sample site configuration file for your use when deploying applications within Apache2

Type application/x-httpd-php .php
<VirtualHost *:80>
ServerAdmin infiltration.jedi@suitecrm-demo

        Redirect permanent / https://###PUT YOUR SITE NAME HERE###/

<VirtualHost *:443>
ServerAdmin infiltration.jedi@suitecrm-demo
DocumentRoot ###PUT WHERE your application is located here###

        ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLEngine on

        SSLCipherSuite HIGH:!aNULL:!MD5

        <Directory "/">
Require all denied

        <Directory "###PUT YOUR APPLICATION DIR HERE###">

            Header set Strict-Transport-Security: 'max-age=31536000 ; includeSubDomains'
Header set X-Frame-Options: 'sameorigin'
Header set X-Content-Type-Options: 'nosniff'
Header set Content-Security-Policy: "script-src 'self'"
Header set X-Permitted-Cross-Domain-Policies: none
Header set Referrer-Policy: same-origin
Header set X-XSS-Protection: 0

            Options -Indexes +FollowSymLinks -MultiViews
AllowOverride All
Require all granted
DirectoryIndex index.php