临时邮箱概述
临时邮箱(Disposable Email Address, DEA)是一种短期有效的邮箱地址,用户无需注册即可使用,通常存活时间从几分钟到几小时不等。常见的临时邮箱服务商包括10minutemail.com、guerrillamail.com、mailinator.com、tempmail.com、throwaway.email、yopmail.com、trashmail.com等。虽然临时邮箱为用户提供了隐私保护,但在业务系统中被大量用于恶意注册、刷单、垃圾评论、薅羊毛等行为,因此检测并过滤临时邮箱是风控系统的重要组成部分。
临时邮箱服务商生态分析
临时邮箱服务商按照功能和特点可以分为以下几类:
| 类别 | 特点 | 代表服务 | 检测难度 |
|---|---|---|---|
| 主流临时邮箱 | 知名度高,用户量大,通常有固定域名 | 10minutemail.com, mailinator.com, guerrillamail.com | 低 |
| 一次性邮箱 | 用完即弃,域名随机生成,自动销毁 | temp-mail.org, getnada.com, emailnator.com | 中 |
| 匿名邮箱 | 不要求注册信息,注重隐私保护 | protonmail.com(免费版), tutanota.com | 高 |
| 隐私邮箱 | 提供邮件转发和别名服务,隐藏真实邮箱 | simplelogin.io, anonaddy.com, firefox relay | 高 |
近年来,临时邮箱服务商呈现出以下趋势:域名轮换频率加快,越来越多的服务商使用随机生成的一级域名;部分服务商开始提供付费长期服务,模糊了临时邮箱与普通邮箱的界限;一些服务商开始提供API接口,方便开发者集成。
邮箱格式验证(RFC 5321/5322标准)
在进行深度检测之前,首先需要验证邮箱地址的格式是否符合标准。RFC 5321(SMTP协议)和RFC 5322(互联网消息格式)定义了邮箱地址的语法规范:
function validateEmailFormat(string $email): array
{
// RFC 5321: 邮箱地址最大长度254字符
if (strlen($email) > 254) {
return ['valid' => false, 'reason' => 'Email too long (>254 chars)'];
}
// 检查是否包含@符号
$atPos = strrpos($email, '@');
if ($atPos === false) {
return ['valid' => false, 'reason' => 'Missing @ symbol'];
}
$localPart = substr($email, 0, $atPos);
$domain = substr($email, $atPos + 1);
// 本地部分长度1-64字符
if (strlen($localPart) < 1 || strlen($localPart) > 64) {
return ['valid' => false, 'reason' => 'Invalid local part length'];
}
// 域名部分长度1-255字符,且至少包含一个点
if (strlen($domain) < 1 || strlen($domain) > 255) {
return ['valid' => false, 'reason' => 'Invalid domain length'];
}
// 本地部分不允许以点开头或结尾
if ($localPart[0] === '.' || $localPart[-1] === '.') {
return ['valid' => false, 'reason' => 'Local part starts/ends with dot'];
}
// 不允许连续两个点
if (strpos($localPart, '..') !== false) {
return ['valid' => false, 'reason' => 'Consecutive dots in local part'];
}
// RFC 5321 允许的本地部分字符
if (!preg_match('/^[a-zA-Z0-9.!#$%&\'*+\\/=?^_`{|}~-]+$/', $localPart)) {
return ['valid' => false, 'reason' => 'Invalid characters in local part'];
}
// 域名格式验证
if (!preg_match('/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/', $domain)) {
return ['valid' => false, 'reason' => 'Invalid domain format'];
}
return ['valid' => true, 'local_part' => $localPart, 'domain' => $domain];
}
邮箱域名DNS深度检测
DNS深度检测通过分析邮箱域名的DNS记录来判断邮箱的可信度,是临时邮箱检测的重要手段:
MX记录检测
检查域名是否存在MX记录,以及MX记录指向的邮件服务器是否属于已知的临时邮箱服务商。临时邮箱的MX记录通常指向特定的邮件服务器,而不是主流的邮件服务商。如果没有MX记录,则说明该域名无法接收邮件,高度可疑。
SPF记录检测
SPF(Sender Policy Framework)记录指定了允许发送邮件的服务器IP地址。临时邮箱服务商的SPF记录通常包含大量IP段,或者指向特定的邮件发送服务。SPF记录格式为v=spf1开头,后跟允许的IP地址或域名。
DKIM签名检测
DKIM(DomainKeys Identified Mail)通过数字签名验证邮件的完整性和来源。临时邮箱服务商通常不会配置DKIM签名,或者签名信息不完整。检查域名的DKIM记录(格式为default._domainkey.example.com的TXT记录)可以辅助判断。
DMARC策略检测
DMARC(Domain-based Message Authentication, Reporting & Conformance)策略定义了邮件验证失败后的处理方式。临时邮箱域名的DMARC策略通常为p=none(不处理),而正规邮箱服务商通常会设置p=quarantine或p=reject。
class DnsDeepCheck
{
public function checkDnsRecords(string $domain): array
{
$results = [];
// MX记录检查
$mxHosts = [];
if (getmxrr($domain, $mxHosts)) {
$results['mx'] = ['exists' => true, 'hosts' => $mxHosts];
$suspiciousMx = ['mailinator', 'guerrillamail', '10minutemail', 'temp-mail', 'trashmail'];
foreach ($mxHosts as $host) {
foreach ($suspiciousMx as $keyword) {
if (stripos($host, $keyword) !== false) {
$results['mx']['suspicious'] = true;
break 2;
}
}
}
} else {
$results['mx'] = ['exists' => false, 'suspicious' => true];
}
// SPF记录检查
$spfRecords = dns_get_record($domain, DNS_TXT);
$results['spf'] = ['exists' => false];
foreach ($spfRecords as $record) {
if (strpos($record['txt'], 'v=spf1') === 0) {
$results['spf'] = ['exists' => true, 'value' => $record['txt']];
break;
}
}
// DMARC记录检查
$dmarcRecords = dns_get_record('_dmarc.' . $domain, DNS_TXT);
$results['dmarc'] = ['exists' => false];
foreach ($dmarcRecords as $record) {
if (strpos($record['txt'], 'v=DMARC1') === 0) {
$results['dmarc'] = ['exists' => true, 'value' => $record['txt']];
break;
}
}
return $results;
}
}
SMTP会话完整握手流程
SMTP验证是最精确的检测方法之一,通过模拟SMTP邮件发送过程来验证邮箱地址是否存在且可用。其核心原理是不真正发送邮件,而是在SMTP协议交互的RCPT TO阶段验证邮箱地址的有效性。完整的SMTP握手流程如下:
// SMTP握手完整流程
C: <连接到SMTP服务器端口25>
S: 220 mx.example.com ESMTP Postfix (Welcome message)
C: EHLO verifier.example.com
S: 250-mx.example.com
S: 250-PIPELINING
S: 250-SIZE 10240000
S: 250-VRFY
S: 250-ETRN
S: 250-STARTTLS
S: 250-AUTH LOGIN PLAIN
S: 250-ENHANCEDSTATUSCODES
S: 250-8BITMIME
S: 250 DSN
C: MAIL FROM:<verify@example.com>
S: 250 2.1.0 Ok
C: RCPT TO:<target@example.com>
S: 250 2.1.5 Ok <-- 邮箱存在
-- 或 --
S: 550 5.1.1 User unknown <-- 邮箱不存在
C: QUIT
S: 221 2.0.0 Bye
class SmtpVerifier
{
private int $timeout = 10;
private string $fromEmail = 'check@verify-domain.com';
public function verify(string $email): array
{
$domain = substr(strrchr($email, '@'), 1);
$mxHosts = [];
if (!getmxrr($domain, $mxHosts)) {
return ['valid' => false, 'score' => 0, 'detail' => 'No MX records'];
}
$conn = @fsockopen($mxHosts[0], 25, $errno, $errstr, $this->timeout);
if (!$conn) {
return ['valid' => false, 'score' => 0, 'detail' => "Connection failed: $errstr"];
}
stream_set_timeout($conn, $this->timeout);
$response = '';
// 读取欢迎信息
$response .= fgets($conn, 512);
// EHLO
fwrite($conn, "EHLO verifier.local\r\n");
while ($line = fgets($conn, 512)) {
$response .= $line;
if (substr($line, 3, 1) === ' ') break;
}
// MAIL FROM
fwrite($conn, "MAIL FROM:<{$this->fromEmail}>\r\n");
$response .= fgets($conn, 512);
// RCPT TO - 关键检查点
fwrite($conn, "RCPT TO:<{$email}>\r\n");
$rcptResponse = fgets($conn, 512);
$response .= $rcptResponse;
// QUIT
fwrite($conn, "QUIT\r\n");
fgets($conn, 512);
fclose($conn);
$code = (int)substr($rcptResponse, 0, 3);
if ($code === 250) {
return ['valid' => true, 'score' => 100, 'detail' => 'SMTP verification passed'];
} elseif (in_array($code, [550, 551, 552, 553, 450, 451])) {
return ['valid' => false, 'score' => 0, 'detail' => "SMTP refused: code $code"];
}
return ['valid' => null, 'score' => 50, 'detail' => "SMTP response: $code"];
}
}
SMTP验证的局限性
SMTP验证虽然精度高,但在实际应用中存在以下局限性:
- 服务器反探测:部分邮件服务器配置了反垃圾邮件策略,会检测并拒绝未经认证的SMTP连接,返回虚假的"用户存在"或"用户不存在"信息
- 灰名单(Greylisting):部分服务器使用灰名单技术,对首次连接临时拒绝(返回450状态码),要求客户端重试。这会导致SMTP验证需要多次尝试
- 速率限制:频繁的SMTP验证请求可能导致IP被临时封禁,需要控制验证频率并合理使用多个验证节点
- 连接超时:部分邮件服务器响应缓慢,SMTP验证可能耗时3-5秒,影响用户体验
- 端口封锁:部分云服务商和ISP封锁了25端口,导致无法直接建立SMTP连接
域名黑名单维护策略
黑名单的维护是临时邮箱检测系统的核心工作,需要持续更新以保证检测效果。系统采用以下策略维护黑名单:
自动发现机制
定期爬取临时邮箱服务商列表网站(如Wikipedia的Comparison of disposable email services页面、GitHub上社区维护的disposable-email-domains项目),自动发现新的临时邮箱域名。同时,通过分析用户注册数据中的异常模式,发现新出现的临时邮箱域名。
社区共享
参与开源黑名单项目,如@mailsac/email-list、disposable-email-domains、mailcheck等,与其他安全团队共享威胁情报。这种社区协作方式能够快速获取新发现的临时邮箱域名。
机器学习辅助
使用机器学习模型分析域名的特征(注册时间、注册商、MX记录模式、域名年龄、Alexa排名等),自动识别疑似临时邮箱域名。模型可以识别出尚未被收录到黑名单中的新域名,实现零日检测。
class BlacklistManager
{
private const CACHE_KEY = 'temp_email:blacklist';
private const CACHE_TTL = 86400;
private Redis $redis;
public function isBlacklisted(string $email): bool
{
$domain = strtolower(substr(strrchr($email, '@'), 1));
$blacklist = $this->getBlacklist();
return in_array($domain, $blacklist, true);
}
public function getBlacklist(): array
{
$cached = $this->redis->get(self::CACHE_KEY);
if ($cached !== false) {
return json_decode($cached, true);
}
// 从本地文件加载
$file = __DIR__ . '/temp_email_domains.json';
$list = file_exists($file) ? json_decode(file_get_contents($file), true) : [];
$this->redis->setex(self::CACHE_KEY, self::CACHE_TTL, json_encode($list));
return $list;
}
public function addDomain(string $domain): void
{
$domain = strtolower(trim($domain));
$list = $this->getBlacklist();
if (!in_array($domain, $list, true)) {
$list[] = $domain;
$this->saveBlacklist($list);
}
}
private function saveBlacklist(array $list): void
{
$file = __DIR__ . '/temp_email_domains.json';
file_put_contents($file, json_encode($list, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
$this->redis->setex(self::CACHE_KEY, self::CACHE_TTL, json_encode($list));
}
}
检测结果可信度评分模型
系统采用综合评分模型,将多种检测方法的结果加权融合,生成最终的可信度评分:
| 检测维度 | 检测方法 | 权重 | 最高分 |
|---|---|---|---|
| 域名黑名单 | 域名是否在黑名单中 | 25% | 100 |
| DNS探测 | MX记录、域名年龄、SPF/DKIM/DMARC | 20% | 80 |
| SMTP验证 | 邮箱地址是否存在 | 25% | 100 |
| 启发式规则 | 域名模式、TLD、邮箱名模式 | 15% | 60 |
| 域名信誉 | 注册时间、注册商、IP信誉 | 15% | 60 |
class EmailScoreModel
{
private array $weights = [
'blacklist' => 0.25,
'dns' => 0.20,
'smtp' => 0.25,
'heuristic' => 0.15,
'reputation' => 0.15,
];
public function evaluate(string $email, array $checkResults): array
{
$totalScore = 0;
foreach ($this->weights as $dimension => $weight) {
$score = $checkResults[$dimension]['score'] ?? 0;
$totalScore += $score * $weight;
}
$totalScore = round($totalScore, 2);
return [
'is_disposable' => $totalScore >= 50,
'total_score' => $totalScore,
'level' => $totalScore >= 80 ? 'highly_likely' : ($totalScore >= 50 ? 'likely' : ($totalScore >= 30 ? 'suspicious' : 'clean')),
'details' => $checkResults,
];
}
}
批量检测性能优化
在实际业务场景中,经常需要批量检测大量邮箱地址。性能优化方案包括:
- 连接池:对SMTP连接使用连接池技术,复用TCP连接,减少连接建立的开销
- 异步检测:使用消息队列(如Redis队列或RabbitMQ)将检测请求异步处理,前端立即返回受理状态,后台消费者完成检测后回调通知
- 缓存域名级别结果:同一域名下的多个邮箱地址共享DNS探测和黑名单检查结果,避免重复查询。缓存键为域名,缓存时间为1小时
- 并发控制:使用Golang协程或PHP的Swoole/Fiber实现并发SMTP验证,将单次检测的等待时间从串行的3-5秒降低到并行的1秒以内
class BatchEmailVerifier
{
private EmailVerifier $verifier;
private array $domainCache = [];
public function batchVerify(array $emails): array
{
$results = [];
$batch = [];
// 先按域名分组,共享域名级检测结果
foreach ($emails as $email) {
$domain = substr(strrchr($email, '@'), 1);
$batch[$domain][] = $email;
}
foreach ($batch as $domain => $domainEmails) {
// 域名级检测只做一次
if (!isset($this->domainCache[$domain])) {
$this->domainCache[$domain] = $this->verifier->checkDomain($domain);
}
$domainResult = $this->domainCache[$domain];
foreach ($domainEmails as $email) {
$results[$email] = $this->verifier->verifyWithCache($email, $domainResult);
}
}
return $results;
}
}
API接口设计
| 接口 | 路径 | 参数 | 返回数据 |
|---|---|---|---|
| 单邮箱检测 | GET /api/email/verify | is_disposable, score, level, details | |
| 批量检测 | POST /api/email/batch-verify | emails[] | 批量检测结果数组 |
| 域名检查 | GET /api/email/domain-check | domain | DNS记录、黑名单状态、信誉评分 |