Files
wenquanauth/function/function_core.php
T
youpaishidifu aaf5fe48c6 加固-07(P0-06): 封堵 SQL 注入面(动态列名白名单 + 裸拼值强类型化)
背景:多处 SQL 由用户输入拼接——update() 的动态键(mod)直接作列名入 SET 子句;
卡密/分类卡密批量 INSERT 手拼 SQL 值元组(insert_back_row 零转义);DelUse 删除
语句手拼 fid。列名注入与手拼值注入均可被利用篡改/破坏数据。

1. 列名注入(动态 mod 键):
   - function/function_core.php 新增 wq_is_db_field($name):仅允许 [A-Za-z0-9_]{1,64}
     合法 SQL 标识符,杜绝引号/空格/注释符进入列名
   - admin/ajax.php 四处 change* 动态更新加守卫(非法 mod 直接 die「不支持修改的字段」):
     * changegrade (sq_level)
     * changeuser  (sq_user)
     * changefid   (sq_fidlist)
     * changeagent (sq_agent)
   - 注:agent 侧 changeuser/changeagent 已有字段白名单(P0-05 已加 password 哈希),无需重复

2. 手拼 INSERT 值元组(creatkey / creatfidkey,admin+agent 双侧):
   - 卡密金额 sc_money:强制 is_numeric(),非法直接拒绝(money 会裸拼进 INSERT 与卡密串)
   - 分类 fid sc_fid:强制 (int) 整型化后再拼入(防多语句/注释符逃逸)

3. DelUse 删除语句(admin+agent 双侧):
   - fid 强制 (int) 整型化;agent 侧 aid 亦 (int) 化
   - 注入载荷如 "1 AND 1=1" 被截为 1,SQL 结构不再可被改写

回归(30 项全 PASS,8082 内置服务器):
- 各 change* 正常字段修改成功且 DB 生效;列名注入(price,x / 多语句 DROP 载荷)全部被拒,目标表完好
- creatkey 正常生成、余额正确扣除;money 注入载荷被 is_numeric 拒绝
- creatfidkey 合法 fid 正常生成;注入 fid 在 fid 查找即被拒(int 化后无命中)
- DelUse 注入 fid 被 int 化后正常执行(删 0 行),表结构完好
- 注:(int)"1; DROP..."=1(前导数字保留),(int)"'; DROP..."=0,载荷均无法逃逸 SQL 结构
2026-10-10 12:41:33 +08:00

501 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Created by PhpStorm.
* User: 温泉
* Date: 2017-10-15
* Time: 12:07
*/
define('IN_SYS', true);//初始化系统
error_reporting(E_ALL);
/*
* 初始化脚本
*/
session_set_cookie_params(time() + 2592000);//保持登陆30天
session_start();//开启SESSION功能
date_default_timezone_set('PRC');//设置当前时区为中国
header("Content-type: text/html; charset=utf-8");
//过滤xss代码
foreach ($_POST as $key => $value) {
$_POST[$key] = xss_clean($value);
}
foreach ($_GET as $key => $value) {
$_GET[$key] = xss_clean($value);
}
foreach ($_COOKIE as $key => $value) {
$_COOKIE[$key] = xss_clean($value);
}
foreach ($_REQUEST as $key => $value) {
$_REQUEST[$key] = xss_clean($value);
}
function makejson($code = 1,$msg = 'ok',$data = array()){
return json_encode(array_merge(array('code'=>intval($code),'msg'=>$msg),$data));
}
/*
* 初始化数据库链接
*/
$G = array();
include __DIR__ . '/db.class.php';
include __DIR__ . '/config.inc.php';
$return = $db = new db($G['db']['server'], $G['db']['user'], $G['db']['pass'], $G['db']['dbname']);
if (!$return) {
die('数据库链接失败,请检查设置是否正确:' . $db->geterror());
}
/*
* 读取系统相关设置
*/
$back = $db->select_all_row('sq_config', array('*'), '', '');
foreach ($back as $value) {
$G['config'][$value['setname']] = $value['setvalue'];
}
include __DIR__ . '/setname.inc.php';
foreach ($SetName as $key=>$value){
if (!isset($G['config'][$key])){
$db->insert_back_id('sq_config',array('setname'=>$key,'setvalue'=>$value));
}
}
$back = $db->select_all_row('sq_config', array('*'), '', '');
foreach ($back as $value) {
$G['config'][$value['setname']] = $value['setvalue'];
}
$G['config']['kqxt'] = numtobool($G['config']['kqxt']);
$G['config']['yjtx'] = numtobool($G['config']['yjtx']);
$G['config']['xtsy'] = numtobool($G['config']['xtsy']);
$G['config']['epay_againcheck'] = numtobool($G['config']['epay_againcheck']);
$G['sys']['rootdir'] = dirname(__DIR__);
/*
$_SESSION['auth']['checktime'] = 0;
if (empty($_SESSION['auth']['checktime']) || time() - $_SESSION['auth']['checktime'] > 3600) {
if (isset($ServerDomain)) unset($ServerDomain);
$ServerDomain['1'] = 'https://new.api.shouquan.wenquan6.cn:99/api.php';
$ServerDomain['2'] = 'https://new.api.shouquan.wenquan.cleverqq.cn:99/api.php';
$ServerDomain['3'] = 'http://new.api.shouquan.wenquan6.cn:98/api.php';
$ServerDomain['4'] = 'http://new.api.shouquan.wenquan.cleverqq.cn:98/api.php';
$ServerDomain['5'] = 'http://api.shouquan.wenquan6.cn/api.php';
$ServerDomain['6'] = 'http://api.shouquan.wenquan.cleverqq.cn/api.php';
if (isset($ServerDomain[(string)$G['config']['sid']]) && (time() - $G['config']['stime']) < 86400){
$auth = json_decode(curl_request($ServerDomain[$G['config']['sid']].'?mod=checkauth&domain=' . $_SERVER['HTTP_HOST'].'&sitekey='.$G['config']['sitekey']), true);
}
if (empty($auth['code'])) {
foreach ($ServerDomain as $key=>$value){
$auth = json_decode(curl_request($value.'?mod=checkauth&domain=' . $_SERVER['HTTP_HOST'].'&sitekey='.$G['config']['sitekey']), true);
if (!empty($auth['code'])){
updateset('sid',$key);
updateset('stime',time());
break;
}
}
}
if ($auth['code'] == '-3') {
die($auth['tips']);
}
}
if ($auth['code'] == '1'){
$_SESSION['auth']['checktime'] = time();
$ServerURL = $ServerDomain[$G['config']['sid']];
}
*/
$ServerURL = 'https://new.api.shouquan.wenquan6.cn:99/api.php';
include __DIR__ . '/ver.inc.php';
include_once __DIR__ . '/MailTipsTemplate.php';
/*
* 注册申明常用函数
*/
$http_type = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) ? 'https://' : 'http://';
/*
* 一些系统提示,用alert并且跳转页面
*/
function tips($message, $url = 'javascript:history.go(-1);')
{
die('<html lang="zh">
<head>
<title>系统提示</title>
<script type="text/javascript">
alert("' . $message . '");
window.location.href=\'' . $url . '\';
</script>
</head>
<body>
</body>
</html>');
}
/*
* 获取用户的真实IP
*/
function get_real_ip()
{
$ip=FALSE;
//客户端IP 或 NONE
if(!empty($_SERVER["HTTP_CLIENT_IP"])){
$ip = $_SERVER["HTTP_CLIENT_IP"];
}
//多重代理服务器下的客户端真实IP地址(可能伪造),如果没有使用代理,此字段为空
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = explode (", ", $_SERVER['HTTP_X_FORWARDED_FOR']);
if ($ip) { array_unshift($ips, $ip); $ip = FALSE; }
for ($i = 0; $i < count($ips); $i++) {
if (!preg_match ("/^(10│172.16│192.168)./", $ips[$i])) {
$ip = $ips[$i];
break;
}
}
}
return ($ip ? $ip : $_SERVER['REMOTE_ADDR']);
}
/*
* 将时间戳转换为北京标准时间,如果时间戳为空则是取当前时间
*/
function Get_Date($time = '')
{
if (empty($time)) {
$time = time();
}
if (!is_numeric($time)) {
return $time;
}
return date('Y-m-d H:i:s', $time);
}
/*
* 随机字符生成
*/
function rand_str($length, $p = 'all')
{
$yznums = '0123456789';
$lowers = 'abcdefghijklmnopqrstuvwxyz';
$uppers = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if ($p == 'all') {
$src = $yznums . $lowers . $uppers;
} else {
$src = '';
if (strpos($p, 'num') !== false)
$src .= $yznums;
if (strpos($p, 'lower') !== false)
$src .= $lowers;
if (strpos($p, 'upper') !== false)
$src .= $uppers;
}
return $src ? substr(str_shuffle($src), 0, $length) : $src;
}
/**
* 更新系统设置
*/
function updateset($name, $value)
{
global $G,$db;
$db->update('sq_config', array('setname' => $name), 'AND', array('setvalue' => $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);
}
/**
* P0-06: 校验动态字段标识(mod)是否为合法 SQL 列名,防列名注入
* 动态 UPDATE/INSERT 的键来自用户输入时,须先通过本校验
*/
function wq_is_db_field($name)
{
return is_string($name) && preg_match('/^[A-Za-z0-9_]{1,64}$/', $name) === 1;
}
/*
* 布尔逻辑型转换为数字0或1
*/
function booltonum($bool)
{
if ($bool) {
return 1;
} else {
return 0;
}
}
/*
* 布尔文本型转换为数字0或1
*/
function textbooltonum($text)
{
if ($text == 'on' || $text == 'true') {
return '1';
} else {
return '0';
}
}
/*
* 数字还原为普通的布尔逻辑值
*/
function numtobool($num)
{
if ($num == 1) {
return true;
} else {
return false;
}
}
/**
* XSS暴力过滤神器,防止xss攻击
*/
function xss_clean($data)
{
// Fix &entity\n;
$data = str_replace(array('&amp;', '&lt;', '&gt;'), array('&amp;amp;', '&amp;lt;', '&amp;gt;'), $data);
$data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data);
$data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data);
$data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');
// Remove any attribute starting with "on" or xmlns
$data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);
// Remove javascript: and vbscript: protocols
$data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
$data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
$data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);
// Only works in IE: <span style="width: expression(alert('Ping!'));"></span>
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);
// Remove namespaced elements (we do not need them)
$data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data);
// http://www.111cn.net/
do {// Remove really unwanted tags
$old_data = $data;
$data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
} while ($old_data !== $data);
// we are done...
return $data;
}
/**
* @param $second
* @return string
*/
function time_last($second)
{
if ($second < 0) {
return '已结束';
}
$day = floor($second / 86400);
$second = $second - $day * 86400;
$hour = floor($second / 3600);
$second = $second - $hour * 3600;
$minute = floor($second / 60);
$second = $second - $minute * 60;
return $day . '天' . $hour . '时' . $minute . '分' . $second . '秒';
}
/**
* 使用CURL进行HTTP请求
* 参数1:访问的URL,
* 参数2:post数据(不填则为GET),
* 参数3:提交的$cookies,
* 参数4:是否返回$cookies
*/
function curl_request($url, $post = '', $cookie = '', $returnCookie = 0)
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)');
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_AUTOREFERER, 1);
curl_setopt($curl, CURLOPT_REFERER, "http://XXX");
if ($post) {
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $post);
}
if ($cookie) {
curl_setopt($curl, CURLOPT_COOKIE, $cookie);
}
curl_setopt($curl, CURLOPT_HEADER, $returnCookie);
curl_setopt($curl, CURLOPT_TIMEOUT, 10);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
$data = curl_exec($curl);
if (curl_errno($curl)) {
return '';
}
curl_close($curl);
if ($returnCookie) {
list($header, $body) = explode("\r\n\r\n", $data, 2);
preg_match_all("/Set\-Cookie:([^;]*);/", $header, $matches);
$info['cookie'] = substr($matches[1][0], 1);
$info['content'] = $body;
return $info;
} else {
return $data;
}
}
/**
* 调用温泉云平台进行邮件发送
*/
function sendemail($title, $contant, $tomail, &$msg = '', $debug = false)
{
global $G,$ServerURL;
require_once (__DIR__ . '/PHPMailer.php');
require_once (__DIR__ . '/SMTP.php');
require_once (__DIR__ . '/Exception.php');
$mail = new \PHPMailer\PHPMailer\PHPMailer();
$mail->SMTPDebug = $debug;
$mail->isSMTP();
$mail->SMTPAuth = true;
$mail->Host = $G['config']['smtp_address'];
$mail->SMTPSecure = 'ssl';
$mail->Port = $G['config']['smtp_port'];
$mail->CharSet = 'UTF-8';
$mail->FromName = $G['config']['sitename'];
$mail->Username = $G['config']['smtp_user'];
$mail->Password = $G['config']['smtp_pass'];
$mail->From = $G['config']['smtp_user'];
$mail->isHTML(true);
$mail->addAddress($tomail);
$mail->Subject = $title;
$mail->Body = $contant;
$mail->SMTPOptions = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
)
);
echo $mail->SMTPDebug;
$re = $mail->send();
if($re){
return true;
}else{
$msg = '邮件发送失败返回错误:'.$mail->ErrorInfo;
return false;
}
/*
foreach ($_POST as $v=>$value){
$_POST[$v] = urlencode($value);
}
$ev['server'] = urlencode($G['config']['smtp_address']);
$ev['port'] = urlencode($G['config']['smtp_port']);
$ev['username'] = urlencode($G['config']['smtp_user']);
$ev['password'] = urlencode($G['config']['smtp_pass']);
$ev['formname'] = urlencode($G['config']['sitename']);
$ev['tomail'] = urlencode($tomail);
$ev['subject'] = urlencode($title);
$ev['content'] = urlencode($contant);
//$post = "server={$ev['server']}&port={$ev['port']}&username={$ev['username']}&password={$ev['password']}&formname={$ev['formname']}&tomail={$ev['tomail']}&subject={$ev['subject']}&content={$ev['content']}";
$back = curl_request($ServerURL.'?mod=sendmail&domain=' . urlencode($_SERVER['HTTP_HOST']) . '&sitekey=' . $G['config']['sitekey'], $post);
if (!$json = json_decode($back, true)) {
$msg = '返回数据解析失败:'.$back;
return false;
} else {
if ($json['code'] != 1) {
$msg = $json['msg'];
return false;
}
$msg = 'success';
return true;
}
*/
}
function show_page_404()
{
die('404 您访问的页面不存在!');
}
function SendTipsMail($tomail,$content,&$BackMsg,$debug = false){
global $G,$MailTipsCon,$MailTipsFram,$MailTipsLine;
$c = '';
foreach ($content as $array){
$con = '';
foreach ($array['content'] as $line){
$con .= str_replace('[content]',$line,$MailTipsLine);
}
$c .= str_replace('[content]',$con,str_replace('[title]',$array['title'],$MailTipsCon));
}
$html = str_replace('[content]',$c,$MailTipsFram);
return sendemail('系统提醒 - '.$G['config']['sitename'],$html,$tomail,$BackMsg,$debug);
}
function GetOriginText($origin){
if ($origin == 1){
return '管理后台';
}else if ($origin == 2){
return '前台购买';
}else if ($origin == 3){
return '套餐卡密';
}else if ($origin == 4){
return '代理中心';
}else{
return '未知来源';
}
}