解决使用标准smtp认证方式通过MailGun等发件服务商发件时暴露源站IP的问题

方法一: 使用API或默认隐藏Received信息的smtp服务商

众所周知,大多数smtp发件都会在邮件header中暴露源服务器ip,比如

Received: from example.com ([123.456.78.90]) 

少数smtp服务商会默认隐藏源站ip,比如阿里云(此外,有一些专业的匿名发件,但我没用过,多数情况下也不需要,另外完全不带发信ip可能更易进垃圾箱)。但有时候并不方便用阿里云的邮件推送服务。好消息是,现在很多网站程序都支持使用api发信,比如现在wordpress有一些smtp插件都支持使用mailgun API发信。

使用 Mailgun API 时,不走 SMTP 协议,而是通过 HTTP API 请求,然后Mailgun 的服务器端会发送邮件。邮件头只显示 Mailgun 自己的服务器 IP,不会显示 API 请求发起方的 IP,因为 API 模式邮件发送时无需遵守 SMTP 协议标准的 Received 头要求。

但平时使用的很多程序并不支持api发信,那么只能想别的办法,即邮件中继服务器。

方法二: 在中继服务器使用端口转发

其实要实现中继功能,有个取巧的方法是直接端口转发,比如直接使用iptables转发tcp。例如,源站服务器ip为 a.a.a.a,中继服务器ip为b.b.b.b,smtp服务商为mailgun:

  • 源站在hosts中将smtp.mailgun.com的ip指定为中继服务器ip
echo "b.b.b.b smtp.mailgun.com" | sudo tee -a /etc/hosts
  • 中继服务器中配置端口转发

打开网络转发功能

echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
sysctl -p

添加iptables NAT规则,转发本地587到smtp.mailgun.com:587

iptables -t nat -A PREROUTING -p tcp --dport 587 -j DNAT --to-destination smtp.mailgun.com:587
iptables -t nat -A POSTROUTING -p tcp -d smtp.mailgun.com --dport 587 -j MASQUERADE

添加中继服务器防火墙安全规则

# 清理已有相冲突的规则,视具体情况可选
iptables -F INPUT 

# 允许已经建立和相关连接数据回程
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

# 限制只允许源站a.a.a.a发起的SMTP连接
iptables -A INPUT -p tcp -s a.a.a.a --dport 587 -j ACCEPT

# 限制新连接速率,防止扫描和重试攻击(例如每分钟最多15个新连接),测试如果没有问题可以考虑删除
iptables -A INPUT -p tcp --dport 587 --syn -m limit --limit 15/minute --limit-burst 20 -j ACCEPT

# 默认拒绝其他主机访问587端口
iptables -A INPUT -p tcp --dport 587 -j DROP

但这样可靠性还是会低一些

方法三:使用现成的开源可自托管的smtp中继项目

首先需要测试中继服务器是否能正确连接smtp发件商的相应端口

apt install -y telnet
telnet smtp.mailgun.org 587

返回类似以下内容即为正常

Trying 12.34.56.78...
Connected to smtp.mailgun.org.
Escape character is '^]'.

如果卡在trying或连接失败,则需要检查防火墙 或 更换其他服务器商家

1. 配置中继服务器

不知道是不是由于合规性问题,好像没有很简便的解决方案,gpt给我推荐了Postfix、Haraka、Postal ,我先使用最轻量的Postfix试一下。

docker-compose.yml

services:
  postfix-relay:
    image: boky/postfix:latest
    container_name: postfix-relay
    restart: unless-stopped
    environment:
      HOSTNAME: relay.example.com # 解析到中继服务器的域名
      RELAYHOST: "[smtp.mailgun.org]:587" # 上游SMTP发件服务商
      RELAYHOST_USERNAME: no-reply@mail.example.com # Mailgun SMTP用户名
      RELAYHOST_PASSWORD: longpasswordlongpasswordlongpassword  # Mailgun SMTP密码
      USE_TLS: yes
      MYNETWORKS: 987.65.43.21  # 白名单ip 即源站服务器ip
      POSTFIX_smtpd_tls_security_level: encrypt
      
      ALLOWED_SENDER_DOMAINS: www.example.net # 白名单域名
      # Postfix TLS配置引用Let's Encrypt证书路径
      POSTFIX_smtpd_tls_cert_file: /etc/letsencrypt/live/relay.example.com/fullchain.pem
      POSTFIX_smtpd_tls_key_file: /etc/letsencrypt/live/relay.example.com/privkey.pem
      POSTFIX_smtpd_tls_CAfile: /etc/letsencrypt/live/relay.example.com/chain.pem
      POSTFIX_smtp_tls_security_level: encrypt
      POSTFIX_smtp_tls_loglevel: "1"
      POSTFIX_smtp_sasl_security_options: noanonymous
      POSTFIX_smtpd_recipient_restrictions: "permit_mynetworks, reject_non_fqdn_recipient, reject_unknown_recipient_domain, check_sender_access lmdb:/etc/postfix/allowed_senders, reject"
      POSTFIX_header_checks: regexp:/etc/postfix/header_checks
      POSTFIX_smtp_header_checks: regexp:/etc/postfix/header_checks

      POSTFIX_smtp_helo_name: mail.example.com  # 使用mail.example.com作为HELO名称
      POSTFIX_proxy_interfaces: 123.45.67.89  # 声明代理接口 改成中继服务ip
      POSTFIX_smtpd_sasl_authenticated_header: no  # 不添加认证信息到头
    ports:
      - 587:587
    volumes:
      - postfix-spool:/var/spool/postfix
      - postfix-log:/var/log
      - ./certbot/data:/etc/letsencrypt # 挂载证书存储文件夹到容器
      - ./postfix/header_checks:/etc/postfix/header_checks

  certbot:
    image: certbot/dns-cloudflare
    container_name: certbot
    volumes:
      - ./certbot/data:/etc/letsencrypt # 证书数据目录
      - ./certbot/log:/var/log/letsencrypt
      - ./certbot/cloudflare.ini:/cloudflare.ini:ro # Cloudflare API token
    command: >
      certonly --dns-cloudflare --dns-cloudflare-credentials /cloudflare.ini
      --email 123@gmail.com --agree-tos --no-eff-email  # 修改邮箱
      --key-type rsa --rsa-key-size 2048
      --dns-cloudflare-propagation-seconds 30 -d relay.example.com

volumes:
  postfix-spool: null
  postfix-log: null
networks: {}

以上配置借助ai生成,可能有部分配置冗余,但是整体满足需求。仅通过白名单ip认证。如果您有更完善的配置,请评论告诉我,感谢。

创建cloudflare api文件用于申请证书 vim ./certbot/cloudflare.ini

dns_cloudflare_api_token = yourapitoken  # 区域dns api token,不是账户api key
dns_cloudflare_zone_id = yourzoneid  # 在域名详情的右侧显示

创建删除邮件头的配置文件 vim ./postfix/header_checks

/^Received:/                IGNORE
/^X-Originating-IP:/        IGNORE
/^X-Mailer:/                IGNORE
/^User-Agent:/              IGNORE

首次运行申请证书

docker compose run --rm certbot

确认证书正确生成后,可以启动中继服务

docker compose up -d postfix-relay

后续可以添加crontab进行自动申请

0 0 * * * cd /你的docker-compose路径 && docker compose run --rm certbot renew --quiet && docker compose restart postfix-relay

脚本说明:

  • 每日尝试续签一次证书。如证书即将(小于30天到期),则真正执行更新,否则什么也不做。
  • 若证书续签完成,立即重启postfix-relay容器以加载新证书。

2. 测试发信(均在白名单ip服务器测试)

测试中继服务器的SMTP端口是否正确设置了证书

openssl s_client -connect relay.example.com:587 -starttls smtp

返回以下证书信息即为正常

图片[1]-解决使用标准smtp认证方式通过MailGun等发件服务商发件时暴露源站IP的问题-THsInk

测试能否正常发信

swaks --to "example <example@gmail.com>" \
      --from "example <no-reply@mail.example.com>" \
      --server relay.example.com \
      --port 587 \
      --ehlo example.com \
      --tls --tls-verify \
      --timeout 30 \
      --data "Subject: 测试隐藏源服务ip

这是测试邮件内容。"

查看邮件头

图片[2]-解决使用标准smtp认证方式通过MailGun等发件服务商发件时暴露源站IP的问题-THsInk

发现源服务器ip已被隐藏,其中178.208.x.x 是我的中继服务器ip,其余ip均为mailgun ip。

使用自建邮件中继服务器的方法适合轻度使用、不想泄漏源站ip,但其实泄漏了也基本不会被攻击的情况。但要是真用到了,也只会造成发件服务离线,不会对网站本身造成影响,更换中继服务器显然成本更低、更简单。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!无需注册,过短或乱码评论会被屏蔽。
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容