193 lines
7.5 KiB
PHP
193 lines
7.5 KiB
PHP
<?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);
|
||
}
|