安全补强: 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:
+28
-14
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user