初始化
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
/**
|
||||
* 一次性弱密码重置脚本(P1:管理员弱密码 123456)
|
||||
*
|
||||
* 用法(在服务器上通过 CLI 执行,不要在浏览器访问):
|
||||
* php fix_db_password.php # 自动探测员工表
|
||||
* php fix_db_password.php --table=jp_employee # 手动指定表名
|
||||
*
|
||||
* 作用:扫描员工表中仍在使用常见弱密码(如 123456)的账号,
|
||||
* 将其重置为自动生成的强随机密码(≥16 位,含大小写+数字+特殊字符),
|
||||
* 并打印新密码,请妥善保存并告知对应员工。
|
||||
*
|
||||
* 注意:本文件已被 .htaccess 禁止通过 Web 访问,仅可 CLI 执行。
|
||||
* 执行完成后建议直接删除本文件。
|
||||
*/
|
||||
|
||||
// 加载配置(与 index.php 相同的 .env 读取逻辑)
|
||||
$envFile = __DIR__ . '/.env';
|
||||
if (file_exists($envFile)) {
|
||||
foreach (file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#' || strpos($line, '=') === false) continue;
|
||||
list($k, $v) = explode('=', $line, 2);
|
||||
if (!array_key_exists(trim($k), $_ENV)) $_ENV[trim($k)] = trim($v);
|
||||
}
|
||||
}
|
||||
|
||||
define('DB_HOST', $_ENV['DB_HOST'] ?? 'localhost');
|
||||
define('DB_NAME', $_ENV['DB_NAME'] ?? 'mes');
|
||||
define('DB_USER', $_ENV['DB_USER'] ?? 'root');
|
||||
define('DB_PASS', $_ENV['DB_PASS'] ?? '');
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4', DB_HOST, DB_NAME),
|
||||
DB_USER, DB_PASS,
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
exit("数据库连接失败:{$e->getMessage()}\n");
|
||||
}
|
||||
|
||||
// 解析命令行参数(--table=xxx)
|
||||
$forcedTable = '';
|
||||
foreach ($argv as $a) {
|
||||
if (strpos($a, '--table=') === 0) {
|
||||
$forcedTable = substr($a, strlen('--table='));
|
||||
}
|
||||
}
|
||||
|
||||
// 常见弱密码字典
|
||||
$weakList = ['123456','12345678','123456789','password','admin123','qwerty','111111','abc123','123123','root'];
|
||||
|
||||
// ---- 自动探测员工表与字段 ----
|
||||
function getAllTables($pdo)
|
||||
{
|
||||
return $pdo->query("SHOW TABLES")->fetchAll(PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
function getColumns($pdo, $table)
|
||||
{
|
||||
return $pdo->query("SHOW COLUMNS FROM `{$table}`")->fetchAll(PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
// 从字段列表中挑选首个命中的字段名
|
||||
function pickField(array $cols, array $candidates)
|
||||
{
|
||||
foreach ($candidates as $c) {
|
||||
if (in_array($c, $cols, true)) return $c;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function chooseEmployeeTable($pdo, $forced)
|
||||
{
|
||||
$tables = getAllTables($pdo);
|
||||
if ($forced !== '') {
|
||||
if (!in_array($forced, $tables, true)) {
|
||||
exit("指定的表 `{$forced}` 不存在。可用表:\n " . implode("\n ", $tables) . "\n");
|
||||
}
|
||||
return $forced;
|
||||
}
|
||||
// 候选评分:优先名称含 employee,其次含 user/admin/staff,且字段需含密码字段
|
||||
$scored = [];
|
||||
foreach ($tables as $t) {
|
||||
$cols = getColumns($pdo, $t);
|
||||
$pwdField = pickField($cols, ['password','pwd','passwd','user_pass','user_password','hash','pass']);
|
||||
if ($pwdField === '') continue; // 无密码字段,不是用户表
|
||||
$score = 0;
|
||||
if (stripos($t, 'employee') !== false) $score += 100;
|
||||
if (stripos($t, 'admin') !== false) $score += 60;
|
||||
if (stripos($t, 'user') !== false) $score += 50;
|
||||
if (stripos($t, 'staff') !== false) $score += 40;
|
||||
if (stripos($t, 'member') !== false) $score += 30;
|
||||
if (stripos($t, 'jp_') === 0) $score += 10; // 业务前缀表
|
||||
if ($score > 0) {
|
||||
$scored[] = ['table' => $t, 'score' => $score, 'cols' => $cols];
|
||||
}
|
||||
}
|
||||
if (empty($scored)) {
|
||||
exit("未在数据库中找到含密码字段的用户表。所有表:\n " . implode("\n ", $tables) . "\n请使用 --table=表名 手动指定。\n");
|
||||
}
|
||||
// 分数最高者唯一则直接用;否则列出候选请用户指定
|
||||
usort($scored, fn($a, $b) => $b['score'] <=> $a['score']);
|
||||
$top = $scored[0]['score'];
|
||||
$topCandidates = array_values(array_filter($scored, fn($x) => $x['score'] === $top));
|
||||
if (count($topCandidates) === 1) {
|
||||
return $topCandidates[0]['table'];
|
||||
}
|
||||
echo "检测到多个候选用户表,请使用 --table= 指定其中一个:\n";
|
||||
foreach ($scored as $c) {
|
||||
echo " {$c['table']} (score={$c['score']})\n";
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
$table = chooseEmployeeTable($pdo, $forcedTable);
|
||||
$cols = getColumns($pdo, $table);
|
||||
|
||||
// 字段映射(自适应不同命名)
|
||||
$idField = pickField($cols, ['id']) ?: 'id';
|
||||
$pwdField = pickField($cols, ['password','pwd','passwd','user_pass','user_password','hash','pass']);
|
||||
$empNoField = pickField($cols, ['emp_no','username','user_no','job_no','work_no','account','login_name','user_name','login_id']);
|
||||
$nameField = pickField($cols, ['emp_name','name','user_name','real_name','nickname','truename']);
|
||||
$roleField = pickField($cols, ['role','position','job_title','type','user_type']);
|
||||
|
||||
if ($pwdField === '') {
|
||||
exit("表 `{$table}` 未找到密码字段,已中止以避免误操作。\n");
|
||||
}
|
||||
|
||||
echo "使用员工表:`{$table}`\n";
|
||||
echo "字段映射:id={$idField}, 密码={$pwdField}, 工号=" . ($empNoField ?: '(无)') . ", 姓名=" . ($nameField ?: '(无)') . ", 角色=" . ($roleField ?: '(无)') . "\n\n";
|
||||
|
||||
// 构造 SELECT(仅选存在的字段)
|
||||
$selectCols = array_filter([$idField, $empNoField, $nameField, $pwdField, $roleField]);
|
||||
$sql = "SELECT " . implode(', ', array_map(fn($c) => "`{$c}`", $selectCols)) . " FROM `{$table}`";
|
||||
$rows = $pdo->query($sql)->fetchAll();
|
||||
|
||||
$changed = [];
|
||||
foreach ($rows as $r) {
|
||||
$hash = $r[$pwdField] ?? '';
|
||||
if (!is_string($hash) || $hash === '') continue;
|
||||
$isWeak = false;
|
||||
foreach ($weakList as $w) {
|
||||
if (password_verify($w, $hash)) { $isWeak = true; break; }
|
||||
}
|
||||
if (!$isWeak) continue;
|
||||
|
||||
$newPwd = generateStrongPassword(16);
|
||||
$hashNew = password_hash($newPwd, PASSWORD_BCRYPT, ['cost' => 12]);
|
||||
$pdo->prepare("UPDATE `{$table}` SET `{$pwdField}` = :h WHERE `{$idField}` = :id")
|
||||
->execute([':h' => $hashNew, ':id' => $r[$idField]]);
|
||||
$changed[] = [
|
||||
'emp_no' => $empNoField ? ($r[$empNoField] ?? '') : $r[$idField],
|
||||
'emp_name' => $nameField ? ($r[$nameField] ?? '') : '',
|
||||
'new' => $newPwd,
|
||||
];
|
||||
}
|
||||
|
||||
if ($changed) {
|
||||
echo "已将以下使用弱密码的账号重置为强密码(请妥善保存):\n";
|
||||
foreach ($changed as $c) {
|
||||
echo " emp_no={$c['emp_no']} name={$c['emp_name']} NEW_PASSWORD={$c['new']}\n";
|
||||
}
|
||||
echo "\n等效 SQL(如需在 phpMyAdmin 手动执行):\n";
|
||||
foreach ($changed as $c) {
|
||||
$h = password_hash($c['new'], PASSWORD_BCRYPT, ['cost' => 12]);
|
||||
$ident = $empNoField ? "{$empNoField} = '{$c['emp_no']}'" : "{$idField} = '{$c['emp_no']}'";
|
||||
echo "UPDATE `{$table}` SET `{$pwdField}` = '$h' WHERE {$ident};\n";
|
||||
}
|
||||
} else {
|
||||
echo "未发现使用弱密码的账号,无需处理。\n";
|
||||
}
|
||||
|
||||
function generateStrongPassword($len = 16)
|
||||
{
|
||||
$upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
|
||||
$lower = 'abcdefghijkmnpqrstuvwxyz';
|
||||
$digit = '23456789';
|
||||
$special = '!@#$%^&*';
|
||||
$all = $upper . $lower . $digit . $special;
|
||||
$pwd = $upper[random_int(0, strlen($upper) - 1)]
|
||||
. $lower[random_int(0, strlen($lower) - 1)]
|
||||
. $digit[random_int(0, strlen($digit) - 1)]
|
||||
. $special[random_int(0, strlen($special) - 1)];
|
||||
for ($i = strlen($pwd); $i < $len; $i++) {
|
||||
$pwd .= $all[random_int(0, strlen($all) - 1)];
|
||||
}
|
||||
$arr = str_split($pwd);
|
||||
shuffle($arr);
|
||||
return implode('', $arr);
|
||||
}
|
||||
Reference in New Issue
Block a user