feat: captcha C++20 重写 + 安全审计修复 (P0/P1/P2/P3, 10/11 已修复)
captcha2 — C++20 验证码服务(FreeType + stb_image_write): - 完整实现: 图片生成、CaptchaStore、RateLimiter、pymsg RPC 路由 - 协议完全兼容 Python 原版,gateway 零改动 审计修复 (captcha2_SECURITY_AUDIT_20260809.md): - P0-1: 存储答案小写化 (对齐 Python 版语义) - P1-1: gateway 按 IP 限流 10/min + captcha2 全局限流 600/min 兜底 - P2-1: generate_text mt19937 → std::random_device - P2-2: ::tolower UB → unsigned char lambda - P3-1: parse_int 辅助函数, stoi 异常可诊断 - P3-2: 显式 #include <iomanip> - P3-3: 补齐 CAPTCHA_LEN/TTL/RATE_LIMIT 环境变量 - P3-4: LogC 日志超时 5.0→1.0s - P3-5: 限流/满容抛异常, gateway 映射 429/503 - P3-6: 清理死代码 集成: - core/init.txt: captcha → captcha2 - confs configs/captcha.ini: 新增 max_store/rate_limit_per_min - docs: 开发方案、安全审计报告、交叉引用更新
This commit is contained in:
1 parent
29e4f96061
commit
546394b681
15 files changed
+3959
-4
No files matched your search
+19
-1
@@ -420,6 +420,7 @@ static RateLimiter _EMAIL_CODE_RATE; // 发送邮箱验证码 IP 级限流
|
||||
static RateLimiter _REFRESH_TOKEN_RATE; // Token 刷新限流
|
||||
static RateLimiter _RESET_PASSWORD_RATE; // 重置密码 IP 级限流
|
||||
static RateLimiter _RESET_CODE_RATE; // 发送重置密码验证码 IP 级限流
|
||||
static RateLimiter _CAPTCHA_IMAGE_RATE; // 获取验证码图片 IP 级限流(防全局 DoS,P1-1)
|
||||
// 邮箱级双窗口限流:同一邮箱在 gateway 层的发送配额。
|
||||
// 配合 IP 限流(IP 可被代理池绕过)与 smtp 同邮箱 60s 冷却(仅防短期重复)。
|
||||
// 双窗口设计:小时窗限制短期轰炸,天窗限制长期骚扰。
|
||||
@@ -1849,6 +1850,16 @@ http::response<http::string_body> handleCaptchaImage(
|
||||
http::response<http::string_body> res;
|
||||
res.version(req.version());
|
||||
|
||||
/* IP 级速率限制(P1-1 修复):单 IP 每分钟最多 10 次,
|
||||
防止单一攻击者打满全局限流导致全站验证码不可用 */
|
||||
std::string client_ip = "direct";
|
||||
auto it_host = req.base().find("X-Forwarded-For");
|
||||
if (it_host != req.base().end())
|
||||
client_ip = std::string(it_host->value());
|
||||
if (!_CAPTCHA_IMAGE_RATE.check_and_record(client_ip, 10, 60))
|
||||
THROW_EX_CUSTOM(HttpException, 2403,
|
||||
"Too many captcha requests. Please try again later.", 429, "/captcha/image");
|
||||
|
||||
/* 调 captcha 服务(pymsg/TCP),连接失败/超时视为 502 */
|
||||
pmc::net::MsgClient client(_CAPTCHA_HOST, _CAPTCHA_PORT, "gateway");
|
||||
std::string resp;
|
||||
@@ -1864,8 +1875,15 @@ http::response<http::string_body> handleCaptchaImage(
|
||||
try {
|
||||
auto jv = json::parse(resp);
|
||||
auto& obj = jv.as_object();
|
||||
if (!obj.at("success").as_bool())
|
||||
if (!obj.at("success").as_bool()) {
|
||||
// P3-5: 依据 error 信息映射准确 HTTP 状态码
|
||||
std::string err_msg = json::value_to<std::string>(obj.at("error"));
|
||||
if (err_msg.find("rate_limit") != std::string::npos)
|
||||
THROW_EX_CUSTOM(HttpException, 2401, "Too many captcha requests", 429, "/captcha/image");
|
||||
if (err_msg.find("store") != std::string::npos || err_msg.find("full") != std::string::npos)
|
||||
THROW_EX_CUSTOM(HttpException, 2401, "Captcha service overloaded", 503, "/captcha/image");
|
||||
THROW_EX_CUSTOM(HttpException, 2401, "Captcha service error", 502, "/captcha/image");
|
||||
}
|
||||
auto& data = obj.at("data").as_object();
|
||||
token = json::value_to<std::string>(data.at("token"));
|
||||
auto image_b64 = json::value_to<std::string>(data.at("image"));
|
||||
|
||||
Reference in new issue
Block a user