安全补强: 6项P0/P1漏洞修复

P0-1: PHP配置注入防护 — install.py添加_escape_php_string()转义单引号和反斜杠
P0-2: WebSocket JWT校验 — _verify_ws_token()要求exp+sub字段
P1-1: 删除SHA256密码fallback — auth_service.py和auth.py直接import bcrypt
P1-3: LIKE通配符转义 — search.py添加_escape_like()并对所有ilike()加escape参数
P1-2: 安全响应头中间件 — main.py添加SecurityHeadersMiddleware注入4个安全头
P0-3: Refresh Token重用检测 — Admin模型添加token_version字段,token格式改为token:admin_id:version

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-05-22 22:16:50 +08:00
parent cea5730804
commit 8530f0e0d5
8 changed files with 313 additions and 77 deletions
+28 -14
View File
@@ -74,9 +74,20 @@ class CreateAdminRequest(BaseModel):
# ── Helpers ──
def _escape_php_string(s: str) -> str:
"""Escape a string for safe insertion into a PHP single-quoted string.
In PHP single-quoted strings, only ``\\`` and ``'`` are special escapes.
A user value containing ``'`` could break out of the define() and inject
arbitrary PHP code (RCE). This function neutralises that vector.
"""
return s.replace("\\", "\\\\").replace("'", "\\'")
def _make_db_url(req: InitDbRequest | CreateAdminRequest) -> str:
from urllib.parse import quote_plus
return (
f"mysql+aiomysql://{req.db_user}:{req.db_pass}"
f"mysql+aiomysql://{req.db_user}:{quote_plus(req.db_pass)}"
f"@{req.db_host}:{req.db_port}/{req.db_name}"
)
@@ -89,7 +100,7 @@ def _build_redis_url(req: InitDbRequest) -> str:
return url
def _write_env(req: InitDbRequest, secret_key: str, api_key: str,
def _write_env(req: InitDbRequest, secret_key: str, api_key: str, encryption_key: str,
pool_size: int, max_overflow: int, redis_url: str,
site_url: str) -> None:
install_dir = str(ROOT_DIR)
@@ -116,7 +127,7 @@ def _write_env(req: InitDbRequest, secret_key: str, api_key: str,
"# Security — PRODUCTION KEYS (immutable after install)",
f"NEXUS_SECRET_KEY={secret_key}",
f"NEXUS_API_KEY={api_key}",
"NEXUS_ENCRYPTION_KEY=",
f"NEXUS_ENCRYPTION_KEY={encryption_key}",
"",
"# Redis",
f"NEXUS_REDIS_URL={redis_url}",
@@ -157,19 +168,19 @@ def _write_config_php(req: InitDbRequest, api_key: str, site_url: str) -> None:
"// Nexus Configuration — Auto-generated by installer",
f"// {now}",
"",
f"define('API_BASE_URL', '{site_url}');",
f"define('API_KEY', '{api_key}');",
f"define('DB_HOST', '{req.db_host}');",
f"define('DB_PORT', '{req.db_port}');",
f"define('DB_NAME', '{req.db_name}');",
f"define('DB_USER', '{req.db_user}');",
f"define('DB_PASS', '{req.db_pass}');",
f"define('API_BASE_URL', '{_escape_php_string(site_url)}');",
f"define('API_KEY', '{_escape_php_string(api_key)}');",
f"define('DB_HOST', '{_escape_php_string(req.db_host)}');",
f"define('DB_PORT', '{_escape_php_string(req.db_port)}');",
f"define('DB_NAME', '{_escape_php_string(req.db_name)}');",
f"define('DB_USER', '{_escape_php_string(req.db_user)}');",
f"define('DB_PASS', '{_escape_php_string(req.db_pass)}');",
"define('APP_NAME', 'Nexus');",
"define('APP_VERSION', '6.0.0');",
"define('SESSION_LIFETIME', 28800);",
f"define('DEPLOY_ROOT', '{install_dir}');",
f"define('DEPLOY_ROOT', '{_escape_php_string(install_dir)}');",
"",
f"date_default_timezone_set('{req.timezone}');",
f"date_default_timezone_set('{_escape_php_string(req.timezone)}');",
"",
]
@@ -423,6 +434,9 @@ async def init_db(req: InitDbRequest):
# Generate keys
secret_key = secrets.token_hex(32)
api_key = secrets.token_hex(16)
# Generate Fernet-compatible encryption key (32 url-safe base64 bytes)
import base64, hashlib
encryption_key = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode()
# Insert settings
settings_kv = {
@@ -455,7 +469,7 @@ async def init_db(req: InitDbRequest):
# Write .env
site_url = req.site_url.rstrip("/") if req.site_url else f"http://127.0.0.1:{req.api_port}"
_write_env(req, secret_key, api_key, pool_size, max_overflow, redis_url, site_url)
_write_env(req, secret_key, api_key, encryption_key, pool_size, max_overflow, redis_url, site_url)
# Write config.php
_write_config_php(req, api_key, site_url)
@@ -550,7 +564,7 @@ async def create_admin(req: CreateAdminRequest):
content = CONFIG_PHP.read_text()
content = re.sub(
r"define\('APP_NAME', '.*?'\);",
f"define('APP_NAME', '{req.system_name}');",
f"define('APP_NAME', '{_escape_php_string(req.system_name)}');",
content,
)
CONFIG_PHP.write_text(content)