加固-06(P0-05): 密码全部以 bcrypt 哈希入库,旧格式(明文/MD5)兼容并惰性迁移

背景:sq_user/sq_agent 密码明文入库、sq_admin 存 MD5;订单表 sq_trade.pass 明文在途;
找回密码 API 会把明文密码直接发邮件;日志记录明文密码。数据库泄露 = 全部账号密码泄露。

1. function/function_core.php 新增 3 个 helper(updateset 之后):
   - wq_is_hashed_password($v):判断存储值是否为新 bcrypt 哈希(60 长度 $2y$ 前缀)
   - wq_hash_password($plain):password_hash(PASSWORD_BCRYPT)
   - wq_verify_password($plain,$stored,$legacy):兼容 新 bcrypt / 旧明文(sq_user/sq_agent) / 旧 md5(sq_admin),
     哈希比对一律 hash_equals,恒定时间比较防时序侧信道

2. 所有落库点改为 bcrypt:
   - 用户开户漏斗 function/function_auth.php auth_add 入口幂等补哈希(上游已哈希则原样保留)
   - 代理开通漏斗 function/trade.inc.php type=4 代理落库前幂等补哈希
   - 下单点订单密码哈希入库:ajax.php(320/388/781)、agent/ajax.php(439) —— 订单表不再有明文在途密码
   - admin/ajax.php:addagent、addadmin、changeagent(mod=password)、changepassword 新密码
   - agent/ajax.php:开下级代理、changeuser(mod=password)、changeagent(mod=password)、changepassword 新密码
   - api.php:ChangePassword 写库
   - api/Communication.php 与 function/communication.php:用户登录校验成功后,旧明文惰性升级为 bcrypt

3. 所有校验点改为取行 + PHP 校验(不再 WHERE password=明文):
   - admin 登录(旧 MD5 兼容,成功即写回 bcrypt,session 与 DB 存同一新哈希保住 re-check 机制)
   - agent 密码登录(旧明文兼容,惰性迁移);agent token 登录 session 改存 DB 存储值
   - 根 ajax.php 续费 kt 密码校验(379/773)、卡密找回密码(1002)
   - api.php GetUserInfo 内联校验、getuserinfo() 校验(均带惰性迁移)

4. 找回密码改造(api.php RetrievePassword):
   密码已不可逆,原"邮件下发原密码"改为"重置为一次性随机新密码(bin2hex(random_bytes(6)))+ 哈希落库 + 仅邮件投递一次"

5. 日志/回传去明文:
   - admin 登录失败日志不再记录明文密码(改记账号)
   - admin getagentinfo 不再回传密码值(掩码 ********,经确认 UI 无回写风险)

6. 表结构:sq_admin.password varchar(33) -> varchar(255)(bcrypt 60 字符装不下)
   - wenquan_auth.sql 同步更新(新装库直接可用)
   - 已有库需执行:ALTER TABLE sq_admin MODIFY password varchar(255) NOT NULL;(本次本地库已执行)

回归(40 项全 PASS,8082 内置服务器 + 8081 nginx 双通道冒烟):
- API(c=api) 旧明文用户登录成功即升级 bcrypt;再登录幂等不错哈希;错密码 -205 被拒
- admin 旧 MD5 登录即迁移 bcrypt;再登录/错密码/日志无明文/checklogin 会话重校验均通过
- admin 新增管理员、改密(旧密码失效/新密码生效/恢复基线)
- admin 新增代理直接 bcrypt;getagentinfo 掩码;changeagent 改密哈希入库
- 旧明文代理登录即迁移 bcrypt;checklogin 通过;代理改密旧失效新生效
- admin 手工开通(funnel)明文 POST 直接存 bcrypt;API 登录新开通用户成功
- 注:测试应用 free 必须为 0(free=1 时 Login 入口直接放行,属产品设计,不走密码校验)

已知行为变化(记录):用户/代理列表按"密码"关键词搜索不再命中(存储已哈希,搜索本身即泄露向量,收紧);
列表中密码列显示 60 位哈希串而非明文;改密会使该账号现有会话失效需重新登录(与旧行为一致)。

api.php 为 2024 树中的旧入口(已被 api/Communication.php 取代),仍按同口径同步修了 4 处密码点防复活踩坑。
This commit is contained in:
youpaishidifu
2026-10-10 12:25:52 +08:00
parent 7d4ed3e7cd
commit 24264cf320
10 changed files with 144 additions and 33 deletions
+22 -10
View File
@@ -34,17 +34,20 @@ if ($_GET['mod'] === 'login'){
if ($verification !== true) { if ($verification !== true) {
die(json_encode(array('code' => '-88', 'msg' => '请先进行人机验证'))); die(json_encode(array('code' => '-88', 'msg' => '请先进行人机验证')));
} }
if (!$result = $db->select_first_row('sq_admin','*',array('username'=>$_POST['username'],'password'=>md5($_POST['password'])),'AND')){ // P0-05: 按用户名取出后用 PHP 校验(兼容新 bcrypt / 旧 md5),成功即写回 bcrypt(惰性迁移);
//$db->insert_back_id('sq_log_system',array('time'=>time(),'ip'=>get_real_ip(),'msg'=>'管理员 '.$_POST['username'].'使用密码'.$_POST['password'].'登陆失败:密码错误或账号不存在','type'=>'danger')); // 失败日志不再记录明文密码(原 posterror 会把密码写入 sq_log_system)
$db->posterror('管理员登录尝试失败,账号['.$_POST['username'].']密码['.$_POST['password'].']','success'); $result = $db->select_first_row('sq_admin','*',array('username'=>$_POST['username']),'AND');
if (!$result || !wq_verify_password($_POST['password'], $result['password'], 'md5')){
$db->posterror('管理员登录尝试失败,账号['.$_POST['username'].']','danger');
die(json_encode(array('code' => -3, 'msg' => '输入的账号密码错误!'.$db->geterror()))); die(json_encode(array('code' => -3, 'msg' => '输入的账号密码错误!'.$db->geterror())));
}else{ }else{
$_SESSION['admin_username'] = $_POST['username']; $_SESSION['admin_username'] = $_POST['username'];
$_SESSION['admin_id'] = $result['ID']; $_SESSION['admin_id'] = $result['ID'];
$_SESSION['admin_qq'] = $result['qq']; $_SESSION['admin_qq'] = $result['qq'];
$_SESSION['admin_password'] = md5($_POST['password']); $newhash = wq_hash_password($_POST['password']);
$_SESSION['admin_password'] = $newhash;
$_SESSION['admin_HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT']; $_SESSION['admin_HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'];
$db->update('sq_admin',array('username'=>$_SESSION['admin_username']),'AND',array('loginip'=>get_real_ip(),'logintime'=>time())); $db->update('sq_admin',array('ID'=>$result['ID']),'AND',array('password'=>$newhash,'loginip'=>get_real_ip(),'logintime'=>time()));
// $db->insert_back_id('sq_log_system',array('time'=>time(),'ip'=>get_real_ip(),'msg'=>'['.get_real_ip().']管理员 '.$_POST['username'].'使用密码登陆成功!','type'=>'success')); // $db->insert_back_id('sq_log_system',array('time'=>time(),'ip'=>get_real_ip(),'msg'=>'['.get_real_ip().']管理员 '.$_POST['username'].'使用密码登陆成功!','type'=>'success'));
$db->posterror('管理员 '.$_POST['username'].'使用密码登录后台成功','success'); $db->posterror('管理员 '.$_POST['username'].'使用密码登录后台成功','success');
die(json_encode(array('code' => '1', 'msg' => '登陆成功,正在跳转!'))); die(json_encode(array('code' => '1', 'msg' => '登陆成功,正在跳转!')));
@@ -491,6 +494,11 @@ switch ($_GET['mod']){
} }
// P0-05: 用户密码改密以 bcrypt 哈希入库
if ($_POST['mod'] == 'password') {
$_POST['value'] = wq_hash_password($_POST['value']);
}
if (!$db->update('sq_user',array('ID'=>$_POST['userid']),'AND',array($_POST['mod']=>$_POST['value']))){ if (!$db->update('sq_user',array('ID'=>$_POST['userid']),'AND',array($_POST['mod']=>$_POST['value']))){
die('修改失败'.$db->geterror()); die('修改失败'.$db->geterror());
}else{ }else{
@@ -624,7 +632,7 @@ switch ($_GET['mod']){
if ($db->select_first_row('sq_agent','*',array('username'=>$_POST['agent_name']),'AND') != false){ if ($db->select_first_row('sq_agent','*',array('username'=>$_POST['agent_name']),'AND') != false){
die('该代理账号已经存在,请尝试其他用户名'); die('该代理账号已经存在,请尝试其他用户名');
} }
if(!$db->insert_back_id('sq_agent',array('username'=>$_POST['agent_name'],'password'=>$_POST['agent_pass'],'money'=>$_POST['agent_money'],'levelid'=>$_POST['agent_leval'],'begintime'=>time(),'status'=>'1','loginip'=>'-','qq'=>$_POST['agent_qq']))){ if(!$db->insert_back_id('sq_agent',array('username'=>$_POST['agent_name'],'password'=>wq_hash_password($_POST['agent_pass']),'money'=>$_POST['agent_money'],'levelid'=>$_POST['agent_leval'],'begintime'=>time(),'status'=>'1','loginip'=>'-','qq'=>$_POST['agent_qq']))){
die('添加失败:'.$db->geterror()); die('添加失败:'.$db->geterror());
}else{ }else{
die('代理添加成功!'); die('代理添加成功!');
@@ -674,11 +682,12 @@ switch ($_GET['mod']){
break; break;
case 'getagentinfo': case 'getagentinfo':
$result = $db->select_first_row('sq_agent','*',array('ID'=>$_POST['id']),'AND'); $result = $db->select_first_row('sq_agent','*',array('ID'=>$_POST['id']),'AND');
// P0-05: 不再把密码(哈希或明文)回传前端;密码修改走 changeagent 单独入口
die(json_encode(array( die(json_encode(array(
'agent_leval'=>$result['levelid'], 'agent_leval'=>$result['levelid'],
'agent_money'=>$result['money'], 'agent_money'=>$result['money'],
'agent_name'=>$result['username'], 'agent_name'=>$result['username'],
'agent_pass'=>$result['password'] 'agent_pass'=>'********'
))); )));
break; break;
@@ -695,6 +704,8 @@ switch ($_GET['mod']){
die('代理ID不能为空'); die('代理ID不能为空');
} }
if ($_POST['mod'] == 'status') $_POST['value'] = textbooltonum($_POST['value']); if ($_POST['mod'] == 'status') $_POST['value'] = textbooltonum($_POST['value']);
// P0-05: 代理密码统一以 bcrypt 哈希入库
if ($_POST['mod'] == 'password') $_POST['value'] = wq_hash_password($_POST['value']);
if (!$db->update('sq_agent',array('ID'=>$_POST['aid']),'AND',array($_POST['mod']=>$_POST['value']))){ if (!$db->update('sq_agent',array('ID'=>$_POST['aid']),'AND',array($_POST['mod']=>$_POST['value']))){
die('修改失败'.$db->geterror()); die('修改失败'.$db->geterror());
}else{ }else{
@@ -762,7 +773,7 @@ switch ($_GET['mod']){
if (empty($_POST['addusername']) || empty($_POST['adduserpass']) || empty($_POST['adduserqq'])){ if (empty($_POST['addusername']) || empty($_POST['adduserpass']) || empty($_POST['adduserqq'])){
die('数据不能为空,请检查'); die('数据不能为空,请检查');
} }
$db->insert_back_id('sq_admin',array('username'=>$_POST['addusername'],'password'=>md5($_POST['adduserpass']),'qq'=>$_POST['adduserqq'])); $db->insert_back_id('sq_admin',array('username'=>$_POST['addusername'],'password'=>wq_hash_password($_POST['adduserpass']),'qq'=>$_POST['adduserqq']));
die('管理员新增成功,如果需要删除请进数据库中的表sq_admin进行删除'); die('管理员新增成功,如果需要删除请进数据库中的表sq_admin进行删除');
break; break;
case 'sendtestmail': case 'sendtestmail':
@@ -1050,11 +1061,12 @@ switch ($_GET['mod']){
if (!$agentinfo = $db->select_first_row('sq_admin','*',array('username'=>$_SESSION['admin_username']),'AND')){ if (!$agentinfo = $db->select_first_row('sq_admin','*',array('username'=>$_SESSION['admin_username']),'AND')){
die('管理员信息获取失败'); die('管理员信息获取失败');
} }
if (md5($_POST['oldpass']) !== $agentinfo['password']){ // P0-05: 兼容校验旧 md5 / 新 bcrypt;新密码以 bcrypt 入库
if (!wq_verify_password($_POST['oldpass'], $agentinfo['password'], 'md5')){
die('输入的原密码错误,无法修改!'); die('输入的原密码错误,无法修改!');
} }
if(!$db->update('sq_admin',array('username'=>$_SESSION['admin_username']),'AND',array('password'=>md5($_POST['newpass'])))){ if(!$db->update('sq_admin',array('username'=>$_SESSION['admin_username']),'AND',array('password'=>wq_hash_password($_POST['newpass'])))){
die('密码修改失败,服务器内部发生错误'); die('密码修改失败,服务器内部发生错误');
}else{ }else{
die('管理密码修改成功,请使用新密码进行登录!'); die('管理密码修改成功,请使用新密码进行登录!');
+27 -9
View File
@@ -12,8 +12,9 @@ if ($_GET['mod'] === 'login') {
if (!$result = $db->select_first_row('sq_agent', '*', array('accesstoken' => $_POST['token']), 'AND')) { if (!$result = $db->select_first_row('sq_agent', '*', array('accesstoken' => $_POST['token']), 'AND')) {
die(json_encode(array('code' => '-1', 'msg' => '秘钥错误'))); die(json_encode(array('code' => '-1', 'msg' => '秘钥错误')));
} else { } else {
$_SESSION['agent_username'] = $_POST['username']; $_SESSION['agent_username'] = $result['username'];
$_SESSION['agent_password'] = $_POST['password']; // P0-05: 会话持有数据库中的"存储值"(新 bcrypt / 旧明文),与重校验 WHERE password=会话 保持一致
$_SESSION['agent_password'] = $result['password'];
$_SESSION['agent_id'] = $result['ID']; $_SESSION['agent_id'] = $result['ID'];
$_SESSION['agent_qq'] = $result['qq']; $_SESSION['agent_qq'] = $result['qq'];
$_SESSION['agent_HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT']; $_SESSION['agent_HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'];
@@ -35,18 +36,22 @@ if ($_GET['mod'] === 'login') {
die(json_encode(array('code' => -2, 'msg' => '请输入您的密码'))); die(json_encode(array('code' => -2, 'msg' => '请输入您的密码')));
} }
if (!$result = $db->select_first_row('sq_agent', '*', array('username' => $_POST['username'], 'password' => $_POST['password']), 'AND')) { // P0-05: 按用户名取出后用 PHP 校验(兼容新 bcrypt / 旧明文),成功即写回 bcrypt(惰性迁移),
// 会话保存"存储值",保证 checklogin 重校验 WHERE password=会话 始终匹配
$result = $db->select_first_row('sq_agent', '*', array('username' => $_POST['username']), 'AND');
if (!$result || !wq_verify_password($_POST['password'], $result['password'])) {
die(json_encode(array('code' => -3, 'msg' => '输入的账号密码错误!' . $db->geterror()))); die(json_encode(array('code' => -3, 'msg' => '输入的账号密码错误!' . $db->geterror())));
} else { } else {
if ($result['status'] != '1') { if ($result['status'] != '1') {
die(json_encode(array('code' => -4, 'msg' => '代理账号已被冻结,请联系管理员或者上级处理'))); die(json_encode(array('code' => -4, 'msg' => '代理账号已被冻结,请联系管理员或者上级处理')));
} }
$newhash = wq_hash_password($_POST['password']);
$_SESSION['agent_username'] = $_POST['username']; $_SESSION['agent_username'] = $_POST['username'];
$_SESSION['agent_password'] = $_POST['password']; $_SESSION['agent_password'] = $newhash;
$_SESSION['agent_id'] = $result['ID']; $_SESSION['agent_id'] = $result['ID'];
$_SESSION['agent_qq'] = $result['qq']; $_SESSION['agent_qq'] = $result['qq'];
$_SESSION['agent_HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT']; $_SESSION['agent_HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'];
$db->update('sq_agent', array('username' => $_SESSION['agent_username']), 'AND', array('loginip' => get_real_ip(), 'logintime' => time())); $db->update('sq_agent', array('ID' => $result['ID']), 'AND', array('password' => $newhash, 'loginip' => get_real_ip(), 'logintime' => time()));
die(json_encode(array('code' => '1', 'msg' => '登陆成功,正在跳转!'))); die(json_encode(array('code' => '1', 'msg' => '登陆成功,正在跳转!')));
} }
@@ -296,6 +301,10 @@ switch ($_GET['mod']) {
if ($_POST['mod'] == 'status') { if ($_POST['mod'] == 'status') {
$_POST['value'] = textbooltonum($_POST['value']); $_POST['value'] = textbooltonum($_POST['value']);
} }
// P0-05: 用户密码以 bcrypt 哈希入库
if ($_POST['mod'] == 'password') {
$_POST['value'] = wq_hash_password($_POST['value']);
}
if ($_POST['mod'] == 'balance') { if ($_POST['mod'] == 'balance') {
if ($_POST['value'] == '永久使用') { if ($_POST['value'] == '永久使用') {
$_POST['value'] = -1; $_POST['value'] = -1;
@@ -309,6 +318,11 @@ switch ($_GET['mod']) {
} }
// P0-05: 用户密码改密以 bcrypt 哈希入库
if ($_POST['mod'] == 'password') {
$_POST['value'] = wq_hash_password($_POST['value']);
}
if (!$db->update('sq_user', array('ID' => $_POST['userid'], 'aid' => $_SESSION['agent_id']), 'AND', array($_POST['mod'] => $_POST['value']))) { if (!$db->update('sq_user', array('ID' => $_POST['userid'], 'aid' => $_SESSION['agent_id']), 'AND', array($_POST['mod'] => $_POST['value']))) {
die('修改失败' . $db->geterror()); die('修改失败' . $db->geterror());
} else { } else {
@@ -436,7 +450,8 @@ switch ($_GET['mod']) {
'tradeno' => $tradno, 'tradeno' => $tradno,
'begintime' => $time, 'begintime' => $time,
'user' => $_POST['kt_user'], 'user' => $_POST['kt_user'],
'pass' => $_POST['kt_pass'], // P0-05: 订单中的密码直接以 bcrypt 哈希存库(开通落库点 function_auth 有幂等保护,不会二次哈希)
'pass' => wq_hash_password($_POST['kt_pass']),
'fid' => $_POST['id'], 'fid' => $_POST['id'],
'num' => $_POST['kt_num'], 'num' => $_POST['kt_num'],
'paymoney' => $spendmoney, 'paymoney' => $spendmoney,
@@ -819,7 +834,7 @@ switch ($_GET['mod']) {
if (!$db->update('sq_agent', array('username' => $_SESSION['agent_username']), 'AND', array('money' => $newmoney))) { if (!$db->update('sq_agent', array('username' => $_SESSION['agent_username']), 'AND', array('money' => $newmoney))) {
die('系统内部错误,开通失败!'); die('系统内部错误,开通失败!');
} else { } else {
if (!$db->insert_back_id('sq_agent', array('username' => $_POST['username'], 'password' => $_POST['pass'], 'qq' => $_POST['qq'], 'begintime' => time(), 'levelid' => $_POST['leval'], 'status' => '1', 'superior' => $_SESSION['agent_id']))) { if (!$db->insert_back_id('sq_agent', array('username' => $_POST['username'], 'password' => wq_hash_password($_POST['pass']), 'qq' => $_POST['qq'], 'begintime' => time(), 'levelid' => $_POST['leval'], 'status' => '1', 'superior' => $_SESSION['agent_id']))) {
die('您的余额已扣除但是内部发生错误,下级开通失败,请联系管理员处理!' . $db->geterror()); die('您的余额已扣除但是内部发生错误,下级开通失败,请联系管理员处理!' . $db->geterror());
} else { } else {
$db->insert_back_id('sq_log_agent', array('time' => time(), 'aid' => $_SESSION['agent_id'], 'ip' => get_real_ip(), 'msg' => '成功开通下级代理 ' . $_POST['username'])); $db->insert_back_id('sq_log_agent', array('time' => time(), 'aid' => $_SESSION['agent_id'], 'ip' => get_real_ip(), 'msg' => '成功开通下级代理 ' . $_POST['username']));
@@ -876,6 +891,8 @@ switch ($_GET['mod']) {
if ($_POST['mod'] != 'username' && $_POST['mod'] != 'password' && $_POST['mod'] != 'qq' && $_POST['mod'] != 'status') die('不支持修改的字段'); if ($_POST['mod'] != 'username' && $_POST['mod'] != 'password' && $_POST['mod'] != 'qq' && $_POST['mod'] != 'status') die('不支持修改的字段');
if ($_POST['mod'] == 'status') $_POST['value'] = textbooltonum($_POST['value']); if ($_POST['mod'] == 'status') $_POST['value'] = textbooltonum($_POST['value']);
// P0-05: 代理密码以 bcrypt 哈希入库
if ($_POST['mod'] == 'password') $_POST['value'] = wq_hash_password($_POST['value']);
if (!$db->update('sq_agent', array('ID' => $_POST['aid'], 'superior' => $_SESSION['agent_id']), 'AND', array($_POST['mod'] => $_POST['value']))) { if (!$db->update('sq_agent', array('ID' => $_POST['aid'], 'superior' => $_SESSION['agent_id']), 'AND', array($_POST['mod'] => $_POST['value']))) {
die('修改失败' . $db->geterror()); die('修改失败' . $db->geterror());
} else { } else {
@@ -933,11 +950,12 @@ switch ($_GET['mod']) {
if (!$agentinfo = $db->select_first_row('sq_agent', '*', array('username' => $_SESSION['agent_username']), 'AND')) { if (!$agentinfo = $db->select_first_row('sq_agent', '*', array('username' => $_SESSION['agent_username']), 'AND')) {
die('代理信息获取失败'); die('代理信息获取失败');
} }
if ($_POST['oldpass'] !== $agentinfo['password']) { // P0-05: 兼容校验旧明文 / 新 bcrypt;新密码以 bcrypt 入库
if (!wq_verify_password($_POST['oldpass'], $agentinfo['password'])) {
die('输入的原密码错误,无法修改!'); die('输入的原密码错误,无法修改!');
} }
if (!$db->update('sq_agent', array('username' => $_SESSION['agent_username']), 'AND', array('password' => $_POST['newpass']))) { if (!$db->update('sq_agent', array('username' => $_SESSION['agent_username']), 'AND', array('password' => wq_hash_password($_POST['newpass'])))) {
die('密码修改失败,服务器内部发生错误'); die('密码修改失败,服务器内部发生错误');
} else { } else {
die('代理密码修改成功,请使用新密码进行登录!'); die('代理密码修改成功,请使用新密码进行登录!');
+15 -6
View File
@@ -317,7 +317,8 @@ function dofill() {
'tradeno' => $tradno, 'tradeno' => $tradno,
'begintime' => $time, 'begintime' => $time,
'user' => $_POST['username'], 'user' => $_POST['username'],
'pass' => $_POST['password'], // P0-05: 订单密码以 bcrypt 哈希入库(开通落库点 function_auth/trade.inc 有幂等保护)
'pass' => wq_hash_password($_POST['password']),
'fid' => $_POST['lid'], 'fid' => $_POST['lid'],
'num' => 1, 'num' => 1,
'paymoney' => $money, 'paymoney' => $money,
@@ -376,7 +377,8 @@ function dofill() {
if (!$userinfo = $db->select_first_row('sq_user', '*', array('username' => $_POST['kt_user'], 'appid' => $fidinfo['appid']), 'AND')) { if (!$userinfo = $db->select_first_row('sq_user', '*', array('username' => $_POST['kt_user'], 'appid' => $fidinfo['appid']), 'AND')) {
} else { } else {
if ($_POST['kt_pass'] !== $userinfo['password']) { // P0-05: 兼容旧明文 / 新 bcrypt 校验续费用户密码
if (!wq_verify_password($_POST['kt_pass'], $userinfo['password'])) {
die(json_encode(array('code' => -2, 'msg' => '您的用户名已开通,您需要输入的正确密码才能进行续费,当前输入的用户密码错误!'))); die(json_encode(array('code' => -2, 'msg' => '您的用户名已开通,您需要输入的正确密码才能进行续费,当前输入的用户密码错误!')));
} }
} }
@@ -385,7 +387,7 @@ function dofill() {
'tradeno' => $tradno, 'tradeno' => $tradno,
'begintime' => $time, 'begintime' => $time,
'user' => $_POST['kt_user'], 'user' => $_POST['kt_user'],
'pass' => $_POST['kt_pass'], 'pass' => wq_hash_password($_POST['kt_pass']),
'fid' => $_POST['id'], 'fid' => $_POST['id'],
'num' => $_POST['kt_num'], 'num' => $_POST['kt_num'],
'paymoney' => $spendmoney, 'paymoney' => $spendmoney,
@@ -770,7 +772,8 @@ function buy_submit(id,type) {
if (!$userinfo = $db->select_first_row('sq_user', '*', array('username' => $_POST['kt_user'], 'appid' => $fidinfo['appid']), 'AND')) { if (!$userinfo = $db->select_first_row('sq_user', '*', array('username' => $_POST['kt_user'], 'appid' => $fidinfo['appid']), 'AND')) {
} else { } else {
if ($_POST['kt_pass'] !== $userinfo['password']) { // P0-05: 兼容旧明文 / 新 bcrypt 校验
if (!wq_verify_password($_POST['kt_pass'], $userinfo['password'])) {
die(json_encode(array('code' => -2, 'msg' => '用户密码错误!'))); die(json_encode(array('code' => -2, 'msg' => '用户密码错误!')));
} }
} }
@@ -778,7 +781,7 @@ function buy_submit(id,type) {
$newtrade = array('name' => $fidinfo['fidname'], $newtrade = array('name' => $fidinfo['fidname'],
'begintime' => $time, 'begintime' => $time,
'user' => $_POST['kt_user'], 'user' => $_POST['kt_user'],
'pass' => $_POST['kt_pass'], 'pass' => wq_hash_password($_POST['kt_pass']),
'fid' => $_POST['id'], 'fid' => $_POST['id'],
'mail' => $_POST['kt_mail'], 'mail' => $_POST['kt_mail'],
'kami' => $_POST['kt_key'], 'kami' => $_POST['kt_key'],
@@ -999,9 +1002,15 @@ function buy_submit(id,type) {
if (empty($_POST['kt_user']) || empty($_POST['kt_pass'])){ if (empty($_POST['kt_user']) || empty($_POST['kt_pass'])){
die(makejson(-2,'请输入您的登录信息')); die(makejson(-2,'请输入您的登录信息'));
} }
if (!$userinfo = $db->select_first_row('sq_user','ID,sendtime,mail',array('username'=>$_POST['kt_user'],'password'=>$_POST['kt_pass'],'appid'=>$appid),'AND')){ // P0-05: 先按用户名+应用取出,再在 PHP 中校验密码(兼容旧明文 / 新 bcrypt),
// 校验成功且为旧明文时惰性升级为 bcrypt
if (!$userinfo = $db->select_first_row('sq_user','ID,sendtime,mail,password',array('username'=>$_POST['kt_user'],'appid'=>$appid),'AND')
|| !wq_verify_password($_POST['kt_pass'], $userinfo['password'])){
die(makejson(-3,'未查询到您的授权信息!<br>请检查您输入的账号密码是否正确!')); die(makejson(-3,'未查询到您的授权信息!<br>请检查您输入的账号密码是否正确!'));
} }
if (!wq_is_hashed_password($userinfo['password'])){
$db->update('sq_user',array('ID'=>$userinfo['ID']),'AND',array('password'=>wq_hash_password($_POST['kt_pass'])));
}
}else if ($appinfo['logintype'] === 'kmsq'){ }else if ($appinfo['logintype'] === 'kmsq'){
if (empty($_POST['kt_user'])){ if (empty($_POST['kt_user'])){
die(makejson(-2,'请输入您登录使用的卡密')); die(makejson(-2,'请输入您登录使用的卡密'));
+17 -5
View File
@@ -132,9 +132,13 @@ switch ($_GET['mod']){
if(!$userinfo = $db->select_first_row('sq_user','*',array('appid'=>$_GET['appid'],'username'=>$postdata['user']),'AND')){ if(!$userinfo = $db->select_first_row('sq_user','*',array('appid'=>$_GET['appid'],'username'=>$postdata['user']),'AND')){
backmsg('-1','账号不存在'); backmsg('-1','账号不存在');
} }
if ($postdata['pass'] !== $userinfo['password']){ // P0-05: 兼容新 bcrypt / 旧明文校验;旧明文校验成功后惰性升级为 bcrypt
if (!wq_verify_password($postdata['pass'], $userinfo['password'])){
backmsg('-2','您输入的密码错误'); backmsg('-2','您输入的密码错误');
} }
if (!wq_is_hashed_password($userinfo['password'])){
$db->update('sq_user',array('ID'=>$userinfo['ID']),'AND',array('password'=>wq_hash_password($postdata['pass'])));
}
}else if ($appinfo['logintype'] == 'kmsq'){ }else if ($appinfo['logintype'] == 'kmsq'){
if(!$userinfo = $db->select_first_row('sq_user','*',array('appid'=>$_GET['appid'],'username'=>$postdata['user']),'AND')){ if(!$userinfo = $db->select_first_row('sq_user','*',array('appid'=>$_GET['appid'],'username'=>$postdata['user']),'AND')){
backmsg('-1','授权卡密不存在'); backmsg('-1','授权卡密不存在');
@@ -276,7 +280,8 @@ switch ($_GET['mod']){
backmsg('-13','验证码已过期,请尝试重新获取'); backmsg('-13','验证码已过期,请尝试重新获取');
} }
$db->update('sq_user',array('ID'=>$userinfo['ID']),'AND',array('password'=>$postdata['newpass'])); // P0-05: 新密码以 bcrypt 哈希入库
$db->update('sq_user',array('ID'=>$userinfo['ID']),'AND',array('password'=>wq_hash_password($postdata['newpass'])));
backmsg('1'); backmsg('1');
break; break;
case 'SubscriberDownline': case 'SubscriberDownline':
@@ -337,8 +342,11 @@ switch ($_GET['mod']){
if (time()-$userinfo['sendtime'] < 60){ if (time()-$userinfo['sendtime'] < 60){
backmsg('-1','邮件发送过于频繁,请耐心等待'.(time()-$userinfo['sendtime']).'秒后发送'); backmsg('-1','邮件发送过于频繁,请耐心等待'.(time()-$userinfo['sendtime']).'秒后发送');
} }
$db->update('sq_user',array('ID'=>$userinfo['ID']),'AND',array('sendtime'=>time())); // P0-05: 密码已以 bcrypt 哈希入库,原明文不再可取出,也禁止邮件外发。
sendemail('找回密码 - '.$appinfo['appname'],'['.$userinfo['username'].']您的密码为:'.$userinfo['password'],$userinfo['mail']); // 改为"重置为一次性随机新密码,仅通过用户本人邮箱投递一次"
$newpass = bin2hex(random_bytes(6));
$db->update('sq_user',array('ID'=>$userinfo['ID']),'AND',array('password'=>wq_hash_password($newpass),'sendtime'=>time()));
sendemail('密码重置 - '.$appinfo['appname'],'['.$userinfo['username'].']您的密码已重置,新密码为:'.$newpass.',请立即登录并尽快修改密码。',$userinfo['mail']);
backmsg('1'); backmsg('1');
break; break;
@@ -361,9 +369,13 @@ function getuserinfo($appinfo,$postdata){
if(!$userinfo = $db->select_first_row('sq_user','*',array('appid'=>$_GET['appid'],'username'=>$postdata['user']),'AND')){ if(!$userinfo = $db->select_first_row('sq_user','*',array('appid'=>$_GET['appid'],'username'=>$postdata['user']),'AND')){
backmsg('-1','账号不存在'); backmsg('-1','账号不存在');
} }
if ($postdata['pass'] !== $userinfo['password']){ // P0-05: 兼容新 bcrypt / 旧明文校验;旧明文校验成功后惰性升级为 bcrypt
if (!wq_verify_password($postdata['pass'], $userinfo['password'])){
backmsg('-2','您输入的密码错误'); backmsg('-2','您输入的密码错误');
} }
if (!wq_is_hashed_password($userinfo['password'])){
$db->update('sq_user',array('ID'=>$userinfo['ID']),'AND',array('password'=>wq_hash_password($postdata['pass'])));
}
}else if ($appinfo['logintype'] == 'kmsq'){ }else if ($appinfo['logintype'] == 'kmsq'){
if(!$userinfo = $db->select_first_row('sq_user','*',array('appid'=>$_GET['appid'],'username'=>$postdata['user']),'AND')){ if(!$userinfo = $db->select_first_row('sq_user','*',array('appid'=>$_GET['appid'],'username'=>$postdata['user']),'AND')){
backmsg('-1','授权卡密不存在'); backmsg('-1','授权卡密不存在');
+5 -1
View File
@@ -50,7 +50,11 @@ if ($data['mod'] === 'Login'){
if (!$userinfo = $db->select_first_row('sq_user', '*', array('appid' => $_GET['appid'], 'username' => $data['user']), 'AND')) $communication->back(-204,'ErrorUsername'); if (!$userinfo = $db->select_first_row('sq_user', '*', array('appid' => $_GET['appid'], 'username' => $data['user']), 'AND')) $communication->back(-204,'ErrorUsername');
if ($data['pass'] !== $userinfo['password']) $communication->back(-205,'ErrorPassword'); // P0-05: 兼容旧明文 / 新 bcrypt 校验用户密码;旧明文校验成功后惰性升级为 bcrypt
if (!wq_verify_password($data['pass'], $userinfo['password'])) $communication->back(-205,'ErrorPassword');
if (!wq_is_hashed_password($userinfo['password'])) {
$db->update('sq_user', array('ID' => $userinfo['ID']), 'AND', array('password' => wq_hash_password($data['pass'])));
}
} else if ($appinfo['logintype'] == 'kmsq') { } else if ($appinfo['logintype'] == 'kmsq') {
if (empty($data['user'])) $communication->back(-211,'NoKami'); if (empty($data['user'])) $communication->back(-211,'NoKami');
+5 -1
View File
@@ -51,7 +51,11 @@ if ($data['mod'] === 'Login'){
if (!$userinfo = $db->select_first_row('sq_user', '*', array('appid' => $_GET['appid'], 'username' => $data['user']), 'AND')) $communication->back(-204,'ErrorUsername'); if (!$userinfo = $db->select_first_row('sq_user', '*', array('appid' => $_GET['appid'], 'username' => $data['user']), 'AND')) $communication->back(-204,'ErrorUsername');
if ($data['pass'] !== $userinfo['password']) $communication->back(-205,'ErrorPassword'); // P0-05: 兼容旧明文 / 新 bcrypt 校验用户密码;旧明文校验成功后惰性升级为 bcrypt
if (!wq_verify_password($data['pass'], $userinfo['password'])) $communication->back(-205,'ErrorPassword');
if (!wq_is_hashed_password($userinfo['password'])) {
$db->update('sq_user', array('ID' => $userinfo['ID']), 'AND', array('password' => wq_hash_password($data['pass'])));
}
} else if ($appinfo['logintype'] == 'kmsq') { } else if ($appinfo['logintype'] == 'kmsq') {
if (empty($data['user'])) $communication->back(-211,'NoKami'); if (empty($data['user'])) $communication->back(-211,'NoKami');
+7
View File
@@ -23,9 +23,16 @@
* @return int 1为新开成功,2为续费成功,3为卡密新开成功 * @return int 1为新开成功,2为续费成功,3为卡密新开成功
*/ */
function auth_add($username,$password,$ip,$balance,$uqq,$mail,$appid,$origin,$oid,$aid,&$newkey,&$tips,$tradeinfo = array()){ function auth_add($username,$password,$ip,$balance,$uqq,$mail,$appid,$origin,$oid,$aid,&$newkey,&$tips,$tradeinfo = array()){
include_once __DIR__ . '/function_core.php';
include_once __DIR__ . '/function_app.php'; include_once __DIR__ . '/function_app.php';
global $db,$G,$MailTips; global $db,$G,$MailTips;
// P0-05: 密码以 bcrypt 哈希入库。幂等保护:若上游(订单创建点)已哈希则原样保留,
// 旧格式明文订单(升级前产生)在此处补哈希
if (!wq_is_hashed_password($password)) {
$password = wq_hash_password($password);
}
$appinfo = app_idgetinfo($appid); $appinfo = app_idgetinfo($appid);
if ($appinfo['usetype']=='dqsj'){ if ($appinfo['usetype']=='dqsj'){
+40
View File
@@ -211,6 +211,46 @@ function updateset($name, $value)
$G['config'][$name] = $value; $G['config'][$name] = $value;
} }
/* ---------- P0-05:密码哈希(bcrypt)与旧格式兼容 ---------- */
/**
* 判断存储的密码值是否为新 bcrypt 哈希
*/
function wq_is_hashed_password($stored)
{
return is_string($stored) && strlen($stored) === 60 && strpos($stored, '$2y$') === 0;
}
/**
* 生成新密码的入库值(bcrypt)
*/
function wq_hash_password($plain)
{
return password_hash((string)$plain, PASSWORD_BCRYPT);
}
/**
* 校验密码(兼容旧格式):
* - 存储值为 bcrypt -> password_verify
* - 旧 sq_admin(md5 存储)-> $legacy='md5',与 md5(明文) 比对
* - 旧 sq_user/sq_agent(明文存储)-> 直接比对
* @param string $plain 明文密码
* @param string $stored 数据库存储值
* @param string $legacy 'plain'(用户/代理旧格式)或 'md5'(管理员旧格式)
*/
function wq_verify_password($plain, $stored, $legacy = 'plain')
{
$plain = (string)$plain;
$stored = (string)$stored;
if (wq_is_hashed_password($stored)) {
return password_verify($plain, $stored);
}
if ($legacy === 'md5') {
return hash_equals($stored, md5($plain));
}
return hash_equals($stored, $plain);
}
/* /*
* 布尔逻辑型转换为数字0或1 * 布尔逻辑型转换为数字0或1
+5
View File
@@ -81,6 +81,11 @@ function trade_do($tradeinfo){
} }
if ($tradeinfo['type'] == '4') { if ($tradeinfo['type'] == '4') {
$nowtime = Get_Date(); $nowtime = Get_Date();
// P0-05: 代理密码以 bcrypt 哈希入库。幂等保护:订单创建点已哈希则原样保留,
// 旧格式明文订单(升级前产生)在此处补哈希
if (!wq_is_hashed_password($tradeinfo['pass'])) {
$tradeinfo['pass'] = wq_hash_password($tradeinfo['pass']);
}
$db->insert_back_id('sq_agent', array('username' => $tradeinfo['user'], 'password' => $tradeinfo['pass'], 'begintime' => time(), 'levelid' => $tradeinfo['fid'], 'status' => 1, 'qq' => $tradeinfo['uqq'])); $db->insert_back_id('sq_agent', array('username' => $tradeinfo['user'], 'password' => $tradeinfo['pass'], 'begintime' => time(), 'levelid' => $tradeinfo['fid'], 'status' => 1, 'qq' => $tradeinfo['uqq']));
$T['title'] = '您的代理已成功开通!'; $T['title'] = '您的代理已成功开通!';
$T['content'][] = 'QQ:'.$tradeinfo['uqq']; $T['content'][] = 'QQ:'.$tradeinfo['uqq'];
+1 -1
View File
@@ -31,7 +31,7 @@ SET time_zone = "+00:00";
CREATE TABLE `sq_admin` ( CREATE TABLE `sq_admin` (
`ID` int(11) NOT NULL, `ID` int(11) NOT NULL,
`username` varchar(255) NOT NULL, `username` varchar(255) NOT NULL,
`password` varchar(33) NOT NULL, `password` varchar(255) NOT NULL,
`loginip` varchar(20) NOT NULL, `loginip` varchar(20) NOT NULL,
`logintime` int(11) NOT NULL, `logintime` int(11) NOT NULL,
`qq` varchar(12) NOT NULL, `qq` varchar(12) NOT NULL,