背景: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 处密码点防复活踩坑。
300 lines
12 KiB
PHP
300 lines
12 KiB
PHP
<?php
|
||
/**
|
||
* Created by PhpStorm.
|
||
* User: 温泉
|
||
* Date: 2019/2/9
|
||
* Time: 15:50
|
||
*/
|
||
|
||
|
||
/**
|
||
* 授权操作
|
||
* @access public
|
||
* @param string $username 用户名、卡密
|
||
* @param string $password 密码
|
||
* @param string $ip 用户IP、绑定的IP
|
||
* @param int $balance 新增授权点数或秒数,-1为永久
|
||
* @param string $uqq 用户QQ
|
||
* @param string $mail 用户邮箱
|
||
* @param int $appid 应用ID
|
||
* @param int $agentid 代理ID
|
||
* @param string $newkey 返回的卡密
|
||
* @param string $tips 提示
|
||
* @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'){
|
||
if ($balance!='-1'){
|
||
$balance=$balance*60;
|
||
}
|
||
}
|
||
if ($appinfo['logintype'] == 'zhmm') {
|
||
//账号密码授权方式
|
||
if (!$userinfo = $db->select_first_row('sq_user', '*', array('username' => $username, 'appid' => $appid), 'AND')) {
|
||
$T['title'] = '您的授权账号已新开成功!';
|
||
$T['content'][] = '应用名称:'.$appinfo['appname'];
|
||
$T['content'][] = '注册时间:'.Get_Date();
|
||
if ($appinfo['usetype']=='dqsj'){
|
||
if ($balance!='-1'){
|
||
$balance=time()+$balance;
|
||
$T['content'][] = '到期时间:'.Get_Date($balance);
|
||
}else{
|
||
$T['content'][] = '到期时间:永久授权';
|
||
}
|
||
}else{
|
||
if ($balance == '-1'){
|
||
$T['content'][] = '开通余额:无限使用';
|
||
}else{
|
||
$T['content'][] = '开通余额:'.$balance;
|
||
}
|
||
|
||
}
|
||
|
||
$db->insert_back_id('sq_user', array(
|
||
'username' => $username,
|
||
'password' =>$password,
|
||
'mail' => $mail,
|
||
'uqq' => $uqq,
|
||
'rtime' => time(),
|
||
'status' => '1',
|
||
'appid' => $appid,
|
||
'aid' => $aid,
|
||
'balance'=>$balance,
|
||
'origin' => $origin,
|
||
'oid'=>$oid
|
||
)
|
||
);
|
||
$T['content'][] = '授权来源:'.GetOriginText($origin);
|
||
$T['content'][] = '联系方式:QQ '.$uqq;
|
||
$T['content'][] = '授权账号:'.$username;
|
||
$T['content'][] = '用户邮箱:'.$mail;
|
||
$MailTips[] = $T;
|
||
SendTipsMail($mail,$MailTips,$backinfo);
|
||
return 1;
|
||
}
|
||
$T['title'] = '您的授权账号已续费成功!';
|
||
$T['content'][] = '应用名称:'.$appinfo['appname'];
|
||
$T['content'][] = '注册时间:'.Get_Date($userinfo['rtime']);
|
||
if ($balance != '-1') {
|
||
if ($appinfo['usetype'] == 'dqsj') {
|
||
if ($userinfo['balance'] < time()) {
|
||
$usernewinfo['balance'] = time() + $balance;
|
||
} else {
|
||
$usernewinfo['balance'] = $userinfo['balance'] + $balance;
|
||
}
|
||
$T['content'][] = '到期时间:'.Get_Date($usernewinfo['balance']);
|
||
} else if ($appinfo['usetype'] == 'kcye') {
|
||
$usernewinfo['balance'] = $userinfo['balance'] +$balance;
|
||
$T['content'][] = '授权余额:'.Get_Date($usernewinfo['balance']);
|
||
} else {
|
||
$tips='应用使用方式设置异常';
|
||
return -3;
|
||
}
|
||
|
||
} else {
|
||
$usernewinfo['balance'] = '-1';
|
||
$T['content'][] = $appinfo['usetype']=='dqsj' ? '到期时间:永久授权' : '开通余额:无限使用';
|
||
}
|
||
$T['content'][] = '授权来源:'.GetOriginText($origin);
|
||
$T['content'][] = '联系方式:QQ '.$uqq;
|
||
$T['content'][] = '授权账号:'.$userinfo['username'];
|
||
$T['content'][] = '用户邮箱:'.$userinfo['mail'];
|
||
$MailTips[]= $T;
|
||
$db->update('sq_user', array('ID'=>$userinfo['ID']), 'AND', $usernewinfo);
|
||
SendTipsMail($userinfo['mail'],$MailTips,$backinfo);
|
||
return 2;
|
||
} else if ($appinfo['logintype'] == 'kmsq') {
|
||
//卡密登录方式
|
||
if (empty($username)) {
|
||
//没有卡密新开授权
|
||
$T['title'] = '您的授权卡密已新开成功!';
|
||
$T['content'][] = '应用名称:'.$appinfo['appname'];
|
||
$T['content'][] = '注册时间:'.Get_Date();
|
||
if ($appinfo['usetype']=='dqsj'){
|
||
if ($balance!='-1'){
|
||
$balance=time()+$balance;
|
||
$T['content'][] = '到期时间:'.Get_Date($balance);
|
||
}else{
|
||
$T['content'][] = '到期时间:永久使用';
|
||
}
|
||
}else{
|
||
if ($balance == '-1'){
|
||
$T['content'][] = '开通余额:无限使用';
|
||
}else{
|
||
$T['content'][] = '开通余额:'.$balance;
|
||
}
|
||
}
|
||
$newkey = rand_str(32);
|
||
while ($info = $db->select_first_row('sq_user', 'ID', array('username' => $newkey, 'appid' => $appinfo['ID']), 'AND')) {
|
||
$newkey = rand_str(32);
|
||
}
|
||
//echo $newkey;
|
||
$db->insert_back_id('sq_user', array(
|
||
'username' => $newkey,
|
||
'mail' => $mail,
|
||
'rtime' => time(),
|
||
'balance' => $balance,
|
||
'status' => 1,
|
||
'origin' => $origin,
|
||
'oid'=>$oid,
|
||
'appid' => $appid,
|
||
'aid' => $aid,
|
||
));
|
||
$T['content'][] = '授权来源:'.GetOriginText($origin);
|
||
$T['content'][] = '联系方式:QQ '.$uqq;
|
||
$T['content'][] = '授权卡密:'.$newkey;
|
||
$T['content'][] = '用户邮箱:'.$mail;
|
||
$MailTips[] = $T;
|
||
SendTipsMail($mail,$MailTips,$backinfo);
|
||
return 3;
|
||
}
|
||
//续费授权
|
||
if (!$userinfo = $db->select_first_row('sq_user', '*', array('username' => $username, 'appid' => $appid), 'AND')) {
|
||
$tips = '授权卡密不存在';
|
||
return -2;
|
||
}
|
||
$T['title'] = '您的授权卡密已续费成功!';
|
||
$T['content'][] = '应用名称:'.$appinfo['appname'];
|
||
$T['content'][] = '注册时间:'.Get_Date($userinfo['rtime']);
|
||
if ($balance != '-1') {
|
||
if ($appinfo['usetype'] == 'dqsj') {
|
||
if ($userinfo['balance'] < time()) {
|
||
$usernewinfo['balance'] = time() + $balance;
|
||
} else {
|
||
$usernewinfo['balance'] = $userinfo['balance'] + $balance;
|
||
}
|
||
$T['content'][] = '到期时间:'.Get_Date($usernewinfo['balance']);
|
||
} else if ($appinfo['usetype'] == 'kcye') {
|
||
$usernewinfo['balance'] = $userinfo['balance'] +$balance;
|
||
$T['content'][] = '授权余额:'.Get_Date($usernewinfo['balance']);
|
||
} else {
|
||
$tips='应用使用方式设置异常';
|
||
return -3;
|
||
}
|
||
} else {
|
||
$usernewinfo['balance'] = '-1';
|
||
$T['content'][] = $appinfo['usetype']=='dqsj' ? '到期时间:永久授权' : '开通余额:无限使用';
|
||
}
|
||
$T['content'][] = '授权来源:'.GetOriginText($origin);
|
||
$T['content'][] = '联系方式:QQ '.$uqq;
|
||
$T['content'][] = '授权卡密:'.$userinfo['username'];
|
||
$T['content'][] = '用户邮箱:'.$userinfo['mail'];
|
||
$MailTips[]= $T;
|
||
$db->update('sq_user', array('username' => $username, 'appid' => $appinfo['ID']), 'AND', $usernewinfo);
|
||
SendTipsMail($userinfo['mail'],$MailTips,$backinfo);
|
||
return 2;
|
||
} else if ($appinfo['logintype'] == 'jcbd') {
|
||
$chect = json_decode($username,true);
|
||
if ($appinfo['bindip'] == '1'){
|
||
$search['lip'] = $chect['lip'];
|
||
$T['content'][] = '绑定IP:'.$chect['lip'];
|
||
}
|
||
if ($appinfo['bindqq'] == '1'){
|
||
$search['rqq'] = $chect['rqq'];
|
||
$T['content'][] = '绑定QQ:'.$chect['rqq'];
|
||
}
|
||
if ($appinfo['bindmac'] == '1'){
|
||
$search['mac'] = $chect['mac'];
|
||
$T['content'][] = '绑定设备:'.$chect['mac'];
|
||
}
|
||
$search['appid'] = $appid;
|
||
if (!isset($chect['lip'])){
|
||
$chect['lip'] = '';
|
||
}
|
||
if (!isset($chect['rqq'])){
|
||
$chect['rqq'] = '';
|
||
}
|
||
if (!isset($chect['mac'])){
|
||
$chect['mac'] = '';
|
||
}
|
||
if (!$userinfo = $db->select_first_row('sq_user', '*', $search, 'AND')) {
|
||
$T['title'] = '您的授权绑定已新开成功!';
|
||
$T['content'][] = '应用名称:'.$appinfo['appname'];
|
||
$T['content'][] = '注册时间:'.Get_Date();
|
||
if ($appinfo['usetype']=='dqsj'){
|
||
if ($balance!='-1'){
|
||
$balance=time()+$balance;
|
||
$T['content'][] = '到期时间:'.Get_Date($balance);
|
||
}else{
|
||
$T['content'][] = '到期时间:永久使用';
|
||
}
|
||
}else{
|
||
if ($balance == '-1'){
|
||
$T['content'][] = '开通余额:无限使用';
|
||
}else{
|
||
$T['content'][] = '开通余额:'.$balance;
|
||
}
|
||
}
|
||
|
||
$db->insert_back_id('sq_user', array(
|
||
'mail' => $mail,
|
||
'rtime' => time(),
|
||
'balance' => $balance,
|
||
'status' => 1,
|
||
'appid' => $appid,
|
||
'uqq' => $uqq,
|
||
'mac' => $chect['mac'],
|
||
'rqq' => $chect['rqq'],
|
||
'rip' => $ip,
|
||
'lip' => $chect['lip'],
|
||
'aid' => $aid,
|
||
'origin' => $origin,
|
||
'oid'=>$oid,
|
||
));
|
||
|
||
$T['content'][] = '授权来源:'.GetOriginText($origin);
|
||
$T['content'][] = '联系方式:QQ '.$uqq;
|
||
$T['content'][] = '用户邮箱:'.$mail;
|
||
$MailTips[] = $T;
|
||
SendTipsMail($mail,$MailTips,$backinfo);
|
||
return 1;
|
||
}
|
||
|
||
$T['title'] = '您的授权卡密已续费成功!';
|
||
$T['content'][] = '应用名称:'.$appinfo['appname'];
|
||
$T['content'][] = '注册时间:'.Get_Date($userinfo['rtime']);
|
||
if ($balance != '-1') {
|
||
if ($appinfo['usetype'] == 'dqsj') {
|
||
if ($userinfo['balance'] < time()) {
|
||
$usernewinfo['balance'] = time() + $balance;
|
||
} else {
|
||
$usernewinfo['balance'] = $userinfo['balance'] + $balance;
|
||
}
|
||
$T['content'][] = '到期时间:'.Get_Date($usernewinfo['balance']);
|
||
} else if ($appinfo['usetype'] == 'kcye') {
|
||
$usernewinfo['balance'] = $userinfo['balance'] +$balance;
|
||
$T['content'][] = '授权余额:'.Get_Date($usernewinfo['balance']);
|
||
} else {
|
||
$tips='应用使用方式设置异常';
|
||
return -3;
|
||
}
|
||
} else {
|
||
$usernewinfo['balance'] = '-1';
|
||
$T['content'][] = $appinfo['usetype']=='dqsj' ? '到期时间:永久授权' : '开通余额:无限使用';
|
||
}
|
||
|
||
$T['content'][] = '授权来源:'.GetOriginText($origin);
|
||
$T['content'][] = '联系方式:QQ '.$uqq;
|
||
$T['content'][] = '用户邮箱:'.$userinfo['mail'];
|
||
$MailTips[]= $T;
|
||
$db->update('sq_user', $search, 'AND', $usernewinfo);
|
||
SendTipsMail($userinfo['mail'],$MailTips,$backinfo);
|
||
return 2;
|
||
}
|
||
$tips = '未知授权请求?';
|
||
return -2;
|
||
}
|
||
|