修复全新部署首跑顺序:装库先于2FA引导;install表单补令牌透传

- security.php 新增 secInstallPending():install.php 在场且无 install.lock = 装库未完成
- 引导模式下所有入口先 302 到 install.php,装完自删后才轮到 admin_2fa_setup.php
- admin_login/admin_2fa_setup 兜住本机直连与已引导未装库两种入口(secTokenUrl 带令牌)
- install.php 表单隐藏 token 字段,已引导站点 POST 也能过第三层门禁
This commit is contained in:
ypsdf1
2026-10-09 18:56:46 +08:00
parent e1010927e5
commit fc8c82b188
4 changed files with 41 additions and 0 deletions
+9
View File
@@ -17,6 +17,15 @@ secEnsureSession();
secGatePage(true);
$boot = secBootSessionActive();
// ★ 首跑顺序(2026-10-09):装库先于安全配置 —— install.php 还没跑完(文件在场、
// 无 install.lock)时,本页先让位给数据库安装页;装完它自删,再回来配 2FA/白名单/令牌。
// 引导模式下 secGatePage 已先行 302,这里兜住「已引导 / 本机直连」两种入口;
// 已引导站点带 ?token= 过 install.php 的第三层门禁(令牌为空时 secTokenUrl 原样返回)。
if (secInstallPending()) {
header('Location: ' . secTokenUrl('install.php'));
exit;
}
// ★ 首次部署:管理密码还是分发占位符 → 走初始化模式。
// 旧行为在这里直接跳登录页,是个死胡同:登录要密码,而这时候根本没有可用密码。
$needInit = !secAdminPassConfigured();
+8
View File
@@ -2,6 +2,14 @@
// ★ 紧急加固(2026-09-29):第二层 IP 白名单 —— 登录页本身也只对白名单 IP 开放
require_once __DIR__ . '/security.php';
secGatePage();
// ★ 首跑顺序(2026-10-09):装库先于安全配置 —— install.php 还没跑完就先去装库。
// 引导模式下 secGatePage 已先行 302;这里兜住的是「本机 127.0.0.1 直连」与
// 「已引导但装库没做过」两种入口。secTokenUrl 在令牌未配置时原样返回
// install.php,配了令牌则带上 ?token= 过第三层门禁(与 admin.php 同款拼法)。
if (secInstallPending()) {
header('Location: ' . secTokenUrl('install.php'));
exit;
}
// ★ 首次部署:管理密码还是分发占位符 → 登录页是个死胡同(要密码,可密码还不存在)。
// 改投初始化页,让部署者先把自己的第一个密码设出来。
if (!secAdminPassConfigured()) {
+5
View File
@@ -465,6 +465,11 @@ ul{margin:6px 0;padding-left:20px}
<form method="post" action="install.php" autocomplete="off">
<input type="hidden" name="act" value="save">
<input type="hidden" name="csrf" value="<?php echo htmlspecialchars($csrf, ENT_QUOTES, 'UTF-8'); ?>">
<?php if (secToken() !== ''): ?>
<!-- 已引导站点(config 有 SEC_ACCESS_TOKEN):POST 也要过第三层令牌门禁;
本页只会渲染给已经过了门禁的访问者,令牌不出圈。全新站点为空 → 不输出。 -->
<input type="hidden" name="token" value="<?php echo htmlspecialchars(secToken(), ENT_QUOTES, 'UTF-8'); ?>">
<?php endif; ?>
<h2>运行模式</h2>
<div class="modes">
+19
View File
@@ -588,6 +588,16 @@ function secBootRecoveryRedirect() {
exit;
}
/**
* 数据库是否还没装完(2026-10-09「首跑顺序」判据)。
* 判据 = 自毁式安装页还在场上:install.php 存在 且 install.lock 不存在。
* 装完后 install.php 自删 + lock 落地 → 恒为 false;
* 线上老站根本没有 install.php → 同样恒为 false,行为零变化。
*/
function secInstallPending() {
return is_file(__DIR__ . '/install.php') && !is_file(__DIR__ . '/install.lock');
}
/**
* 页面级闸门。$allowBoot=true 时接受 boot 令牌解锁(引导配置页 / 2FA 页使用)。
* - 已引导 且 不在 IP 白名单 → nginx 原生 404(不是 403:403 会告诉探测者
@@ -608,6 +618,15 @@ function secGatePage($allowBoot = false) {
if (!secIsBootstrapped()) {
secLog('ip_gate_open_bootstrap', 'path=' . (isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '-'));
$script = basename(isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : '');
// ★ 首跑顺序(2026-10-09):装库先于安全配置 —— install.php 还没跑完
// (文件在场、无 install.lock)时,任何引导入口一律先 302 到自毁式安装页;
// 装完它自删 + lock 落地,下一次进来才轮到 admin_2fa_setup.php。
// 修复实录:全新测试站直接进了 2FA 引导页,数据库安装页根本没露面。
if ($script !== 'install.php' && secInstallPending()) {
secLog('route_install_first', 'from=' . $script);
header('Location: install.php');
exit;
}
if ($script === 'admin.php') {
header('Location: admin_2fa_setup.php');
exit;