复查修复(四): 独立审查发现的问题

- S1/S2: 会话锁改 RLock(持锁可重入调 _save_sessions);cookie 时间戳改整数+解析兼容(会话创建/验证往返已实测)
- M1: api_login 接入 verify_user(账号锁定/失败计数生效),DB 无用户时才回退 config 凭据
- M3+L5: handler _do_auth 统一入口加 IP 白名单检查;未知 auth_type 返回 401
- M4+L10: metrics 与无版本 /api/admin/ 加入受保护端点
- M6: debug 日志敏感头脱敏;main.py 不再打印 token 前缀
- M7: auth_token.txt / auth_sessions.json chmod 600
- M9: verify_user 统一错误消息防用户枚举
- M10: AdminAPI 复用共享 APIAuthManager(修复会话状态分裂)
- L7: check_auth 大小写不敏感匹配(防 /API/.. 大写绕过)
- L8: token_expires_at 显式 is not None 判断
- L11: verify_password 对非 bcrypt 哈希回退 PBKDF2(重写,修复 ValueError 分支不落回退的问题)
This commit is contained in:
HYC Fixer
2026-09-02 00:45:01 +08:00
parent f77a51247c
commit abdbec85a4
7 changed files with 807 additions and 754 deletions
+51 -12
View File
@@ -282,6 +282,9 @@ class MirrorServerHandler(BaseHTTPRequestHandler):
method = (getattr(self, 'command', '') or 'GET').upper()
# 大小写不敏感匹配(仅用于鉴权决策,不影响文件路径处理)
check_path = check_path.lower()
# 任何方法都公开的端点(登录、认证状态查询等)
public_any = [
'api/v2/user/login',
@@ -344,6 +347,10 @@ class MirrorServerHandler(BaseHTTPRequestHandler):
# 管理控制台页面本身: 访问时浏览器弹 Basic 登录框,
# 登录后同源 API 请求自动携带凭据, 面板才能工作
'api/ui',
# 无版本前缀的管理路由
'api/admin/',
# 系统指标(泄露主机资源信息)
'api/v2/metrics',
]
# 任何方法都公开
@@ -372,11 +379,39 @@ class MirrorServerHandler(BaseHTTPRequestHandler):
return True
def _do_auth(self, auth_type):
"""执行指定类型的认证检查"""
"""执行指定类型的认证检查(统一入口: 先过 IP 白名单)"""
if not self._ip_allowed():
self.send_auth_required()
return False
if auth_type == 'basic':
return self._check_basic_auth()
elif auth_type == 'token':
return self._check_token_auth()
# 未知 auth_type: 拒绝而非静默断连
self.send_auth_required()
return False
def _ip_allowed(self):
"""检查客户端 IP 是否在白名单内(白名单未启用时放行)"""
if not self.config.get('ip_whitelist_enabled', False):
return True
whitelist = self.config.get('ip_whitelist', []) or []
if not whitelist:
return True
try:
ip = self.client_address[0]
except Exception:
return False
import ipaddress
for pattern in whitelist:
try:
if '/' in str(pattern):
if ipaddress.ip_address(ip) in ipaddress.ip_network(str(pattern), strict=False):
return True
elif str(pattern) == ip:
return True
except ValueError:
continue
return False
def _check_basic_auth(self):
@@ -454,7 +489,9 @@ class MirrorServerHandler(BaseHTTPRequestHandler):
sys.stderr.flush()
# 调试模式输出请求详情 (debug-http)
if self._is_debug_enabled('http'):
msg = f"\n=== DEBUG GET Request ===\n Path: {self.path}\n Headers: {dict(self.headers)}"
safe_headers = {k: ('***' if k.lower() in ('authorization', 'x-api-key', 'cookie', 'token')
else v) for k, v in self.headers.items()}
msg = f"\n=== DEBUG GET Request ===\n Path: {self.path}\n Headers: {safe_headers}"
self._debug_log('http', msg, '\033[33m')
try:
@@ -724,7 +761,10 @@ class MirrorServerHandler(BaseHTTPRequestHandler):
<body>
<h1>{title}</h1>
'''
import html as _html
for item in items:
item['name'] = _html.escape(str(item['name']))
item['path'] = _html.escape(str(item['path']), quote=True)
if item['is_dir']:
html += f'''
<div class="item dir">
@@ -1504,19 +1544,18 @@ class MirrorServerHandler(BaseHTTPRequestHandler):
except Exception as e:
print(f"Error logging download to database: {e}")
# 回退到 JSON 文件(加锁防并发写坏/丢记录)
# 回退到 JSON 文件(读-改-写全程持同一把锁,防止并发丢记录)
with self._stats_lock:
history = self.load_download_history(1000)
entry = {
'timestamp': datetime.now().isoformat(),
'filepath': filepath,
'file_size': file_size,
'client_ip': client_ip,
'user_agent': user_agent,
'method': self.command if hasattr(self, 'command') else 'GET'
}
entry = {
'timestamp': datetime.now().isoformat(),
'filepath': filepath,
'file_size': file_size,
'client_ip': client_ip,
'user_agent': user_agent,
'method': self.command if hasattr(self, 'command') else 'GET'
}
with self._stats_lock:
history.append(entry)
self.save_download_history(history)