[Solution] Inbound DMARC Enforcement for rspamd on ISPConfig 3.x — Stop Domain Impersonation Phishin

Discussion in 'General' started by haluk yildirim, Mar 7, 2026 at 7:04 AM.

  1. haluk yildirim

    haluk yildirim New Member

    Hi everyone,

    I've been running ISPConfig for long time (currently 3.3.0p3 with rspamd and 38 mail domains) and recently got hit by a phishing email impersonating my own domain — despite having SPF, DKIM, and DMARC (p=reject) all properly configured.

    This was actually confirmed as a known gap by Jesse Norell (HowtoForge Staff) back in 2022 in this thread:
    https://forum.howtoforge.com/threads/how-ispconfig-manage-spf-dmarc-for-incoming-mails.88247/

    He noted that rspamd checks SPF/DKIM/DMARC and uses them for scoring, but does not reject even when the domain policy explicitly calls for it. No solution was posted in that thread, so here's one that works alongside ISPConfig without modifying any ISPConfig-managed files.

    The Problem:

    ISPConfig does an excellent job of configuring outbound DMARC — it manages DKIM signing, SPF DNS records, and DMARC DNS records (with p=reject). This tells other mail servers to reject anyone impersonating your domain.

    However, ISPConfig does not configure rspamd to enforce DMARC policies on inbound mail. This means phishing emails that impersonate your own domain are delivered to your inbox despite failing every authentication check.

    What actually happens:

    1. Attacker sends email FROM [email protected] using an external server
    2. Postfix passes it to rspamd (milter on port 11332)
    3. rspamd checks: SPF = FAIL, DKIM = NONE, DMARC = FAIL (policy=reject)
    4. rspamd stamps the headers correctly (Authentication-Results shows all failures)
    5. rspamd adds only +2.0 points to the spam score for DMARC_POLICY_REJECT
    6. The email is delivered to your inbox because 2.0 points is well below the reject threshold of 8-15

    I discovered this when I received a phishing email that appeared to come from my own domain. The headers showed everything failed:

    Code:
    Authentication-Results: mail.example.com;
      dkim=none;
      dmarc=fail reason="No valid SPF, No valid DKIM" (policy=reject);
      spf=fail (domain does not designate 192.227.246.77 as permitted sender)
    
    Despite my DMARC policy being p=reject, the email landed in my inbox.

    Why this happens:

    ISPConfig's rspamd plugin (rspamd_plugin.inc.php) configures many modules in /etc/rspamd/local.d/ — DKIM signing, ARC, Bayes, whitelists, milter headers, neural networks, antivirus — but it does not create a dmarc.conf with enforcement actions. The rspamd DMARC module loads with defaults (dmarc {}) which only adds score adjustments without taking reject/quarantine actions.

    You can verify this on your own server:

    Code:
    cat /etc/rspamd/local.d/dmarc.conf 2>/dev/null || echo "NO dmarc.conf"
    grep -c "dmarc" /usr/local/ispconfig/server/plugins-available/rspamd_plugin.inc.php
    
    You'll find: no dmarc.conf exists, and ISPConfig's rspamd plugin has zero references to DMARC configuration.

    The Fix:

    Since ISPConfig doesn't manage any dmarc.conf file, we can safely add one in /etc/rspamd/override.d/ — this is the standard rspamd location for admin customisations and ISPConfig does not touch it (only rbl_group.conf and surbl_group.conf exist there from ISPConfig).

    Code:
    # Step 1: Verify ISPConfig doesn't manage DMARC
    cat /etc/rspamd/local.d/dmarc.conf 2>/dev/null || echo "NO local.d/dmarc.conf"
    cat /etc/rspamd/override.d/dmarc.conf 2>/dev/null || echo "NO override.d/dmarc.conf"
    grep -c "dmarc" /usr/local/ispconfig/server/plugins-available/rspamd_plugin.inc.php
    # Should return 0
    
    # Step 2: Create DMARC enforcement config (ISPConfig-safe)
    cat > /etc/rspamd/override.d/dmarc.conf << 'EOF'
    # DMARC inbound enforcement for ISPConfig 3 + rspamd
    # Place in /etc/rspamd/override.d/ (ISPConfig-safe)
    # ISPConfig does not manage this file
    #
    # Enforces DMARC policies published by sending domains:
    #   p=reject     -> message rejected at SMTP level
    #   p=quarantine -> spam header added (delivered to junk)
    #   p=none       -> no action (reporting only)
    
    enabled = true;
    
    actions {
      quarantine = "add_header";
      reject = "reject";
    }
    EOF
    
    # Step 3: Validate and activate
    rspamadm configtest
    # Should show: syntax OK
    
    systemctl restart rspamd
    
    # Step 4: Verify enforcement is active
    rspamadm configdump dmarc
    # Should show: actions { quarantine = "add_header"; reject = "reject"; }
    
    What this does:

    After this change, rspamd will enforce the DMARC policy published by the sending domain:

    - If the sender's domain has p=reject and the email fails SPF+DKIM alignment → hard rejected at SMTP level (never reaches any mailbox)
    - If the sender's domain has p=quarantine and the email fails → spam header added (delivered to junk folder)
    - If the sender's domain has p=none → no action (as before)

    This is global — it protects you from impersonation of ANY domain that publishes a DMARC policy, not just your own. If someone spoofs a gmail.com address to email you, and Gmail publishes p=reject, it'll be rejected too.

    What about legitimate forwarding?

    rspamd maintains an automatically-updated whitelist of known legitimate forwarders (mailing lists, forwarding services) that commonly break DKIM/SPF alignment. This prevents false positives. If you encounter issues with a specific forwarder, add it to /etc/rspamd/local.d/maps.d/dmarc_whitelist.inc.local.

    Prerequisite — make sure your domains have DMARC DNS records:

    In ISPConfig DNS, each domain should have:

    Code:
    Hostname:  _dmarc.yourdomain.com.au.
    Type:      TXT
    Text:      v=DMARC1; p=reject; rua=mailto:[email protected]; adkim=s; aspf=s
    
    Also ensure SPF and DKIM records are in place. ISPConfig manages DKIM signing automatically if enabled.

    Suggested ISPConfig Enhancement:

    I believe ISPConfig should include DMARC inbound enforcement as part of its rspamd integration. It already publishes DMARC policies (protecting others from spoofed mail from your domain) but doesn't enforce those same policies on inbound mail (leaving your own users vulnerable). A "DMARC Enforcement" toggle in Server Config → Mail would close this gap.

    This is a companion to my earlier contribution for per-domain mail SSL/SNI:
    https://github.com/bosspacific/ispconfig-mail-sni
    (Forum thread: [Solution] Per-Domain Mail SSL/TLS SNI for Dovecot & Postfix on ISPConfig 3.x)

    Full documentation and ISPConfig SNI tools: https://github.com/bosspacific/ispconfig-mail-sni

    Tested on: Debian 12 (Bookworm), ISPConfig 3.3.0p3, rspamd 3.x, Postfix 3.7

    Cheers,
    Haluk
     
    ahrasis and till like this.
  2. chico11mbit

    chico11mbit Member

    I use
    Code:
    v=DMARC1; p=quarantine; pct=100; rua=mailto:[email protected]; sp=quarantine; aspf=r;
    is this ok with your config?
     
  3. haluk yildirim

    haluk yildirim New Member

    Yes, that DMARC record works perfectly with this rspamd configuration.

    Your record:
    ```
    v=DMARC1; p=quarantine; pct=100; rua=mailto:[email protected]; sp=quarantine; aspf=r;
    ```

    Here's what each part means in relation to the inbound enforcement:

    - p=quarantine — the rspamd config will honour this and add spam score/quarantine messages that fail DMARC alignment, rather than outright rejecting them.
    - pct=100 — policy applies to all failing messages, not just a percentage. Good.
    - aspf=r — relaxed SPF alignment is the practical choice. It means the SPF domain only needs to match the organisational domain (e.g. bounces from mail.domain.com will still pass for domain.com). Strict (aspf=s) breaks too many legitimate mail flows.
    - sp=quarantine — applies the same policy to subdomains, which prevents attackers from spoofing subdomains like hr.domain.com or support.domain.com.

    To clarify how it all fits together: this rspamd config enforces "inbound" DMARC — meaning it checks the DMARC policy published by the "sender's" domain and applies whatever action they've specified (none/quarantine/reject). Your own DMARC record (shown above) tells "other" mail servers what to do when someone spoofs your domain.

    So both sides work independently:
    1. Your DMARC DNS record protects your domain from being spoofed (outbound reputation)
    2. The rspamd config protects your users from receiving spoofed mail from other domains (inbound enforcement)

    If you're happy that all your legitimate outbound mail is passing SPF and DKIM (check your rua aggregate reports), you can consider moving to p=reject later for maximum protection. But p=quarantine is a perfectly good production setting and many large organisations stay there permanently.
     
  4. chico11mbit

    chico11mbit Member

    wow. thx.
    Do you know how I can block whole countries with rspamd?
     
  5. haluk yildirim

    haluk yildirim New Member

    Yes, rspamd can do country-based blocking. If you're running rspamd 3.x (check with `rspamd --version`), the ASN module already does GeoIP lookups via DNS — no MaxMind account needed.

    1. Verify ASN module is active:
    ```bash
    rspamadm configdump | grep -i asn
    ```
    You should see `asn.rspamd.com` entries. If so, country data is already available for every inbound connection.

    2. Create a country blocklist map:
    ```bash
    mkdir -p /etc/rspamd/local.d/maps
    cat > /etc/rspamd/local.d/maps/blocked_countries.map << 'EOF'
    CN
    RU
    KP
    IR
    EOF
    ```
    Use ISO 3166-1 alpha-2 country codes. Add or remove as needed.

    3. Configure rspamd multimap:
    ```bash
    cat > /etc/rspamd/local.d/multimap.conf << 'EOF'
    BLOCKED_COUNTRY {
    type = "country";
    map = "/etc/rspamd/local.d/maps/blocked_countries.map";
    score = 10.0;
    description = "Mail from blocked country";
    }
    EOF
    ```

    4. Reload rspamd:
    ```bash
    systemctl reload rspamd
    ```

    This adds a score of 10.0 for emails from IPs in those countries. Combined with other spam scores it will usually push them over the reject threshold. Increase the score for harder blocking.

    Notes:
    - Blocks based on the sending server's IP, not the sender's claimed location
    - Legitimate mail from Gmail, Outlook etc won't be affected — their servers are in US/EU
    - Uses `/etc/rspamd/local.d/` so it survives ISPConfig updates
    - Consider score-based rather than outright reject — some legitimate businesses host in these countries
    - No MaxMind account needed — rspamd 3.x uses its own DNS-based GeoIP lookups via the ASN module

    This is a separate topic from DMARC enforcement, so if there's enough interest I could write it up as a separate tutorial.
     
    chico11mbit likes this.
  6. chico11mbit

    chico11mbit Member

    I think that would be very interesting for some users. Perhaps with different scores for different countries or different ASN? Because many users have problems with spam from providers like OVH :)
     
    Last edited: Mar 11, 2026 at 1:13 AM
  7. haluk yildirim

    haluk yildirim New Member

    Great suggestion — here's the expanded config with per-country scores and ASN blocking, fully tested on rspamd 3.14.3.


    Updated /etc/rspamd/local.d/multimap.conf

    Code:
    cat > /etc/rspamd/local.d/multimap.conf << 'EOF'
    # Country blocking - high risk (reject territory)
    BLOCKED_COUNTRY_HIGH {
    type = "country";
    map = "/etc/rspamd/local.d/maps/blocked_countries_high.map";
    score = 15.0;
    description = "Mail from high-risk country";
    prefilter = false;
    }

    # Country blocking - medium risk (adds score, doesn't reject alone)
    BLOCKED_COUNTRY_MED {
    type = "country";
    map = "/etc/rspamd/local.d/maps/blocked_countries_med.map";
    score = 5.0;
    description = "Mail from medium-risk country";
    prefilter = false;
    }

    # ASN blocking - spam-heavy hosting providers
    BLOCKED_ASN {
    type = "asn";
    map = "/etc/rspamd/local.d/maps/blocked_asn.map";
    score = 5.0;
    description = "Mail from blocked ASN/hosting provider";
    prefilter = false;
    }
    EOF


    Map files

    Code:
    # High risk - direct reject territory
    cat > /etc/rspamd/local.d/maps/blocked_countries_high.map << 'EOF'
    CN
    KP
    IR
    EOF

    # Medium risk - adds score but doesn't reject alone
    cat > /etc/rspamd/local.d/maps/blocked_countries_med.map << 'EOF'
    RU
    BY
    EOF

    # Spam-heavy hosting providers by ASN number
    cat > /etc/rspamd/local.d/maps/blocked_asn.map << 'EOF'
    # OVH / OVHcloud
    16276
    # Hetzner
    24940
    EOF

    Code:
    systemctl reload rspamd && rspamadm configtest

    Should return: syntax OK


    How the scoring works in practice:

    - CN IP only → +15.0 → reject
    - RU IP only → +5.0 → add header
    - OVH IP only → +5.0 → add header
    - RU + OVH + other spam signals → +5.0 +5.0 +x → reject
    - Legitimate Gmail / Outlook → 0 → unaffected

    ASN scoring is additive with country scoring. An OVH server sending from Russia scores +10.0 before any other spam signals — typically enough to reject when combined with RDNS_NONE, MISSING_DATE, RBL hits etc.


    Test it

    Code:
    # Test CN (high risk - should reject)
    echo "From: [email protected]
    To: [email protected]
    Subject: Test
    Date: $(date -R)
    Message-ID: <[email protected]>

    Test body" | rspamc -i 1.180.0.1 check | grep "BLOCKED\|Score:\|Action:"

    # Test RU (medium risk - should add header)
    echo "From: [email protected]
    To: [email protected]
    Subject: Test
    Date: $(date -R)
    Message-ID: <[email protected]>

    Test body" | rspamc -i 95.213.0.1 check | grep "BLOCKED\|Score:\|Action:"

    # Test OVH ASN (should add header)
    echo "From: [email protected]
    To: [email protected]
    Subject: Test
    Date: $(date -R)
    Message-ID: <[email protected]>

    Test body" | rspamc -i 51.75.0.1 check | grep "BLOCKED\|Score:\|Action:"

    Expected output:

    Code:
    # CN
    Action: reject
    Score: 23.90 / 15.00
    Symbol: BLOCKED_COUNTRY_HIGH (15.00)[CN]

    # RU
    Action: add header
    Score: 7.50 / 15.00
    Symbol: BLOCKED_COUNTRY_MED (5.00)[RU]

    # OVH
    Action: add header
    Score: 11.10 / 15.00
    Symbol: BLOCKED_ASN (5.00)[16276]


    Finding ASN numbers

    To find the ASN for any provider:

    Code:
    whois 51.75.0.1 | grep -i "^OriginAS\|^origin\|^AS"

    Or use https://bgp.he.net — search by provider name or IP.

    Common spam-source ASNs:
    - OVH / OVHcloud → 16276
    - Hetzner → 24940
    - Vultr → 20473
    - DigitalOcean → 14061
    - Linode / Akamai → 63949

    Add conservatively — these providers also host legitimate services. Score-based (not outright reject) is recommended for ASN blocking so legitimate mail still gets through unless combined with other spam signals.

    Note on GeoIP database: The legacy /usr/share/GeoIP/GeoIP.dat installed via apt works without a MaxMind account but may be outdated for some IP ranges. For current data: apt install geoipupdate and configure a free MaxMind account at https://www.maxmind.com

    Tested on: rspamd 3.14.3, Debian, ISPConfig 3.x.

    Cheers,
    Haluk
     
  8. haluk yildirim

    haluk yildirim New Member

    While testing the DMARC enforcement fix above, I discovered another critical vulnerability in ISPConfig's Spamfilter Whitelist that silently bypasses all rspamd actions — including the DMARC reject we just configured.

    The Problem:

    When you add your own email address (e.g. [email protected]) to ISPConfig's Spamfilter Whitelist, ISPConfig generates a rspamd settings file like this:

    Code:
    cat /etc/rspamd/local.d/users/spamfilter_wblist_3.conf

    spamfilter_wblist-3 {
    priority = 45;
    from = "[email protected]";
    rcpt = "@bosspacific.com.au";
    want_spam = no;
    apply {
    actions {
    reject = null;
    "add header" = null;
    greylist = null;
    "rewrite subject" = null;
    }
    }
    }

    This disables ALL rspamd actions — reject, add header, greylist, rewrite subject — for any email with From: [email protected]. The problem is rspamd matches this on the envelope/header From address, which anyone can forge. A phisher spoofing your address gets a free pass: score=24.89, action=no action (delivered).

    You can confirm this is happening by checking the rspamd log:

    Code:
    grep "settings_id\|disabled action\|no action" /var/log/rspamd/rspamd.log | tail -20

    You will see lines like:

    Code:
    apply static settings spamfilter_wblist-3; from,rcpt matched; priority high
    disabled action add header due to settings
    disabled action reject due to settings
    disabled action greylist due to settings
    NOT set pre-result to 'reject': 'Action set by DMARC' from dmarc(1); action is disabled

    Why it happens:

    Admins add their own address to the whitelist because self-sent emails (sending from your own address to yourself) get spam-scored. This is a legitimate use case. But ISPConfig's whitelist implementation whitelists by From address only, with no check for DKIM authentication or SPF alignment.

    The Fix:

    Step 1: Remove your own domain addresses from ISPConfig Spamfilter Whitelist.
    Go to ISPConfig → Email → Whitelist and delete any entries for your own domains.

    Step 2: Add your domain to the DKIM whitelist instead.

    Code:
    echo "yourdomain.com.au" >> /etc/rspamd/local.d/maps.d/dkim_whitelist.inc.ispc
    systemctl reload rspamd

    This whitelists mail that is DKIM-signed by your domain — which only your legitimate mail server can produce. A phisher spoofing your From address has no DKIM signature, so they get full scoring.

    Step 3: Force ISPConfig to regenerate rspamd configs:

    Code:
    php /usr/local/ispconfig/server/server.php

    Verify the whitelist conf file is gone:

    Code:
    ls /etc/rspamd/local.d/users/spamfilter_wblist_*.conf

    Step 4: Test that spoofed mail is now rejected:

    Code:
    echo "From: [email protected]
    To: [email protected]
    Subject: Test spoof
    Date: $(date -R)
    Message-ID: <[email protected]>

    Test body" | rspamc -i SPOOFED_IP check | grep "DMARC\|Score:\|Action:"

    Expected result:

    Code:
    Action: reject
    Score: 23.00 / 15.00
    Symbol: DMARC_POLICY_REJECT (2.00)[yourdomain.com.au : No valid SPF, No valid DKIM, reject]

    Result:

    - Legitimate self-sent mail (DKIM signed by your server) → DKIM whitelist match → delivered ✅
    - Phisher spoofing your From address (no DKIM) → full scoring + DMARC reject → rejected at SMTP ✅

    Note: This fix works in combination with the DMARC inbound enforcement fix posted earlier in this thread. Both are needed — the DMARC enforcement ensures p=reject is honoured, and this fix ensures the whitelist doesn't bypass it.

    Suggested ISPConfig GUI Improvement:

    The root cause of this trap is that ISPConfig's Email menu has a Whitelist and Blacklist but no DKIM Whitelist entry. Admins adding their own address to the regular Whitelist have no way of knowing it disables all rspamd actions rather than just lowering the score.

    A proper fix in ISPConfig would be to add a "DKIM Whitelist" entry under Email (alongside Whitelist/Blacklist) that writes to /etc/rspamd/local.d/maps.d/dkim_whitelist.inc.ispc instead of generating a spamfilter_wblist_*.conf with null actions. This would give admins a safe way to whitelist their own domains without disabling DMARC enforcement.

    As a minimum, the existing Whitelist UI should display a warning: "Adding your own domain or address here will disable all spam actions including DMARC reject for any mail claiming to be from that address, including spoofed mail."

    I attempted to raise this as a feature request / bug report on ISPConfig GitLab issue #6197 directly but have been unable to activate an account — confirmation emails never arrived despite two follow-up emails to the admin, and a second registration attempt failed with "email already taken". I have since registered a new account and emailed gitlab dot ispconfig dot org requesting manual approval. Once approved I will add the security impact details as a comment on #6197. In the meantime, the manual fix above is the safe approach.

    Tested on: rspamd 3.14.3, ISPConfig 3.3.x, Debian 12.

    Cheers,
    Haluk
     
    madmucho likes this.
  9. till

    till Super Moderator Staff Member ISPConfig Developer

    The purpose of the whitelist is to disable all filtering or blocking actions in Rspamd. So it does exactly what it should do, and you added your email address to the whitelist yourself. So you did not discover any security vulnerability as you claimed; in fact, you disabled all Rspamd filter actions for your address yourself, and that's what happened.

    What you posted above is a feature request for adding a more selective whitelist. Which is not a bad idea imho.
     
  10. haluk yildirim

    haluk yildirim New Member

    Hello Till,

    Thank you for the clarification. You are absolutely right that I added my own address to the whitelist myself, and it works exactly as designed.

    The concern I was trying to raise is a slightly different scenario: when a legitimate domain is on the whitelist, an external attacker can send mail FROM that whitelisted address (spoofing it), and rspamd will disable all filter actions for that message — including the DMARC p=reject check. So the phisher bypasses DMARC enforcement not because of anything wrong with ISPConfig, but because the whitelist match happens before DMARC evaluation.

    The user added their own domain to the whitelist with good intentions (to ensure their own mail is never rejected), not realising it also opens that domain up to being spoofed past DMARC.

    The DKIM whitelist approach I described would address this — only bypass filtering when the message is actually signed by that domain's DKIM key, not just because the From: address matches.

    As you said, not a bad idea — I just wanted to make sure the scenario was clear. Happy to discuss further or submit it as a proper feature request.

    Best regards,
    Haluk
     

Share This Page