fix(gateway,smtp): 修复邮件轰炸漏洞(审计 P0-1)
gateway v0.5.3 → v0.5.4: 1. gateway/src/main.cpp - /register/send_code 强制 CAPTCHA 校验(经 captcha:8009,Token 一次性消费,fail-closed) - 新增邮箱级双窗口限流(3 次/小时 + 10 次/天,CAPTCHA 通过后才计入配额) - 新增 _EMAIL_ADDR_RATE_HOUR / _EMAIL_ADDR_RATE_DAY 限流器实例 2. smtp/smtp_v2.py - 新增邮箱级长期限流兜底(EMAIL_HOURLY_LIMIT / EMAIL_DAILY_LIMIT,默认 3/10) - 新增全局熔断(SMTP_HOURLY_LIMIT,默认 100 封/小时,防 smtp 账号被封) - 预占/回滚与 60s 冷却同模式,发送失败不计入配额 3. gateway/docs/SECURITY_AUDIT_20260801.md - 新增带日期的安全审计报告 - P0-1 标记为已修复 v0.5.4 4. gateway/README.md - 更新 /register/send_code 接口文档(CAPTCHA 必填 + 5 层防护) - 更新版本变更日志 防护层次:IP 限流 → CAPTCHA → 邮箱级限流(gateway)→ 60s 冷却 → 邮箱级限流(smtp)→ 全局熔断
This commit is contained in:
1 parent
2774c3471f
commit
49f0698b9d
4 files changed
+424
-7
No files matched your search
+29
-2
@@ -28,7 +28,7 @@ using namespace qing;
|
||||
namespace json = boost::json;
|
||||
namespace po = boost::program_options;
|
||||
|
||||
constexpr const char *_VER = "0.5.3";
|
||||
constexpr const char *_VER = "0.5.4";
|
||||
|
||||
//////////////////////////////////
|
||||
#include <sstream>
|
||||
@@ -345,8 +345,13 @@ static std::unique_ptr<MyRSA::Generator> _KEYGEN;
|
||||
//static std::unique_ptr<Sqlite3> _SQLDB;
|
||||
static RateLimiter _LOGIN_RATE;
|
||||
static RateLimiter _REGISTER_RATE;
|
||||
static RateLimiter _EMAIL_CODE_RATE; // 发送邮箱验证码限流
|
||||
static RateLimiter _EMAIL_CODE_RATE; // 发送邮箱验证码 IP 级限流
|
||||
static RateLimiter _REFRESH_TOKEN_RATE; // Token 刷新限流
|
||||
// 邮箱级双窗口限流:同一邮箱在 gateway 层的发送配额。
|
||||
// 配合 IP 限流(IP 可被代理池绕过)与 smtp 同邮箱 60s 冷却(仅防短期重复)。
|
||||
// 双窗口设计:小时窗限制短期轰炸,天窗限制长期骚扰。
|
||||
static RateLimiter _EMAIL_ADDR_RATE_HOUR; // 3 次/小时/邮箱
|
||||
static RateLimiter _EMAIL_ADDR_RATE_DAY; // 10 次/天/邮箱
|
||||
|
||||
/* pymsg 下游调用超时(秒):
|
||||
* captcha / smtp verify_email 均为本机内存操作,5s 足够;
|
||||
@@ -1421,6 +1426,28 @@ http::response<http::string_body> handleSendEmailCode(
|
||||
if (!std::regex_match(email, email_regex))
|
||||
THROW_EX_CUSTOM(HttpException, 3004, "Invalid email format", 400, "/register/send_code");
|
||||
|
||||
// CAPTCHA 强制校验(防邮件轰炸:挡住自动化脚本,见审计报告 P0-1)
|
||||
// captcha_token / captcha_answer 必填,经 captcha:8009 pymsg 校验,Token 一次性消费。
|
||||
// fail-closed:captcha 服务不可用或响应异常一律视为校验失败。
|
||||
auto captcha_token_it = obj.find("captcha_token");
|
||||
auto captcha_answer_it = obj.find("captcha_answer");
|
||||
if (captcha_token_it == obj.end() || captcha_answer_it == obj.end())
|
||||
THROW_EX_CUSTOM(HttpException, 3007, "CAPTCHA required", 400, "/register/send_code");
|
||||
if (!verify_captcha_remote(std::string(captcha_token_it->value().as_string()),
|
||||
std::string(captcha_answer_it->value().as_string())))
|
||||
THROW_EX_CUSTOM(HttpException, 2201, "Invalid CAPTCHA", 400, "/register/send_code");
|
||||
|
||||
// 邮箱级双窗口限流(CAPTCHA 通过后才计入配额,避免无效请求消耗限流额度)
|
||||
// 3 次/小时 + 10 次/天;IP 限流可被代理池绕过,邮箱维度是更可靠的限制。
|
||||
if (!_EMAIL_ADDR_RATE_HOUR.check_and_record(email, 3, 3600))
|
||||
THROW_EX_CUSTOM(HttpException, 3104,
|
||||
"Too many code requests for this email. Please try again later.",
|
||||
429, "/register/send_code");
|
||||
if (!_EMAIL_ADDR_RATE_DAY.check_and_record(email, 10, 86400))
|
||||
THROW_EX_CUSTOM(HttpException, 3105,
|
||||
"Daily email limit exceeded. Please try again tomorrow.",
|
||||
429, "/register/send_code");
|
||||
|
||||
// 调 smtp 服务(pymsg/TCP :8008)发送验证码
|
||||
bool sent = false;
|
||||
int retry_after = 0; // smtp 同邮箱发送冷却的剩余秒数,0 表示非冷却拒绝
|
||||
|
||||
Reference in new issue
Block a user