修复关键安全漏洞(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:
@@ -149,6 +149,7 @@ class PlaywrightAutomation:
|
||||
self.page: Optional[Page] = None
|
||||
self.main_page: Optional[Page] = None
|
||||
self._closed = False # 防止重复关闭
|
||||
self._lock = threading.Lock() # Bug #13 fix: 保护浏览器资源访问
|
||||
|
||||
# 注册退出清理函数,确保进程异常退出时也能关闭浏览器
|
||||
atexit.register(self._cleanup_on_exit)
|
||||
@@ -434,14 +435,16 @@ class PlaywrightAutomation:
|
||||
"""
|
||||
for attempt in range(max_retries if retry else 1):
|
||||
try:
|
||||
# 先检查main_page是否有效
|
||||
if not self.main_page:
|
||||
self.log("⚠ main_page无效")
|
||||
return None
|
||||
# Bug #13 fix: 使用锁保护main_page访问
|
||||
with self._lock:
|
||||
# 先检查main_page是否有效
|
||||
if not self.main_page:
|
||||
self.log("⚠ main_page无效")
|
||||
return None
|
||||
|
||||
iframe = self.main_page.frame('mainframe')
|
||||
if iframe:
|
||||
return iframe
|
||||
iframe = self.main_page.frame('mainframe')
|
||||
if iframe:
|
||||
return iframe
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if self.is_context_error(error_msg):
|
||||
@@ -1327,49 +1330,51 @@ class PlaywrightAutomation:
|
||||
|
||||
def close(self):
|
||||
"""完全关闭浏览器进程(每个账号独立)并确保资源释放"""
|
||||
# 防止重复关闭
|
||||
if self._closed:
|
||||
return
|
||||
self._closed = True
|
||||
# Bug #13 fix: 使用锁保护close操作
|
||||
with self._lock:
|
||||
# 防止重复关闭
|
||||
if self._closed:
|
||||
return
|
||||
self._closed = True
|
||||
|
||||
errors = []
|
||||
errors = []
|
||||
|
||||
# 第一步:关闭上下文
|
||||
if self.context:
|
||||
try:
|
||||
self.context.close()
|
||||
# self.log("上下文已关闭") # 精简日志
|
||||
except Exception as e:
|
||||
error_msg = f"关闭上下文时出错: {str(e)}"
|
||||
self.log(error_msg)
|
||||
errors.append(error_msg)
|
||||
# 第一步:关闭上下文
|
||||
if self.context:
|
||||
try:
|
||||
self.context.close()
|
||||
# self.log("上下文已关闭") # 精简日志
|
||||
except Exception as e:
|
||||
error_msg = f"关闭上下文时出错: {str(e)}"
|
||||
self.log(error_msg)
|
||||
errors.append(error_msg)
|
||||
|
||||
# 第二步:关闭浏览器进程
|
||||
if self.browser:
|
||||
try:
|
||||
self.browser.close()
|
||||
# self.log("浏览器进程已关闭") # 精简日志
|
||||
except Exception as e:
|
||||
error_msg = f"关闭浏览器时出错: {str(e)}"
|
||||
self.log(error_msg)
|
||||
errors.append(error_msg)
|
||||
# 第二步:关闭浏览器进程
|
||||
if self.browser:
|
||||
try:
|
||||
self.browser.close()
|
||||
# self.log("浏览器进程已关闭") # 精简日志
|
||||
except Exception as e:
|
||||
error_msg = f"关闭浏览器时出错: {str(e)}"
|
||||
self.log(error_msg)
|
||||
errors.append(error_msg)
|
||||
|
||||
# 第三步:停止Playwright
|
||||
if self.playwright:
|
||||
try:
|
||||
self.playwright.stop()
|
||||
# self.log("Playwright已停止") # 精简日志
|
||||
except Exception as e:
|
||||
error_msg = f"停止Playwright时出错: {str(e)}"
|
||||
self.log(error_msg)
|
||||
errors.append(error_msg)
|
||||
# 第三步:停止Playwright
|
||||
if self.playwright:
|
||||
try:
|
||||
self.playwright.stop()
|
||||
# self.log("Playwright已停止") # 精简日志
|
||||
except Exception as e:
|
||||
error_msg = f"停止Playwright时出错: {str(e)}"
|
||||
self.log(error_msg)
|
||||
errors.append(error_msg)
|
||||
|
||||
# 第四步:清空引用,确保垃圾回收
|
||||
self.context = None
|
||||
self.page = None
|
||||
self.main_page = None
|
||||
self.browser = None
|
||||
self.playwright = None
|
||||
# 第四步:清空引用,确保垃圾回收
|
||||
self.context = None
|
||||
self.page = None
|
||||
self.main_page = None
|
||||
self.browser = None
|
||||
self.playwright = None
|
||||
|
||||
# 第五步:强制等待,确保进程完全退出
|
||||
time.sleep(0.5)
|
||||
@@ -1383,18 +1388,24 @@ class PlaywrightAutomation:
|
||||
|
||||
def _cleanup_on_exit(self):
|
||||
"""进程退出时的清理函数(由atexit调用)"""
|
||||
if not self._closed:
|
||||
try:
|
||||
# 静默关闭,避免在退出时产生过多日志
|
||||
if self.context:
|
||||
self.context.close()
|
||||
if self.browser:
|
||||
self.browser.close()
|
||||
if self.playwright:
|
||||
self.playwright.stop()
|
||||
self._closed = True
|
||||
except:
|
||||
pass # 退出时忽略所有错误
|
||||
# Bug #13 fix: 尝试获取锁,但不阻塞(避免退出时死锁)
|
||||
acquired = self._lock.acquire(blocking=False)
|
||||
try:
|
||||
if not self._closed:
|
||||
try:
|
||||
# 静默关闭,避免在退出时产生过多日志
|
||||
if self.context:
|
||||
self.context.close()
|
||||
if self.browser:
|
||||
self.browser.close()
|
||||
if self.playwright:
|
||||
self.playwright.stop()
|
||||
self._closed = True
|
||||
except:
|
||||
pass # 退出时忽略所有错误
|
||||
finally:
|
||||
if acquired:
|
||||
self._lock.release()
|
||||
|
||||
def __enter__(self):
|
||||
"""Context manager支持 - 进入"""
|
||||
|
||||
Reference in New Issue
Block a user