修复关键安全漏洞(Bug #19和Bug #13)
修复的Bug: - Bug #19: 路径遍历漏洞 - Bug #13: 浏览器上下文竞态条件 主要改进: 1. 路径遍历防护: - /screenshots/<filename> 端点添加is_safe_path()验证 - /static/<path:filename> 端点添加is_safe_path()验证 - 防止攻击者通过../等序列访问系统文件 2. 浏览器资源并发保护: - PlaywrightAutomation类添加_lock线程锁 - get_iframe_safe()方法使用锁保护main_page访问 - close()方法使用锁保护资源释放 - _cleanup_on_exit()使用非阻塞锁避免退出死锁 - 解决TOCTOU(Time-of-Check-Time-of-Use)竞态条件 影响: - 防止路径遍历攻击,保护系统文件安全 - 防止多线程环境下的浏览器资源竞争 - 提升系统安全性和稳定性 受影响文件: - app.py (路径验证) - playwright_automation.py (线程锁) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
81
app.py
81
app.py
@@ -98,11 +98,17 @@ user_accounts = {} # {user_id: {account_id: Account对象}}
|
||||
active_tasks = {} # {account_id: Thread对象}
|
||||
task_status = {} # {account_id: {"user_id": x, "username": y, "status": "排队中/运行中", "detail_status": "具体状态", "browse_type": z, "start_time": t, "source": s, "progress": {...}, "is_vip": bool}}
|
||||
|
||||
# 线程安全锁 - 修复Bug #12
|
||||
active_tasks_lock = threading.Lock() # 保护 active_tasks 字典
|
||||
task_status_lock = threading.Lock() # 保护 task_status 字典
|
||||
user_accounts_lock = threading.Lock() # 保护 user_accounts 字典
|
||||
|
||||
# VIP优先级队列
|
||||
vip_task_queue = [] # VIP用户任务队列
|
||||
normal_task_queue = [] # 普通用户任务队列
|
||||
task_queue_lock = threading.Lock()
|
||||
log_cache = {} # {user_id: [logs]} 每个用户独立的日志缓存
|
||||
log_cache_lock = threading.Lock() # 保护 log_cache 字典
|
||||
log_cache_total_count = 0 # 全局日志总数,防止无限增长
|
||||
|
||||
# 日志缓存限制
|
||||
@@ -214,6 +220,73 @@ class Account:
|
||||
return result
|
||||
|
||||
|
||||
# ========== 线程安全辅助函数 - 修复Bug #12 ==========
|
||||
|
||||
def safe_set_task(account_id, thread):
|
||||
"""线程安全地设置任务"""
|
||||
with active_tasks_lock:
|
||||
active_tasks[account_id] = thread
|
||||
|
||||
def safe_get_task(account_id):
|
||||
"""线程安全地获取任务"""
|
||||
with active_tasks_lock:
|
||||
return active_tasks.get(account_id)
|
||||
|
||||
def safe_remove_task(account_id):
|
||||
"""线程安全地移除任务"""
|
||||
with active_tasks_lock:
|
||||
return active_tasks.pop(account_id, None)
|
||||
|
||||
def safe_set_task_status(account_id, status_dict):
|
||||
"""线程安全地设置任务状态"""
|
||||
with task_status_lock:
|
||||
task_status[account_id] = status_dict
|
||||
|
||||
def safe_update_task_status(account_id, updates):
|
||||
"""线程安全地更新任务状态"""
|
||||
with task_status_lock:
|
||||
if account_id in task_status:
|
||||
task_status[account_id].update(updates)
|
||||
|
||||
def safe_get_task_status(account_id):
|
||||
"""线程安全地获取任务状态"""
|
||||
with task_status_lock:
|
||||
return task_status.get(account_id, {}).copy()
|
||||
|
||||
def safe_remove_task_status(account_id):
|
||||
"""线程安全地移除任务状态"""
|
||||
with task_status_lock:
|
||||
return task_status.pop(account_id, None)
|
||||
|
||||
def safe_get_all_task_status():
|
||||
"""线程安全地获取所有任务状态"""
|
||||
with task_status_lock:
|
||||
return {k: v.copy() for k, v in task_status.items()}
|
||||
|
||||
def safe_add_log(user_id, log_entry):
|
||||
"""线程安全地添加日志"""
|
||||
global log_cache_total_count
|
||||
with log_cache_lock:
|
||||
if user_id not in log_cache:
|
||||
log_cache[user_id] = []
|
||||
|
||||
# 限制每个用户的日志数量
|
||||
if len(log_cache[user_id]) >= MAX_LOGS_PER_USER:
|
||||
log_cache[user_id].pop(0)
|
||||
log_cache_total_count = max(0, log_cache_total_count - 1)
|
||||
|
||||
log_cache[user_id].append(log_entry)
|
||||
log_cache_total_count += 1
|
||||
|
||||
# 全局日志总数限制
|
||||
if log_cache_total_count > MAX_TOTAL_LOGS:
|
||||
# 删除最早用户的最早日志
|
||||
for uid in list(log_cache.keys()):
|
||||
if log_cache[uid]:
|
||||
log_cache[uid].pop(0)
|
||||
log_cache_total_count -= 1
|
||||
break
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
"""Flask-Login 用户加载"""
|
||||
@@ -2078,6 +2151,10 @@ def serve_screenshot(filename):
|
||||
if not filename.startswith(username_prefix + '_'):
|
||||
return jsonify({"error": "无权访问"}), 403
|
||||
|
||||
# 防止路径遍历攻击 (Bug #19 fix)
|
||||
if not is_safe_path(SCREENSHOTS_DIR, filename):
|
||||
return jsonify({"error": "非法路径"}), 403
|
||||
|
||||
return send_from_directory(SCREENSHOTS_DIR, filename)
|
||||
|
||||
|
||||
@@ -2177,6 +2254,10 @@ def handle_disconnect():
|
||||
@app.route('/static/<path:filename>')
|
||||
def serve_static(filename):
|
||||
"""提供静态文件访问"""
|
||||
# 防止路径遍历攻击 (Bug #19 fix)
|
||||
if not is_safe_path('static', filename):
|
||||
return jsonify({"error": "非法路径"}), 403
|
||||
|
||||
response = send_from_directory('static', filename)
|
||||
# 禁用缓存,强制浏览器每次都重新加载
|
||||
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
|
||||
|
||||
Reference in New Issue
Block a user