背景: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 处密码点防复活踩坑。
247 lines
12 KiB
PHP
247 lines
12 KiB
PHP
<?php
|
||
/**
|
||
* Created by PhpStorm.
|
||
* User: 温泉
|
||
* Date: 2018/7/9
|
||
* Time: 17:31
|
||
* Notice: 强加密版Api,与易语言通信
|
||
*/
|
||
|
||
error_reporting(1); //只输出致命错误
|
||
|
||
include __DIR__ . '/../function/function_core.php';
|
||
include __DIR__ . '/../function/function_app.php';
|
||
include __DIR__ . '/../function/Tips.php';
|
||
if (!empty($_GET['c']) && $_GET['c'] == 'api') include __DIR__ . '/../function/api.class.php';else include __DIR__ . '/../function/Encryption.class.php';
|
||
|
||
if (empty($_GET['appid'])) die(makejson(-1,$Tips['NoAppID']));
|
||
if (strlen($_GET['sign']) !== 32) die(makejson(-2,$Tips['NoSign']));
|
||
$data = $_POST['data'];
|
||
if (!$appinfo = app_idgetinfo($_GET['appid'])) die(makejson(-4,$Tips['GetAppInfoErr']));
|
||
$communication = !empty($_GET['c']) && $_GET['c'] == 'api'? new ApiCommunication($appinfo['connectkey'],$appinfo['decryptkey']) : new communication($appinfo['connectkey'],$appinfo['decryptkey']);
|
||
if ($communication->getsign($data) !== $_GET['sign']) die(makejson(-3,$Tips['SignError']));
|
||
$time = time();
|
||
$d = !empty($_GET['c']) && $_GET['c'] == 'api' ? $data : $communication->data_decode($data);
|
||
unset($data);
|
||
$data = json_decode($d,true);
|
||
$sign = '';
|
||
if ($data['mod'] === 'Initialization'){
|
||
if ($data['ddition'] < $appinfo['ver'] && numtobool($appinfo['forceup'])) $communication->back(-101,'AppVerOld',array('updateurl'=>$appinfo['upurl'],'updatelog'=>$appinfo['uplog'],'notice'=>$appinfo['notice']));
|
||
if (numtobool($appinfo['close'])) $communication->back(-102,'AppClosed',array('updateurl'=>$appinfo['upurl'],'updatelog'=>$appinfo['uplog'],'notice'=>$appinfo['notice']));
|
||
$token = rand_str(64);
|
||
$db->insert_back_id('sq_token',array('token'=>$token,'ip'=>get_real_ip(),'addtime'=>$time,'appid'=>$_GET['appid']));
|
||
$communication->back(1,'',array('updateurl'=>$appinfo['upurl'],'updatelog'=>$appinfo['uplog'],'notice'=>$appinfo['notice'],'token'=>$token));
|
||
}
|
||
|
||
if ($data['mod'] === 'Login'){
|
||
//echo 'login';
|
||
if (empty($data['token']) || !$tokeninfo = $db->select_first_row('sq_token','uid,ip',array('token'=>$data['token']),'AND')) $communication->back(-201,'InitTimeOut');
|
||
|
||
$userip = empty($data['ip']) ? get_real_ip() : $data['ip'];
|
||
if ($appinfo['free']) {
|
||
$db->update('sq_token',array('token'=>$data['token']),'AND',array('uid'=>0,'start'=>time()));
|
||
$communication->back(1);
|
||
}
|
||
|
||
if ($appinfo['logintype'] == 'zhmm') {
|
||
if (empty($data['user'])) $communication->back(-202,'NoUsername');
|
||
|
||
if (empty($data['pass'])) $communication->back(-203,'NoPassword');
|
||
|
||
if (!$userinfo = $db->select_first_row('sq_user', '*', array('appid' => $_GET['appid'], 'username' => $data['user']), 'AND')) $communication->back(-204,'ErrorUsername');
|
||
|
||
// 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');
|
||
if (!$userinfo = $db->select_first_row('sq_user', '*', array('appid' => $_GET['appid'], 'username' => $data['user']), 'AND')) $communication->back(-212,'ErrorKami');
|
||
|
||
} else if ($appinfo['logintype'] == 'jcbd') {
|
||
$where['appid'] = $_GET['appid'];
|
||
if ($appinfo['bindip'] == 1) $where['lip'] = $userip;
|
||
if ($appinfo['bindmac'] == 1) $where['mac'] = $data['mac'];
|
||
if ($appinfo['bindqq'] == 1) $where['rqq'] = $data['robot'];
|
||
|
||
if (!$userinfo = $db->select_first_row('sq_user', '*', $where, 'AND')) $communication->back(-221,'ErrorBind');
|
||
}
|
||
if ($userinfo['status'] != 1) $communication->back(-231,'UserCold');
|
||
if ($appinfo['usetype'] == 'dqsj') {
|
||
if ($userinfo['balance'] < time() && $userinfo['balance'] != '-1') $communication->back(-232,'AuthTimeOut',array('balance'=>$userinfo['balance']));
|
||
|
||
} else if ($appinfo['usetype'] == 'kcye') {
|
||
if ($userinfo['balance'] <= 0) $communication->back(-233,'BalanceOut');
|
||
} else {
|
||
$communication->back(-4,'GetAppInfoErr');
|
||
}
|
||
$bindip = $userinfo['lip'];
|
||
$bindmac = $userinfo['mac'];
|
||
$usermac = $data['mac'];
|
||
$bindrqq = $userinfo['rqq'];
|
||
$userqq = $data['robot'];
|
||
|
||
if ($appinfo['bindip'] == 1 && !empty($userinfo['lip']) && $userinfo['lip'] !== $userip) {
|
||
$communication->back(-234,'IpBindNeed',array('bindip'=>$userinfo['lip']));
|
||
|
||
}
|
||
if ($appinfo['bindmac'] == 1 && !empty($userinfo['mac']) && $bindmac !== $usermac) {
|
||
$communication->back(-235,'MacBindNeed',array('bindmac'=>$userinfo['mac']));
|
||
}
|
||
if ($appinfo['bindqq'] == 1 && !empty($userinfo['rqq']) && $bindrqq !== $userqq) {
|
||
$communication->back(-236,'QQBindNeed',array('bindqq'=>$userinfo['rqq']));
|
||
}
|
||
|
||
$db->update('sq_user', array('ID' => $userinfo['ID']), 'AND', array('rqq' =>$userqq, 'mac' =>$usermac, 'lip' => $userip, 'ltime' => time()));
|
||
//$token = rand_str(64);
|
||
$db->update('sq_token',array('token'=>$data['token']),'AND',array('uid'=>$userinfo['ID'],'start'=>time()));
|
||
//$db->insert_back_id('sq_token',array('token'=>$token,'uid'=>$userinfo['ID'],'lastest'=>$time,'start'=>$time));
|
||
if ($appinfo['onlinecheck'] == 1) {
|
||
$onlineuser = $db->select_all_row('sq_token','lastest,ip',array('uid'=>$userinfo['ID']),'AND','ORDER BY lastest DESC');
|
||
foreach ($onlineuser as $on){
|
||
if ((time() - $on['lastest']) < $appinfo['onlinesecond']){
|
||
$communication->back(-301,'LoggedInTips',array('ip'=>$on['ip'],''));
|
||
}else{
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
$communication->back(1);
|
||
|
||
}
|
||
|
||
if ($data['mod'] === 'GoOnline'){
|
||
if (empty($data['token']) || !$tokeninfo = $db->select_first_row('sq_token','*',array('token'=>$data['token']),'AND')) $communication->back(-201,'InitTimeOut');
|
||
if ($tokeninfo['start'] == 0 || empty($tokeninfo['start'])){
|
||
$communication->back(-201,'InitTimeOut');
|
||
}
|
||
if ($appinfo['free']) {
|
||
$db->update('sq_token',array('token'=>$data['token']),'AND',array('lastest'=>time()));
|
||
$communication->back(1);
|
||
}
|
||
if ($appinfo['onlinecheck'] == 1){
|
||
$db->delete('sq_token',array('uid'=>$tokeninfo['uid']),'AND');
|
||
}
|
||
//$db->insert_back_id('sq_token',array('token'=>$tokeninfo['token'],'uid'=>$tokeninfo['uid'],'start'=>$tokeninfo['start'],'lastest'=>time()));
|
||
$communication->back(1);
|
||
|
||
}
|
||
|
||
if ($data['mod'] === 'OnlineHeart'){
|
||
if (empty($data['token'])) $communication->back(-401,'HeartNoToken');
|
||
if (!$tokeninfo = $db->select_first_row('sq_token','*',array('token'=>$data['token']),'AND')) $communication->back(-402,'HeartTokenError');
|
||
if ($tokeninfo['start'] == 0 || empty($tokeninfo['start'])){
|
||
$communication->back(-201,'InitTimeOut');
|
||
}
|
||
$db->update('sq_token',array('token'=>$data['token']),'AND',array('lastest'=>time()));
|
||
if ($appinfo['free']) {
|
||
$communication->back(1,'',array('time'=>$time));
|
||
}
|
||
if (!$userinfo = $db->select_first_row('sq_user','*',array('ID'=>$tokeninfo['uid']),'AND')) $communication->back(-234,'HeartNoFoundUser');
|
||
if ($userinfo['status'] != 1) $communication->back(-231,'UserCold');
|
||
if ($appinfo['usetype'] == 'dqsj') {
|
||
if ($userinfo['balance'] < time() && $userinfo['balance'] != '-1') $communication->back(-232,'AuthTimeOut');
|
||
} else if ($appinfo['usetype'] == 'kcye') {
|
||
if ($userinfo['balance'] <= 0) $communication->back(-233,'BalanceOut');
|
||
} else {
|
||
$communication->back(-4,'GetAppInfoErr');
|
||
}
|
||
|
||
$msg = '';
|
||
if ($appinfo['usetype'] == 'kcye') {
|
||
$time = time();
|
||
$chazhi = time() - $userinfo['lastreducetime'];
|
||
if ($chazhi > $appinfo['onlinesecond'] + 120) {
|
||
//新登录
|
||
$lastbalance = (int)$userinfo['balance'] - (int)$appinfo['cycreduce'];
|
||
$lastreducetime = $time;
|
||
$msg = '余额扣除成功';
|
||
} else {
|
||
//在线减
|
||
$oninemin = (int)($chazhi / 60);
|
||
$lastbalance = $userinfo['balance'] - $appinfo['cycreduce'] * $oninemin;
|
||
$lastreducetime = $userinfo['lastreducetime'] - $oninemin * 60;
|
||
$msg = '余额扣除成功';
|
||
}
|
||
$db->update('sq_user', array('ID' => $userinfo['ID'], 'appid' => $_GET['appid']), 'AND', array('htime' => time(), 'balance' => $lastbalance, 'lastreducetime' => $lastreducetime));
|
||
} else {
|
||
$db->update('sq_user', array('ID' => $userinfo['ID'], 'appid' => $_GET['appid']), 'AND', array('htime' => time()));
|
||
}
|
||
$communication->back(1,'',array('time'=>$time,'o'=>$msg));
|
||
}
|
||
|
||
if ($data['mod'] == 'GetCustumData'){
|
||
if (empty($data['token'])) $communication->back(-401,'HeartNoToken');
|
||
if (!$tokeninfo = $db->select_first_row('sq_token','*',array('token'=>$data['token']),'AND')) $communication->back(-402,'HeartTokenError');
|
||
if ($tokeninfo['start'] == 0 || empty($tokeninfo['start'])){
|
||
$communication->back(-201,'InitTimeOut');
|
||
}
|
||
|
||
if ($appinfo['free']) {
|
||
$communication->back(1,'',array('data'=>$appinfo['data']));
|
||
}
|
||
if (!$userinfo = $db->select_first_row('sq_user','*',array('ID'=>$tokeninfo['uid']),'AND')) $communication->back(-234,'HeartNoFoundUser');
|
||
if ($userinfo['status'] != 1) $communication->back(-231,'UserCold');
|
||
if ($appinfo['usetype'] == 'dqsj') {
|
||
if ($userinfo['balance'] < time() && $userinfo['balance'] != '-1') $communication->back(-232,'AuthTimeOut');
|
||
} else if ($appinfo['usetype'] == 'kcye') {
|
||
if ($userinfo['balance'] <= 0) $communication->back(-233,'BalanceOut');
|
||
} else {
|
||
$communication->back(-4,'GetAppInfoErr');
|
||
}
|
||
$communication->back(1,'',array('data'=>$appinfo['data']));
|
||
}
|
||
|
||
if ($data['mod'] == 'GetAppInfo'){
|
||
if (empty($data['token']) || !$tokeninfo = $db->select_first_row('sq_token','uid',array('token'=>$data['token']),'AND')) $communication->back(-201,'InitTimeOut');
|
||
|
||
$bool = true;
|
||
$i = 0 ;
|
||
while ($bool){
|
||
if (isset($appinfo[$i])){
|
||
unset($appinfo[$i]);
|
||
}else{
|
||
$bool = false;
|
||
}
|
||
$i ++ ;
|
||
}
|
||
// 安全加固 (P0-04):connectkey/decryptkey 是服务端与 SDK 通信的签名/解密密钥,
|
||
// 原实现把 sq_apps 全行(含这两个密钥明文)返回给 SDK,导致任何持有合法
|
||
// token 的调用方/抓包者拿到密钥后可伪造该应用的任意 API 请求。改为掩码下发。
|
||
if (isset($appinfo['connectkey'])) $appinfo['connectkey'] = str_repeat('*', 8);
|
||
if (isset($appinfo['decryptkey'])) $appinfo['decryptkey'] = str_repeat('*', 8);
|
||
$communication->back(1,'',$appinfo);
|
||
}
|
||
|
||
if ($data['mod'] == 'GetUserInfo'){
|
||
if (empty($data['token'])) $communication->back(-401,'HeartNoToken');
|
||
if (!$tokeninfo = $db->select_first_row('sq_token','*',array('token'=>$data['token']),'AND')) $communication->back(-402,'HeartTokenError');
|
||
if ($tokeninfo['start'] == 0 || empty($tokeninfo['start'])){
|
||
$communication->back(-201,'InitTimeOut');
|
||
}
|
||
|
||
if ($appinfo['free']) {
|
||
$communication->back(2,'');
|
||
}
|
||
|
||
if (!$userinfo = $db->select_first_row('sq_user','*',array('ID'=>$tokeninfo['uid']),'AND')) $communication->back(-234,'HeartNoFoundUser');
|
||
if ($userinfo['status'] != 1) $communication->back(-231,'UserCold');
|
||
if ($appinfo['usetype'] == 'dqsj') {
|
||
if ($userinfo['balance'] < time() && $userinfo['balance'] != '-1') $communication->back(-232,'AuthTimeOut');
|
||
} else if ($appinfo['usetype'] == 'kcye') {
|
||
if ($userinfo['balance'] <= 0) $communication->back(-233,'BalanceOut');
|
||
} else {
|
||
$communication->back(-4,'GetAppInfoErr');
|
||
}
|
||
$communication->back(1,'',$userinfo);
|
||
}
|
||
|
||
if($data['mod'] == 'OffLine'){
|
||
if (empty($data['token'])) $communication->back(-401,'HeartNoToken');
|
||
if (!$tokeninfo = $db->select_first_row('sq_token','*',array('token'=>$data['token']),'AND')) $communication->back(-402,'HeartTokenError');
|
||
$db->delete('sq_token',array('token'=>$data['token']),'AND');
|
||
$communication->back(1);
|
||
}
|