Files
MES/fix_db_password.php
2026-08-08 18:28:49 +08:00

193 lines
7.5 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);
}