security: P3+ — 面板密码哈希 + 上传类型白名单 + CSRF Token
面板密码安全: - 支持哈希存储 (password_hash/password_salt) - 登录使用 secrets.compare_digest 时序安全比较 - 回退明文兼容旧配置 - 默认密码 admin 时打印 CRITICAL 警告 文件上传安全: - 拒绝危险扩展名: .exe/.dll/.so/.sh/.bat/.ps1 等 - 拒绝敏感文件名: .htaccess/Makefile/Dockerfile 等 - 上传时检查并返回 403 CSRF 防护: - panel_auth(csrf_protect=True) 参数 - 写操作需 X-CSRF-Token header 匹配 session token - 保护范围: 文件删除/写入/创建/上传/重命名 + 项目启停 + 代理管理 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,16 @@ _ALLOWED_ROOTS = _filtered or [Path("/") if os.name != 'nt' else Path("C:\\")]
|
||||
|
||||
MAX_READ_SIZE = 1 * 1024 * 1024 # 1 MB for text read
|
||||
MAX_UPLOAD_SIZE = 50 * 1024 * 1024 # 50 MB per upload
|
||||
|
||||
# 危险文件扩展名 — 禁止上传
|
||||
_DENY_EXTENSIONS = {
|
||||
'.exe', '.dll', '.so', '.sh', '.bash', '.zsh', '.fish',
|
||||
'.bat', '.cmd', '.ps1', '.vbs', '.vba', '.wsf', '.msi',
|
||||
'.pyc', '.pyo', '.class', '.jar', '.war',
|
||||
'.php', '.jsp', '.asp', '.aspx', '.cgi', '.pl',
|
||||
'.deb', '.rpm', '.apk', '.ipa',
|
||||
}
|
||||
_DENY_NAMES = {'.htaccess', 'Makefile', 'Dockerfile', '.bashrc', '.profile'}
|
||||
TEXT_EXTENSIONS = {
|
||||
'.txt','.py','.js','.ts','.html','.css','.json','.yaml','.yml',
|
||||
'.md','.ini','.cfg','.conf','.log','.sh','.bat','.env','.xml',
|
||||
@@ -316,6 +326,13 @@ def setup_file_routes(app, service_manager, prefix=''):
|
||||
continue
|
||||
# Sanitize filename
|
||||
fname = Path(fname).name
|
||||
# 安全检查: 拒绝危险文件类型
|
||||
ext = Path(fname).suffix.lower()
|
||||
if ext in _DENY_EXTENSIONS or fname in _DENY_NAMES:
|
||||
return web.json_response(
|
||||
{"error": f"禁止上传的文件类型: {ext or fname}"},
|
||||
status=403,
|
||||
)
|
||||
dest = target_dir / fname
|
||||
size = 0
|
||||
with open(dest, 'wb') as f:
|
||||
@@ -423,13 +440,13 @@ def setup_file_routes(app, service_manager, prefix=''):
|
||||
|
||||
# ── Register routes ──
|
||||
app.router.add_get(f'{prefix}/api/files/list', panel_auth(list_dir))
|
||||
app.router.add_post(f'{prefix}/api/files/mkdir', panel_auth(mkdir))
|
||||
app.router.add_post(f'{prefix}/api/files/touch', panel_auth(touch))
|
||||
app.router.add_post(f'{prefix}/api/files/delete', panel_auth(delete))
|
||||
app.router.add_post(f'{prefix}/api/files/rename', panel_auth(rename))
|
||||
app.router.add_post(f'{prefix}/api/files/upload', panel_auth(upload))
|
||||
app.router.add_post(f'{prefix}/api/files/mkdir', panel_auth(mkdir, csrf_protect=True))
|
||||
app.router.add_post(f'{prefix}/api/files/touch', panel_auth(touch, csrf_protect=True))
|
||||
app.router.add_post(f'{prefix}/api/files/delete', panel_auth(delete, csrf_protect=True))
|
||||
app.router.add_post(f'{prefix}/api/files/rename', panel_auth(rename, csrf_protect=True))
|
||||
app.router.add_post(f'{prefix}/api/files/upload', panel_auth(upload, csrf_protect=True))
|
||||
app.router.add_get(f'{prefix}/api/files/download', panel_auth(download))
|
||||
app.router.add_get(f'{prefix}/api/files/read', panel_auth(read_file))
|
||||
app.router.add_post(f'{prefix}/api/files/write', panel_auth(write_file))
|
||||
app.router.add_post(f'{prefix}/api/files/write', panel_auth(write_file, csrf_protect=True))
|
||||
app.router.add_get(f'{prefix}/api/files/info', panel_auth(file_info))
|
||||
app.router.add_get(f'{prefix}/api/files/picker', panel_auth(picker_api))
|
||||
|
||||
Reference in New Issue
Block a user