Campaign Classic - Security and Privacy checklist

This document will introduce you to the key elements to check regarding security and privacy.

Don't forget to check the Adobe Marketing Cloud Compliance Overview and the Adobe Campaign Security Overview.

Last update: 2019-05-30

Here is the table of contents:

    ✔  Privacy

    ✔  Access management

    ✔  Development

    ✔  Network, database and SSL/TLS

    ✔  Server configuration

    ✔  Web-server configuration


Privacy configuration and hardening is a key element of security optimization.

Click Read More and learn more on privacy bast practices:

  • Protect your customer PI by using HTTPS instead of HTTP
  • Use PII view restriction to protect privacy and prevent data from being misused.
  • Make sure that a encrypted passwords are restricted.
  • Protect the pages that might contain personal information such as mirror pages, web applications, etc.
Read More

Access management

Access management is an important part of security hardening. Here are some of the main best practices:

  • Create enough security groups
  • Check that each operator has the appropriate access rights
  • Avoid using the admin operator and avoid having too many operators in the admin group

Refer to the documentation: Access rights and Folder access properties

Read More


When developing in Adobe Campaign (workflows, Javascript, JSSP, etc.), always follow these guide lines:

  • Scripting: try to avoid SQL statements, use parameterized functions instead of string concatenation, avoid SQL injection by whitelisting the SQL functions to use
  • Securing the data model: use named rights to limit operator actions, add system filters (sysFilter)
  • Adding captchas in web applications: learn how to add captchas in your public landing pages and subscription pages.
Read More

Network, database and SSL/TLS

A very important thing to check when deploying an on-premise type of architecture is the networking configuration.

Ensure that the Tomcat server is NOT directly accessible outside the server:

  • Close the Tomcat port (8080) on external IPs (must work on localhost)
  • Do not map the standard HTTP port (80) to the Tomcat one (8080)

When it's possible, use a secure channel: POP3S instead POP3 (or POP3 over TLS).

To get details on database and SSL/TLS configuration, click the Read more button.

Read More

Server configuration

Configuration has to be performed on all servers. The configuration files are of the type serverConf.xml and config-<instance>.xml. Here are the key elements that need to be verified:

  • Security zones: Configure security zones so that they directly take into account the IP addresses of clients of a proxy.
  • File upload protection: limit the types of files that can be uploaded to the Adobe Campaign server using a new uploadWhiteList attribute. This can be used in the server configuration file.
  • Relay: fine tune the relay configuration by deactivating the relay rules for unused modules/applications.
  • Outgoing connection protection and Command restriction (server-side)
  • You can also add extra HTTP headers, activate checkIPConsistent, enableTLS, sessionTimeOutSec, etc.

Refer to the documentation for more information.

Read More

Web-server configuration

Here are some of the main best practices related to web-server configuration:

  • Change default error pages
  • Disable old SSL version and ciphers
  • Remove TRACE method

Read More

Any question? Visit our forum and ask the community.

Follow Adobe Experience Cloud on the social networks

Copyright © Adobe 2019

Privacy hardening


Privacy hardening

URL Personalization

When adding personalized links to your content, always avoid having any personalization in the DNS part of the URL. The following examples should never be used in all URL attributes <a href=""> or <img src="">):

  • <%= url >
  • https://<%= url >
  • https://<%= domain >/path
  • https://<%= sub-domain >.domain.tld/path
  • https://sub.domain<%= main domain %>/path

A dedicated function can be used to protect those links, for example:

                  function protectPhishing(givenUrl) {
  if( givenUrl.startsWith("") )
return givenUrl
     logError("Phishing attempt")

Privacy basics

The first thing to check is that you use HTTPS everywhere you can (mirror pages, web applications, etc.)

Links in these pages are sent in referrer header when asking for a linked resource (images, css, js). Because of this, to avoid leaking your customer PI, check the following points:

  • Use HTTPS in your linked page.
  • Don't add PI in your link (if you're using a web application or landing page). Use the default reconciliation mode (not email).
  • Avoid using third party tracking systems, even for other resources (images, js, etc.) hosted on a third party domain.

PII view restriction

Some customers need Campaign users to be able to access data records but do not want them to see Personally Identifiable Information (PII), such as first name, last name, email. Adobe Campaign proposes a way to protect privacy and prevent data from being misused by regular campaign operators.

Password protection is a key point of security hardening. It allows to restrict access to encrypted passwords in Campaign. In other word, hide to all non-admins all the fields that contain a password. To achieve this, PII view restriction can be used.

For more information, refer to the detailed documentation.

Data restriction

We have to make sure that the encrypted passwords will not be accessible by a low privilege authenticated user. To do that, there are two main way: restrict access to password fields only or to the entire entity (need a build >= 8770).

This restriction allow you to remove passwords fields but let the external account accessible from the interface for all users. Refer to the documentation

  1. Go in Administration > Configuration > Data schemas.
  2. Create a new Extension of a schema.

  3. Choose External Account (extAccount).
  4. In the last wizard screen, you can edit your new srcSchema to restrict access to all password fields:

You can replace the main element (<element name="extAccount" ... >) by:

<element name="extAccount">
    <attribute accessibleIf="$(loginId) = 0 or $(login) = 'admin'" name="password"/>
    <attribute accessibleIf="$(loginId) = 0 or $(login) = 'admin'" name="clientSecret"/>
    <element name="s3Account">
      <attribute accessibleIf="$(loginId) = 0 or $(login) = 'admin'" name="awsSecret"/>
    <element name="wapPush">
      <attribute accessibleIf="$(loginId) = 0 or $(login) = 'admin'" name="password"/>
      <attribute accessibleIf="$(loginId) = 0 or $(login) = 'admin'" name="clientSecret"/>
    <element name="mms">
      <attribute accessibleIf="$(loginId) = 0 or $(login) = 'admin'" name="password"/>
      <attribute accessibleIf="$(loginId) = 0 or $(login) = 'admin'" name="clientSecret"/>

So your extended srcSchema can look like

<srcSchema _cs="External Accounts (cus)" created="2017-05-12 07:53:49.691Z" createdBy-id="0"
           desc="Definition of external accounts (Email, SMS...) used by the modules"
           entitySchema="xtk:srcSchema" extendedSchema="nms:extAccount" img="" label="External Accounts"
           labelSingular="External account" lastModified="2017-05-12 08:33:49.365Z"
           mappingType="sql" md5="E9BB0CD6A4375F500027C86EA854E101" modifiedBy-id="0"
           name="extAccount" namespace="cus" xtkschema="xtk:srcSchema">
  <createdBy _cs="Administrator (admin)"/>
  <modifiedBy _cs="Administrator (admin)"/>
  <element name="extAccount">
    <attribute accessibleIf="$(loginId) = 0 or $(login) = 'admin'" name="password"/>
    <attribute accessibleIf="$(loginId) = 0 or $(login) = 'admin'" name="clientSecret"/>
    <element name="s3Account">
      <attribute accessibleIf="$(loginId) = 0 or $(login) = 'admin'" name="awsSecret"/>
    <element name="wapPush">
      <attribute accessibleIf="$(loginId) = 0 or $(login) = 'admin'" name="password"/>
      <attribute accessibleIf="$(loginId) = 0 or $(login) = 'admin'" name="clientSecret"/>
    <element name="mms">
      <attribute accessibleIf="$(loginId) = 0 or $(login) = 'admin'" name="password"/>
      <attribute accessibleIf="$(loginId) = 0 or $(login) = 'admin'" name="clientSecret"/>

Note: you can remplace $(loginId) = 0 or $(login) = 'admin' by hasNamedRight('admin') to let all users with admin right see these passwords.

Protecting pages containing PI

We strongly advise on-premise customers to protect the pages that might contain personal information such as mirror pages, web applications, etc.

The goal of this procedure is to prevent these pages from being indexed, thus avoiding a potential security risk. Here are a few useful articles:

To protect your pages, follow these steps:

  1. Add a robots.txt file at the root of your web server (Apache or IIS). Here is the content of the file:

    # Make changes for all web spiders
    *Disallow: /

    For IIS, refer to this page:

    For Apache, you can place the file in /var/www/robots.txt (Debian)

  2. Sometimes adding a robots.txt file is not sufficient in terms of security. For example, if another website contains a link to your page, it might appear in a search result.

    In addition to the robots.txt file, it is advised to add a X-Robots-Tag header. You can do it in Apache or IIS and in the serverConf.xml configuration file.

    For more information, refer to this article:

Access management


Access management

Out of the box, the webApp operator is an administrator. To improve security, follow these guide lines:

  • You have to replace the admin named right from this operator with a new one (can be named 'webapp'). For more information, refer to the documentation.
  • Add the webApp operator in folders (mainly recipient folders) to grant read/write access to recipients (refer to the documentation).
  • If using a multi-brand (or multi-geo) instance, you may want to split web application access to different recipient folders:
    • Duplicate the webApp operator, name the duplicates ('webapp_brand1' and 'webapp_brand2' for example)
    • Duplicate a web application template to have one template per brand and edit the properties to change the operator by selecting "Use a specific account" (see the documentation.)

Create enough security groups to give just enough rights to your operators to let them do what they need (and not more).

Do not use the admin operator (or don't share it). Create one operator per physical user (to have an accurate audit/logging). Add your newly named administrators to the admin group. If you don't use the admin operator, do not delete it, and don't disable it (this operator is used internally to execute processing). But you can ban its access to the client console (see the documentation) and restrict its security zone (to localhost).

Avoid adding too many operators in the admin group (or with admin named rights). They are very powerful operators (they can perform all SQL statements, execute commands on the server, etc.).

Adobe Campaign provides three high-level privileges (named rights):

  • admin: gives access to everything and allows to do everything (bypasses all named right checks), so it includes the createProcess and SQL named rights
  • createProcess: allows executing external programs (on the server)
  • sql: allows running SQL scripts on the database (so it can bypass the security model). Note: if you need to perform complex computations (filtering, for example), you can ask your database administrator to create an SQL function and whitelist them (refer to the "Development" section).
  • Grant them to very few (and trusted) operators





For more details, refer to Campaign JSAPI documentation.

If you script using workflow, web applications, jssp, follow these best practices:

  • Try to avoid using SQL statements as much as you can.
  • If you need to, use parameterized (prepare statement) functions instead of string concatenation.
    • bad practice:
      sqlGetInt( "select iRecipientId from NmsRecipient where sEmail ='" + request.getParameter('email') +  "'  limit 1" )
    • good practice:

      sqlGetInt( "select iRecipientId from NmsRecipient where sEmail = $(sz) limit 1", request.getParameter('email'));

Warning: sqlSelect doesn't support this feature, so you have to use the query function of DBEngine class:

var cnx = application.getConnection() 
var stmt = cnx.query("SELECT sFirstName, sLastName FROM NmsRecipient where sEmail = $(sz)", request.getParameter('email'))
for each(var row in stmt) logInfo(row[0] + " : " + row[1]) 


To avoid SQL injections, SQL functions must be whitelisted to be used in Adobe Campaign. Once they are whitelisted, they become visible to your operators in the expression editor. Refer to the documentation. Warning: if you are using a build that is older than 8140, the XtkPassUnknownSQLFunctionsToRDBMS option might be set to '1'. If you want to secure your database, delete this option (or set it to '0').

If you are using user input to build filters in queries or SQL statements, you always have to escape them (refer to Campaign JSAPI documentation - Data protection: escaping functions). These functions are:

  • NL.XML.escape(data)
  • NL.SQL.escape(data)
  • NL.JS.escape(data)
  • NL.XML.escapeAttribute(data)

Securing your new data model

Folder based

Refer to the documentation:

Named rights

In addition to the folder-based security model, you can use named rights to limit operator actions:

You can add some system filters (sysFilter) to prevent reading/writing to your data. See the documentation.

<sysFilter name="writeAccess">     
    <condition enabledIf="hasNamedRight('myNewRole')=false" expr="FALSE"/>   


You can also protect some actions (SOAP method) defined in schemas. Just set the access attribute with the corresponding named right as the value (see the documentation):

<method name="grantVIPAccess" access="myNewRole">


Warning: you can use named rights in the command node in a navtree. It gives a better user experience but doesn't provide any protection (use only client side to hide / disable them). You have to use the access attribute.


If you need to protect confidential data (part of a schema) depending on the operator access level, do not hide them in the form definition (enabledIf/visibleIf conditions). The full entity is loaded by the screen, you can also display them in column definition. To do this, you have to create an overflow table. Refer to the documentation.

Adding captchas in web applications

It is a good practice to add a captcha in public landing pages/subscription pages. Unfortunately, adding a captcha in DCE (Digital Content Editor) pages is not easy. We will show you how to add a v5 captcha or a Google reCAPTCHA.

The general way to add a captcha in the DCE is to create a personalization block to include it easily within the page content. You will have to add a Script activity and a Test.

Personalization block

  1. Go to Resources > Campaign Management > Personalization blocks and create a new one.
  2. Use the Web application content type and check Visible in the customization menus).

For more information, refer to the documentation.

Here is an example of a Campaign captcha:

var captchaID = CaptchaIDGen();
<img src="/nms/jsp/captcha.jsp?captchaID=<%=captchaID%>&width=200&height=50&minWordSize=8&maxWordSize=8"/>
<input id="captchaValue" name="captchaValue" <%= String(ctx.vars.captchaValid) === "false" ? class="ui-state-error" : "" %>>
<input type="hidden" name="captchaID" value="<%=captchaID%>"/>
if( serverForm.isInputErroneous("captchaValue") ) {
<script type="text/javascript">  

Lines 1 to 6 generate all needed inputs.

Lines 7 to the end handle errors.

Line 4 allows you to change captcha gray box size (width/height) and the length of generated word (minWordSize/maxWordSize).

Before using Google reCAPTCHA, you must register on Google and create a new reCAPTCHA site:

<div class="g-recaptcha" data-sitekey="YOUR_SITE_KEY"></div>

You should be able to disable the validation button, but as we don't have any standard button/link, it's better to do it in the HTML itself. To learn how to do it, see:

Updating your web application

  1. Access the the properties of your web application to add a boolean variable named captchaValid.

  2. Between the last page and the Storage activity, add a Script and a Test. Plug the branch True to the Storage and the other one to the page which will have the captcha.

  3. Edit the condition of the branch True with "[vars/captchaValid]" equals True.

  4. Then, edit the Script activity (content will depend on the chosen captcha engine).
  5. Finally, you can add your personalized block in the DCE page: refer to the documentation.

    WARNING: for reCAPTCHA integration, you have to add client-side JavaScript in the HTML (in <head>...</head>):

    <script src="" async defer></script>

Campaign Captcha

var captchaID = request.getParameter("captchaID");
var captchaValue = request.getParameter("captchaValue");
if( !CaptchaValidate(captchaID, captchaValue) ) {
                           "The characters you typed for the captcha must match the image ones.",
  ctx.vars.captchaValid = false
  ctx.vars.captchaValid = true

Line 6: you can put any kind of error message.

Google reCAPTCHA

Please read the official documentation.

ctx.vars.captchaValid = false
var gReCaptchaResponse = request.getParameter("g-recaptcha-response");
// Call reCaptcha API to validate it
var req = new HttpClientRequest("")
req.method = "POST"
req.header["Content-Type"] = "application/x-www-form-urlencoded"
req.body = "secret=YOUR_SECRET_HERE&response=" + encodeURIComponent(gReCaptchaResponse)
var response = req.response
if( response.code == 200 ) {
  captchaRes = JSON.parse(response.body.toString(response.codePage));
  ctx.vars.captchaValid = captchaRes.success
if( ctx.vars.captchaValid == false ) {
                           "Please validate the captcha",
  logInfo("reCaptcha not validated")


To use JSON.parse you have to include "shared/json2.js" in your webApp:

Since build 8797, in order to use the verification API URL, you have to whitelist it in serverConf by adding in urlPermission node:

<url dnsSuffix="" urlRegEx=""/>

Database and SSL/TLS


Database and SSL/TLS


It is imperative that you follow your database engine security.

SSL/TLS configuration

To check the certificate, you can use openssl. To check active ciphers, you can use nmap:

# usage: [port]

echo |\
openssl s_client -connect ${REMHOST}:${REMPORT} -servername ${REMHOST} 2>&1 |\
openssl x509 -noout -subject -dates
nmap --script ssl-enum-ciphers -p ${REMPORT} ${REMHOST}


You can also use an sslyze python script which does both.

python --sslv2 --sslv3 --tlsv1 --reneg --resum --certinfo=basic --hide_rejected_ciphers --sni=SNI


Adobe Campaign server configuration


Adobe Campaign server configuration

Here is some additional information on Adobe Campaign server configuration.

Configuring security zones

To learn how to use the Security Zones Self Service UI to manage entries in the VPN Security Zone configuration, refer to this technote.

Make sure that your reverse proxy in not allowed in subNetwork. If it is the case, all traffic will be detected as coming from this local IP, so will be trusted.

Minimize the use of sessionTokenOnly="true":

  • Warning: If this attribute is set to true, the operator can be exposed to a CRSF attack.
  • In addition, the sessionToken cookie is not set with an httpOnly flag, so some client-side javascript code can read it.
  • However Message Center on multiple execution cells needs sessionTokenOnly: create a new security zone with sessionTokenOnly set to "true" and add only the needed IP(s) in this zone.

When possible, set all allowHTTP, showErrors to be false (not for localhost) and check them!

  • allowHTTP = "false": forces operators to use HTTPS
  • showErrors = "false": hides technical errors (including SQL ones). It prevents displaying too much information, but reduces the capability for the marketer to solve mistakes (without asking for more information from an administrator)

Set allowDebug to true only on IPs used by marketing users/administrators who need to create (in fact preview) surveys, webApps and reports. This flag allows these IPs to get relay rules displayed and to debug them.

Never set allowEmptyPassword, allowUserPassword, allowSQLInjection to true. These attributes are only here to allow a smooth migration from v5 and v6.0:

  • allowEmptyPassword lets operators have an empty password. If this is the case for you, notify all your operators to ask them to set a password with a deadline. Once this deadline has passed, change this attribute to false.
  • allowUserPassword lets operators send their credentials as parameters (so they will be logged by apache/IIS/proxy). This feature was used in the past to simplify API usage. You can check in your cookbook (or in the specification) whether some third-party applications use this. If so, you have to notify them to change the way they use our API and as soon as possible remove this feature.
  • allowSQLInjection lets the user perform SQL injections by using an old syntax. As soon as possible carry out the corrections described in the documentation to be able to set this attribute to false.

You can use /nl/jsp/ping.jsp?zones=true to check your security zone configuration. This page displays the active status of security measures (computed with these security flags) for the current IP.

HttpOnly cookie/useSecurityToken: refer to sessionTokenOnly flag.

Minimize whitelisted IPs: Out of the box, in security zones, we have added the 3 ranges for private networks. It is unlikely that you will use all of these IP addresses. So keep only the ones that you need.

Update webApp/internal operator to be only accessible in localhost.

File upload protection

Check with operational users what kind of files they upload to the server using nlclient/web interface. As a reminder, business needs can be:

  • images (jpg, gif, png, ...)
  • content (zip, html, css, ...)
  • marketing assets (doc, xls, pdf, psd, tiff, ...)
  • email attachment (doc, pdf, ...)
  • ETL (txt, csv, tab, ...)
  • etc.

Add all of them in serverConf/shared/datastore/@uploadWhitelist (valid java regular expression): refer to the documentation

Adobe Campaign does not restrict the file size. But you can do it by configuring IIS/Apache (see corresponding paragraph).


Please refer to the documentation for more information.

By default, all dynamic pages are automatically relayed to the local Tomcat server of the machine whose Web module is started. You can choose to not relay some of them. If you are not using some Adobe Campaign modules (such as webapp, interaction, some jsp) you can remove them from relay rules.

Out of the box, we have forced the capability to display end user resources using http (httpAllowed="true"). As these pages can display some PII (email content/address), redeem coupon, offer, you should force HTTPS again on these paths.

If you are using different host names (one public and one for operators), you can also prevent the relaying of some resources needed by operators over the public DNS name.

Outgoing connection protection

The default list of URLs that can be called by JavaScript codes (workflows, etc.) is limited. To allow a new URL, the administrator needs to reference it in the serverConf.xml file.

Three connection protection modes exist:

  • Blocking : all URLs that do not belong to the whitelist are blocked, with an error message. This is the default mode after a postupgrade.
  • Permissive : all URLs that do not belong to the whitelist are allowed.
  • Warning : all non-white URLs are allowed, but the JS interpreter emits a warning, so that the administrator can collect them. This mode adds JST-310027 warning messages.
<urlPermission action="warn" debugTrace="true">
  <url dnsSuffix="" urlRegEx=".*" />
  <url dnsSuffix="" urlRegEx=".*" />
  <url dnsSuffix="" urlRegEx=".*" />

New clients will use the blocking mode. If they want to allow a new URL, they need to contact their administrator to whitelist it.

Existing customers coming from a migration can use the warning mode for a while. Meanwhile they need to analyze the outbound trafic before authorizing the URLS.

Command restriction (server-side)

Several commands are blacklisted and cannot be executed using the execCommand function. An extra-security is provided by a dedicated Unix user to execute external commands. For hosted installations, this restriction is automatically applied. For on-premise installations, you can manually set up this restriction by following the dedicated documentation. In addition, Script and External task workflow activities are not available (newly installed instances).

Other configurations

You can add extra HTTP headers (for all pages): refer to the documentation:

  • You can add some additional headers such as HSTS, X-FRAME-OPTIONS, CSP...
  • You have to test them in a test environment before applying them in production. WARNING: Adobe Campaign can be broken by adding certain headers.

Adobe Campaign lets you set a plain password in the <dbcnx .../> element. Do not use this feature.

By default, Adobe Campaign does not stick a session to a specific IP, but you can active it to prevent the session from being stolen. To do it, in serverConf.conf set the checkIPConsistent attribute (in the authentication node) to true.

By default, Adobe Campaign's MTA does not use a secured connection to send content to the SMTP server. You have to enable this feature (may reduce delivery speed). To do this, set enableTLS to true in the <smtp ...> node.

You can reduce the lifetime of a session in the authentication node (sessionTimeOutSec attribute).

Web-server (Apache/IIS) configuration


Web-server (Apache/IIS) configuration

Change default error pages.

Disable old SSL version and ciphers:

  • on Apache, edit /etc/apache2/mods-available/ssl.conf. Here is an example:
    • SSLProtocol all -SSLv2 -SSLv3 -TLSv1
    • SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!SSLv3:!SSLv2:!TLSv1
  • on IIS, (see Microsoft KB245030), perform the following configuration:
    • Add registry subkey in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL
    • To enable the system to use the protocols that will not be negotiated by default (such as TLS 1.1 and TLS 1.2), change the DWORD value data of the DisabledByDefault value to 0x0 in the following registry keys under the Protocols key:

      SCHANNEL\Protocols\TLS 1.1\Client

      SCHANNEL\Protocols\TLS 1.1\Server

      SCHANNEL\Protocols\TLS 1.2\Client

      SCHANNEL\Protocols\TLS 1.2\Server

    • Disable SSL x.0:

      SCHANNEL\Protocols\SSL 3.0\Client: DisabledByDefault: DWORD (32-bit) Value to 1

      SCHANNEL\Protocols\SSL 3.0\Server: Enabled: DWORD (32-bit) Value to 0

Remove the TRACE method:

  • on Apache, edit in /etc/apache2/conf.d/security: TraceEnable Off
  • on IIS (see the documentation), perform the following configuration:
    • Make sure that Request Filtering role service or feature is installed.
    • In the Request Filtering pane, click the HTTP verbs tab, and then click Deny Verb. In the Actions pane, enter TRACE in the open dialog.

Remove the banner:

  • on Apache, edit /etc/apache2/conf.d/security:
    • ServerSignature Off
    • ServerTokens Prod
  • on IIS (see Microsoft KB317741):
    • Install URLScan.
    • Edit the Urlscan.ini file to have RemoveServerHeader=1

Limit query size to prevent important files from being uploaded:

  • On Apache, add the LimitRequestBody directive (size in bytes) in / directory. See the documentation.

    <Directory />
            Options FollowSymLinks
            AllowOverride None
            LimitRequestBody 10485760
  • On IIS, set the maxAllowedContentLength (maximum allowed content length) in the content filtering options. See the documentation).