背景: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 处密码点防复活踩坑。
137 lines
6.1 KiB
PHP
137 lines
6.1 KiB
PHP
<?php
|
||
/**
|
||
* Created by PhpStorm.
|
||
* User: 温泉
|
||
* Date: 2019/2/17
|
||
* Time: 11:47
|
||
*
|
||
*/
|
||
|
||
|
||
/**
|
||
* 订单处理
|
||
* @param $tradeinfo
|
||
* @return false|string
|
||
*/
|
||
function trade_do($tradeinfo){
|
||
global $db;
|
||
$MailTips[] = MakeTradeTips($tradeinfo);
|
||
ignore_user_abort(true);//关闭浏览器继续处理,保证订单不会处理到一半就凉了
|
||
if ($tradeinfo['type'] == '1') {
|
||
//代理充值
|
||
if (!$agentinfo = $db->select_first_row('sq_agent', '*', array('username' => $tradeinfo['user']), 'AND')) {
|
||
return makejson(-101,'代理信息获取失败');
|
||
}
|
||
$newinfo['money'] = $agentinfo['money'] + $tradeinfo['paymoney'];
|
||
$newinfo['allrecharge'] = $agentinfo['allrecharge'] + $tradeinfo['paymoney'];
|
||
if (!$db->update('sq_agent', array('username' => $tradeinfo['user']), 'AND', $newinfo)) {
|
||
return makejson(-102,'代理信息更新失败');
|
||
}
|
||
$db->insert_back_id('sq_log_agent', array('time' => time(), 'aid' => $agentinfo['ID'], 'ip' => get_real_ip(), 'msg' => '成功充值' . $tradeinfo['paymoney'] . '元,余额' . $newinfo['allrecharge']));
|
||
return makejson(1,'充值余额成功!');
|
||
}
|
||
if ($tradeinfo['type'] == '2') {
|
||
//代理开通商品
|
||
if (!$fidinfo = $db->select_first_row('sq_fidlist', '*', array('ID' => $tradeinfo['fid']), 'AND')) {
|
||
return makejson(-201,'商品信息拉取失败');
|
||
}
|
||
if (!$agentinfo = $db->select_first_row('sq_agent', '*', array('ID' => $tradeinfo['agentid']), 'AND')) {
|
||
return makejson(-202,'代理信息拉取失败');
|
||
}
|
||
$allspend = $agentinfo['allspend'] + $tradeinfo['paymoney'];
|
||
$db->update('sq_agent', array('ID' => $tradeinfo['agentid']), 'AND', array('allspend' => $allspend));
|
||
include_once __DIR__ . '/function_auth.php';
|
||
$back = auth_add($tradeinfo['user'],$tradeinfo['pass'],$tradeinfo['ip'],$tradeinfo['num'] * $fidinfo['num'],$tradeinfo['uqq'],$tradeinfo['mail'],$fidinfo['appid'],4,'',$agentinfo['ID'],$newkey,$tips);
|
||
if ($back == 1){
|
||
return makejson(1,'授权新增成功');
|
||
}else if ($back==2){
|
||
return makejson(1,'授权续费成功');
|
||
}else if ($back==3){
|
||
return makejson(2,'成功生成卡密',array('kami'=>$newkey));
|
||
}else{
|
||
return makejson($back,$tips);
|
||
}
|
||
}
|
||
if ($tradeinfo['type'] == '3') {
|
||
//在线购买
|
||
if (!$fidinfo = $db->select_first_row('sq_fidlist', '*', array('ID' => $tradeinfo['fid']), 'AND')) {
|
||
return makejson(-301,'商品信息拉取失败');
|
||
}
|
||
include_once __DIR__ . '/function_auth.php';
|
||
if ($tradeinfo['paytype'] == 'czkm') {
|
||
if (!$kamiinfo = $db->select_first_row('sq_key','*',array('kami'=>$tradeinfo['kami']),'AND'))
|
||
{
|
||
return makejson(-301,'卡密不存在');
|
||
}
|
||
$back = auth_add($tradeinfo['user'],$tradeinfo['pass'],$tradeinfo['ip'],$tradeinfo['num'] * $fidinfo['num'],$tradeinfo['uqq'],$tradeinfo['mail'],$fidinfo['appid'],2,$kamiinfo['ID'],$kamiinfo['aid'],$newkey,$tips);
|
||
}elseif ($tradeinfo['paytype'] == 'zxzf') {
|
||
$back = auth_add($tradeinfo['user'],$tradeinfo['pass'],$tradeinfo['ip'],$tradeinfo['num'] * $fidinfo['num'],$tradeinfo['uqq'],$tradeinfo['mail'],$fidinfo['appid'],2,0,0,$newkey,$tips);
|
||
}else {
|
||
return makejson(-302,'操作错误');
|
||
}
|
||
if ($back == 1){
|
||
return makejson(1,'授权新增成功');
|
||
}else if ($back==2){
|
||
return makejson(1,'授权续费成功');
|
||
}else if ($back==3){
|
||
return makejson(2,'成功生成卡密',array('kami'=>$newkey));
|
||
}else{
|
||
return makejson($back,$tips);
|
||
}
|
||
}
|
||
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'];
|
||
$T['content'][] = '账号:'.$tradeinfo['user'];
|
||
$T['content'][] = '时间:'.Get_Date();
|
||
$T['content'][] = '等级:'.level_idgetname($tradeinfo['fid']);
|
||
$MailTips[] = $T;
|
||
SendTipsMail($tradeinfo['mail'],$MailTips,$back);
|
||
return makejson(1,'代理开通成功');
|
||
}
|
||
return makejson(-404,'订单类型异常');
|
||
}
|
||
|
||
function MakeTradeTips($tradeinfo){
|
||
$T['title'] = '您的订单已成功处理!';
|
||
$T['content'][] = '订单金额:'.$tradeinfo['paymoney'].' 元';
|
||
switch ($tradeinfo['paytype']){
|
||
case 'czkm':
|
||
$T['content'][] = '支付方式:充值卡密';
|
||
break;
|
||
case 'yezf':
|
||
$T['content'][] = '支付方式:代理余额';
|
||
break;
|
||
case 'zxzf':
|
||
switch ($tradeinfo['onlinepaytype']){
|
||
case 'zfb':
|
||
$T['content'][] = '支付方式:在线支付-支付宝';
|
||
break;
|
||
case 'wx':
|
||
$T['content'][] = '支付方式:在线支付-微信支付';
|
||
break;
|
||
case 'qq':
|
||
$T['content'][] = '支付方式:在线支付-QQ钱包';
|
||
break;
|
||
default:
|
||
$T['content'][] = '支付方式:在线支付-未知';
|
||
}
|
||
break;
|
||
default:
|
||
$T['content'][] = '支付方式:未知方式';
|
||
}
|
||
$T['content'][] = '订单名称:'.$tradeinfo['name'];
|
||
$T['content'][] = '下单时间:'.Get_Date($tradeinfo['begintime']);
|
||
$T['content'][] = '完成时间:'.Get_Date();
|
||
$T['content'][] = '订单编号:'.$tradeinfo['tradeno'];
|
||
return $T;
|
||
}
|
||
|