From 24264cf320a087edc3a95008ccf3ffcd63ecc89e Mon Sep 17 00:00:00 2001 From: youpaishidifu <1@ypshidifu.cn> Date: Sat, 10 Oct 2026 12:25:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=9B=BA-06(P0-05):=20=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E5=85=A8=E9=83=A8=E4=BB=A5=20bcrypt=20=E5=93=88?= =?UTF-8?q?=E5=B8=8C=E5=85=A5=E5=BA=93=EF=BC=8C=E6=97=A7=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=EF=BC=88=E6=98=8E=E6=96=87/MD5=EF=BC=89=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E5=B9=B6=E6=83=B0=E6=80=A7=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 背景: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 处密码点防复活踩坑。 --- admin/ajax.php | 32 ++++++++++++++++++++---------- agent/ajax.php | 36 +++++++++++++++++++++++++--------- ajax.php | 21 ++++++++++++++------ api.php | 22 ++++++++++++++++----- api/Communication.php | 6 +++++- function/communication.php | 6 +++++- function/function_auth.php | 7 +++++++ function/function_core.php | 40 ++++++++++++++++++++++++++++++++++++++ function/trade.inc.php | 5 +++++ wenquan_auth.sql | 2 +- 10 files changed, 144 insertions(+), 33 deletions(-) diff --git a/admin/ajax.php b/admin/ajax.php index fab7ec6..70027dc 100644 --- a/admin/ajax.php +++ b/admin/ajax.php @@ -34,17 +34,20 @@ if ($_GET['mod'] === 'login'){ if ($verification !== true) { die(json_encode(array('code' => '-88', 'msg' => '请先进行人机验证'))); } - if (!$result = $db->select_first_row('sq_admin','*',array('username'=>$_POST['username'],'password'=>md5($_POST['password'])),'AND')){ - //$db->insert_back_id('sq_log_system',array('time'=>time(),'ip'=>get_real_ip(),'msg'=>'管理员 '.$_POST['username'].'使用密码'.$_POST['password'].'登陆失败:密码错误或账号不存在','type'=>'danger')); - $db->posterror('管理员登录尝试失败,账号['.$_POST['username'].']密码['.$_POST['password'].']','success'); + // P0-05: 按用户名取出后用 PHP 校验(兼容新 bcrypt / 旧 md5),成功即写回 bcrypt(惰性迁移); + // 失败日志不再记录明文密码(原 posterror 会把密码写入 sq_log_system) + $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()))); }else{ $_SESSION['admin_username'] = $_POST['username']; $_SESSION['admin_id'] = $result['ID']; $_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']; - $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->posterror('管理员 '.$_POST['username'].'使用密码登录后台成功','success'); 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']))){ die('修改失败'.$db->geterror()); }else{ @@ -624,7 +632,7 @@ switch ($_GET['mod']){ if ($db->select_first_row('sq_agent','*',array('username'=>$_POST['agent_name']),'AND') != false){ 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()); }else{ die('代理添加成功!'); @@ -674,11 +682,12 @@ switch ($_GET['mod']){ break; case 'getagentinfo': $result = $db->select_first_row('sq_agent','*',array('ID'=>$_POST['id']),'AND'); + // P0-05: 不再把密码(哈希或明文)回传前端;密码修改走 changeagent 单独入口 die(json_encode(array( 'agent_leval'=>$result['levelid'], 'agent_money'=>$result['money'], 'agent_name'=>$result['username'], - 'agent_pass'=>$result['password'] + 'agent_pass'=>'********' ))); break; @@ -695,6 +704,8 @@ switch ($_GET['mod']){ die('代理ID不能为空'); } 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']))){ die('修改失败'.$db->geterror()); }else{ @@ -762,7 +773,7 @@ switch ($_GET['mod']){ if (empty($_POST['addusername']) || empty($_POST['adduserpass']) || empty($_POST['adduserqq'])){ 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进行删除'); break; case 'sendtestmail': @@ -1050,11 +1061,12 @@ switch ($_GET['mod']){ if (!$agentinfo = $db->select_first_row('sq_admin','*',array('username'=>$_SESSION['admin_username']),'AND')){ die('管理员信息获取失败'); } - if (md5($_POST['oldpass']) !== $agentinfo['password']){ + // P0-05: 兼容校验旧 md5 / 新 bcrypt;新密码以 bcrypt 入库 + if (!wq_verify_password($_POST['oldpass'], $agentinfo['password'], 'md5')){ 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('密码修改失败,服务器内部发生错误'); }else{ die('管理密码修改成功,请使用新密码进行登录!'); diff --git a/agent/ajax.php b/agent/ajax.php index eceb95e..7af053a 100644 --- a/agent/ajax.php +++ b/agent/ajax.php @@ -12,8 +12,9 @@ if ($_GET['mod'] === 'login') { if (!$result = $db->select_first_row('sq_agent', '*', array('accesstoken' => $_POST['token']), 'AND')) { die(json_encode(array('code' => '-1', 'msg' => '秘钥错误'))); } else { - $_SESSION['agent_username'] = $_POST['username']; - $_SESSION['agent_password'] = $_POST['password']; + $_SESSION['agent_username'] = $result['username']; + // P0-05: 会话持有数据库中的"存储值"(新 bcrypt / 旧明文),与重校验 WHERE password=会话 保持一致 + $_SESSION['agent_password'] = $result['password']; $_SESSION['agent_id'] = $result['ID']; $_SESSION['agent_qq'] = $result['qq']; $_SESSION['agent_HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT']; @@ -35,18 +36,22 @@ if ($_GET['mod'] === 'login') { 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()))); } else { if ($result['status'] != '1') { die(json_encode(array('code' => -4, 'msg' => '代理账号已被冻结,请联系管理员或者上级处理'))); } + $newhash = wq_hash_password($_POST['password']); $_SESSION['agent_username'] = $_POST['username']; - $_SESSION['agent_password'] = $_POST['password']; + $_SESSION['agent_password'] = $newhash; $_SESSION['agent_id'] = $result['ID']; $_SESSION['agent_qq'] = $result['qq']; $_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' => '登陆成功,正在跳转!'))); } @@ -296,6 +301,10 @@ switch ($_GET['mod']) { if ($_POST['mod'] == 'status') { $_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['value'] == '永久使用') { $_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']))) { die('修改失败' . $db->geterror()); } else { @@ -436,7 +450,8 @@ switch ($_GET['mod']) { 'tradeno' => $tradno, 'begintime' => $time, 'user' => $_POST['kt_user'], - 'pass' => $_POST['kt_pass'], + // P0-05: 订单中的密码直接以 bcrypt 哈希存库(开通落库点 function_auth 有幂等保护,不会二次哈希) + 'pass' => wq_hash_password($_POST['kt_pass']), 'fid' => $_POST['id'], 'num' => $_POST['kt_num'], 'paymoney' => $spendmoney, @@ -819,7 +834,7 @@ switch ($_GET['mod']) { if (!$db->update('sq_agent', array('username' => $_SESSION['agent_username']), 'AND', array('money' => $newmoney))) { die('系统内部错误,开通失败!'); } 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()); } else { $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'] == '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']))) { die('修改失败' . $db->geterror()); } else { @@ -933,11 +950,12 @@ switch ($_GET['mod']) { if (!$agentinfo = $db->select_first_row('sq_agent', '*', array('username' => $_SESSION['agent_username']), 'AND')) { die('代理信息获取失败'); } - if ($_POST['oldpass'] !== $agentinfo['password']) { + // P0-05: 兼容校验旧明文 / 新 bcrypt;新密码以 bcrypt 入库 + if (!wq_verify_password($_POST['oldpass'], $agentinfo['password'])) { 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('密码修改失败,服务器内部发生错误'); } else { die('代理密码修改成功,请使用新密码进行登录!'); diff --git a/ajax.php b/ajax.php index c87c0e0..9121a92 100644 --- a/ajax.php +++ b/ajax.php @@ -317,7 +317,8 @@ function dofill() { 'tradeno' => $tradno, 'begintime' => $time, 'user' => $_POST['username'], - 'pass' => $_POST['password'], + // P0-05: 订单密码以 bcrypt 哈希入库(开通落库点 function_auth/trade.inc 有幂等保护) + 'pass' => wq_hash_password($_POST['password']), 'fid' => $_POST['lid'], 'num' => 1, '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')) { } 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' => '您的用户名已开通,您需要输入的正确密码才能进行续费,当前输入的用户密码错误!'))); } } @@ -385,7 +387,7 @@ function dofill() { 'tradeno' => $tradno, 'begintime' => $time, 'user' => $_POST['kt_user'], - 'pass' => $_POST['kt_pass'], + 'pass' => wq_hash_password($_POST['kt_pass']), 'fid' => $_POST['id'], 'num' => $_POST['kt_num'], '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')) { } 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' => '用户密码错误!'))); } } @@ -778,7 +781,7 @@ function buy_submit(id,type) { $newtrade = array('name' => $fidinfo['fidname'], 'begintime' => $time, 'user' => $_POST['kt_user'], - 'pass' => $_POST['kt_pass'], + 'pass' => wq_hash_password($_POST['kt_pass']), 'fid' => $_POST['id'], 'mail' => $_POST['kt_mail'], 'kami' => $_POST['kt_key'], @@ -999,9 +1002,15 @@ function buy_submit(id,type) { if (empty($_POST['kt_user']) || empty($_POST['kt_pass'])){ 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,'未查询到您的授权信息!
请检查您输入的账号密码是否正确!')); } + 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'){ if (empty($_POST['kt_user'])){ die(makejson(-2,'请输入您登录使用的卡密')); diff --git a/api.php b/api.php index eb90e72..6ab1433 100644 --- a/api.php +++ b/api.php @@ -132,9 +132,13 @@ switch ($_GET['mod']){ if(!$userinfo = $db->select_first_row('sq_user','*',array('appid'=>$_GET['appid'],'username'=>$postdata['user']),'AND')){ backmsg('-1','账号不存在'); } - if ($postdata['pass'] !== $userinfo['password']){ + // P0-05: 兼容新 bcrypt / 旧明文校验;旧明文校验成功后惰性升级为 bcrypt + if (!wq_verify_password($postdata['pass'], $userinfo['password'])){ 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'){ if(!$userinfo = $db->select_first_row('sq_user','*',array('appid'=>$_GET['appid'],'username'=>$postdata['user']),'AND')){ backmsg('-1','授权卡密不存在'); @@ -276,7 +280,8 @@ switch ($_GET['mod']){ 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'); break; case 'SubscriberDownline': @@ -337,8 +342,11 @@ switch ($_GET['mod']){ if (time()-$userinfo['sendtime'] < 60){ backmsg('-1','邮件发送过于频繁,请耐心等待'.(time()-$userinfo['sendtime']).'秒后发送'); } - $db->update('sq_user',array('ID'=>$userinfo['ID']),'AND',array('sendtime'=>time())); - sendemail('找回密码 - '.$appinfo['appname'],'['.$userinfo['username'].']您的密码为:'.$userinfo['password'],$userinfo['mail']); + // P0-05: 密码已以 bcrypt 哈希入库,原明文不再可取出,也禁止邮件外发。 + // 改为"重置为一次性随机新密码,仅通过用户本人邮箱投递一次" + $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'); 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')){ backmsg('-1','账号不存在'); } - if ($postdata['pass'] !== $userinfo['password']){ + // P0-05: 兼容新 bcrypt / 旧明文校验;旧明文校验成功后惰性升级为 bcrypt + if (!wq_verify_password($postdata['pass'], $userinfo['password'])){ 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'){ if(!$userinfo = $db->select_first_row('sq_user','*',array('appid'=>$_GET['appid'],'username'=>$postdata['user']),'AND')){ backmsg('-1','授权卡密不存在'); diff --git a/api/Communication.php b/api/Communication.php index 66e72a2..296092c 100644 --- a/api/Communication.php +++ b/api/Communication.php @@ -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 ($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') { if (empty($data['user'])) $communication->back(-211,'NoKami'); diff --git a/function/communication.php b/function/communication.php index 46cfbd1..039be87 100644 --- a/function/communication.php +++ b/function/communication.php @@ -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 ($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') { if (empty($data['user'])) $communication->back(-211,'NoKami'); diff --git a/function/function_auth.php b/function/function_auth.php index 83549ad..2dbade2 100644 --- a/function/function_auth.php +++ b/function/function_auth.php @@ -23,9 +23,16 @@ * @return int 1为新开成功,2为续费成功,3为卡密新开成功 */ 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'; global $db,$G,$MailTips; + // P0-05: 密码以 bcrypt 哈希入库。幂等保护:若上游(订单创建点)已哈希则原样保留, + // 旧格式明文订单(升级前产生)在此处补哈希 + if (!wq_is_hashed_password($password)) { + $password = wq_hash_password($password); + } + $appinfo = app_idgetinfo($appid); if ($appinfo['usetype']=='dqsj'){ diff --git a/function/function_core.php b/function/function_core.php index bd41838..550adf9 100644 --- a/function/function_core.php +++ b/function/function_core.php @@ -211,6 +211,46 @@ function updateset($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 diff --git a/function/trade.inc.php b/function/trade.inc.php index a78c632..2cca7df 100644 --- a/function/trade.inc.php +++ b/function/trade.inc.php @@ -81,6 +81,11 @@ function trade_do($tradeinfo){ } if ($tradeinfo['type'] == '4') { $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'])); $T['title'] = '您的代理已成功开通!'; $T['content'][] = 'QQ:'.$tradeinfo['uqq']; diff --git a/wenquan_auth.sql b/wenquan_auth.sql index 6476abf..217a99f 100644 --- a/wenquan_auth.sql +++ b/wenquan_auth.sql @@ -31,7 +31,7 @@ SET time_zone = "+00:00"; CREATE TABLE `sq_admin` ( `ID` int(11) NOT NULL, `username` varchar(255) NOT NULL, - `password` varchar(33) NOT NULL, + `password` varchar(255) NOT NULL, `loginip` varchar(20) NOT NULL, `logintime` int(11) NOT NULL, `qq` varchar(12) NOT NULL,