FLY-CRM-安全审计报告

16次阅读
没有评论

FLY-CRM 代码安全审计报告

审计对象: 07FlyCRM 客户管理系统(开源版)
审计日期: 2026-08-12
审计范围: s1/(模块化版, ThinkPHP 5.0.24, ~2288 个 PHP 文件: 框架层 + CRM/工单/办公用品/会议/多语言/微信/工作流/admin)、v1/(旧版整体式 DWZ, 157 个 PHP 文件)、v2/(旧版整体式 HAdmin, 304 个 PHP 文件, 含 wap 移动端)
审计性质: 项目所有者授权的源码审计(静态代码审计, 未进行实际攻击验证)
漏洞类型覆盖: 命令执行/RCE、反序列化、SQL 注入、未授权访问、硬编码凭据、文件上传/目录穿越、XSS、SSRF、越权/IDOR


一、总体结论

该系统存在多个严重级别的安全漏洞, 攻击链完整, 且漏洞呈系统性分布而非偶发:

  1. 认证体系可被整体击穿: 默认安装的 API 加密密钥(API_KEY/JWT_KEY)硬编码在公开源码与安装数据中, 攻击者可离线伪造任意用户的 JWT 令牌(payload 中 id=1 即管理员), 从而调用任意已登录接口; 部分 RPC 控制器连这层形同虚设的校验都没有, 甚至存在"自算 token 自校验"(CRM/工作流 RpcBase)与"认证被整段注释"(v2 wap、v1 WxApi、v2 后台)的硬性绕过。
  2. 越权/IDOR 是全模块通病: CRM、工单、办公用品、会议、工作流(审批/自定义表单/OA 单据)等模块约二十余个逻辑类均直接按请求 id 增删改查, 无任何数据归属校验; 组织级数据隔离开关(SYS_ORG_STATUS)默认关闭。
  3. SQL 注入多处可直接拖库: s1 的业绩统计、工单统计、扩展字段 DDL、自定义表单建表均将用户输入拼接进原生 SQL; v1/v2 旧版几乎全部业务类使用原始 $_POST/$_GET 拼接 SQL 且全局过滤被注释。
  4. XSS 系统性存在: s1 的 TP5 {$var} 模板与 baidu.template <%:= 均不做 HTML 转义; v1 的 Smarty #{$var}# 同样不转义。
  5. 文件上传/删除 → RCE 链路完整: 三套版本的 Upload 组件均保留上传文件原始扩展名、支持路径穿越、且可 unlink 任意文件; v1 安装向导与 v2 上传配合可未认证写配置/删锁 → 重装接管。
  6. v2 移动端(wap)认证整体失效: SYS_USER_ID 硬编码为 '1'(超级管理员)且不校验登录, 未认证即可访问全部移动端 CRM 数据。
  7. v1 旧版多处未认证高危入口: 安装向导 RCE、Data 类重建超管、登录页万能密码、15 个 CRM 业务文件 SQL 注入、上传类无鉴权——老代码整体处于可被未认证直接接管的状态。

⚠️ 框架层说明: 框架为 ThinkPHP 5.0.24, 经典 TP5 RCE 链(\think\Request/invokefunction)已被 s1/core/library/think/App.php:377-380 的控制器名正则 /^[A-Za-z](\w|\.)*$/ 阻断(反斜杠被拒绝), 未发现可利用的框架级 RCE。RCE 风险主要来自文件上传、升级包解压、lpt 语言文件写入。


二、风险总览

编号 版本 模块 漏洞类型 严重性 简评
CRIT-1 S1 全站 硬编码密钥 → 任意 JWT 伪造 🔴 严重 API_KEY/JWT_KEY 公开, 离线铸造管理员令牌
CRIT-2 S1 CRM 未授权访问(认证自绕过) 🔴 严重 RpcBase 自算 token 自校验, 匿名可达
CRIT-3 S1 微信 未授权 + 签名校验绕过 🔴 严重 伪造微信报文 → 注册/绑定/群发/泄露
CRIT-4 S1 多语言包 代码注入 + 路径穿越 → RCE 🔴 严重 写任意 PHP 文件, include 即执行
CRIT-5 V1 全站 SQL 注入泛在 + 未授权 + 硬编码 🔴 严重 老代码整体无防护
CRIT-6 V2 wap 未授权(认证失效) 🔴 严重 SYS_USER_ID=1, 匿名管理员级访问
CRIT-7 V2 上传 未授权任意文件上传 → RCE 🔴 严重 保留扩展名, 无鉴权
CRIT-8 V1 安装 安装向导 RCE(Config 写入注入) 🔴 严重 锁检查引用不存在的文件, 向导恒可达
CRIT-9 V1 Data 未授权重建超级管理员 🔴 严重 upgrade() 重置 id=1 并全量授权
CRIT-10 V1 登录 SQL 注入(未认证入口) 🔴 严重 万能密码登录任意账号
CRIT-11 V2 会员 仅凭手机号重置登录/支付密码 🔴 严重 forget_password 无验证码
HIGH-1 S1 CRM SQL 注入(业绩统计) 🟠 高 sys_user_id 直拼原生 SQL
HIGH-2 S1 公共层 SQL 注入(DDL 原语) 🟠 高 TableField 拼接 ALTER TABLE, 30+ 调用方
HIGH-3 S1 工单 SQL 注入(统计) 🟠 高 sys_user_id 直拼 IN()
HIGH-4 S1 CRM 越权/IDOR(客户全流程) 🟠 高 任意 id 删改/分配/转状态
HIGH-5 S1 CRM 越权(列表数据范围) 🟠 高 sys_user_id 覆盖当前用户
HIGH-6 S1 CRM 越权(setField 任意字段) 🟠 高 任意记录任意字段改写
HIGH-7 S1 CRM 存储型 XSS 🟠 高 {$var} 与 <%:= 均不转义
HIGH-8 S1 CRM 文件上传路径穿越/任意删除 🟠 高 穿越+保留扩展名+unlink
HIGH-9 S1 admin 未授权(rpc.Comm/CronJob) 🟠 高 匿名写文件/读消息/群发
HIGH-10 S1 admin 越权(allow_url 子串放行) 🟠 高 低权限调用任意插件方法
HIGH-11 V1 上传/文件包含 任意文件上传 + 目录穿越 🟠 高 组合可 RCE
HIGH-12 V1 全站 SQL 注入(列名/值) 🟠 高 searchKeyword/orderDirection 直拼
HIGH-13 V1 wxapp 未授权 🟠 高 Api 类认证被注释
HIGH-14 V2 多处 SQL 注入 🟠 高 前台/后台/未授权入口均有
HIGH-15 S1 工作流 越权(审批/流程接口未授权) 🟠 高 任意 wbhid 代审批/转交, RPC 泄露
HIGH-16 S1 工作流 SQL 注入(nid 建表 DDL) 🟠 高 自定义表单表名注入
HIGH-17 S1 工作流 越权(DiyData/Oa* 系列) 🟠 高 任意行增删改/伪造归属
HIGH-18 V1 权限体系 SQL 注入(User/Role/Menu/Method) 🟠 高 可提权/重置口令/篡改授权名单
HIGH-19 V1 CRM SQL 注入(15 个业务文件) 🟠 高 全量客户/合同/订单可拖库
HIGH-20 V2 后台登录 SQL 注入(认证绕过) 🟠 高 万能密码登录管理员
HIGH-21 V2 会员 明文口令 Cookie + 整行泄露 🟠 高 print_r 输出含 password
HIGH-22 V2 SmsMb 未授权 + SQL 注入 🟠 高 鉴权被注释, $_GET id 直拼
HIGH-23 V2 架构 数据库层零转义/零参数化 🟠 高 全部查询无预处理, 注入根因
MED-1 S1 admin 暴力破解(无验证码/无锁定) 🟡 中 api.Login 无限爆破
MED-2 S1 全站 CSRF(无令牌) 🟡 中 后台变更操作均无 token
MED-3 S1 工单/OA/会议 越权(任意字段修改) 🟡 中 set_field 无白名单无归属
MED-4 S1 办公用品/会议 越权/IDOR 🟡 中 增删改查无归属校验
MED-5 S1 微信 任意文件读取(路径穿越) 🟡 中 image_file 穿越读取本地文件
MED-6 S1 CRM 任意文件解析(导入) 🟡 中 pathname 可控, PHPExcel 解析
MED-7 S1 多语言包 硬编码百度翻译 Key 🟡 中 正式 Key 公开在源码
MED-8 S1 admin 升级包路径穿越/解压覆盖 🟡 中 需管理员
MED-9 V1 全站 硬编码 DB root/root + SMTP 🟡 中 随源码分发
MED-10 S1 工作流 存储型 XSS(审批意见/业务摘要) 🟡 中 自定义字段/反馈直拼 HTML
MED-11 V1 登录 反射型 XSS 🟡 中 登录失败 $_POST 回显模板
LOW-1 S1 全站 信息泄露(app_debug) ⚪ 低 生产开启 debug
LOW-2 S1 admin 开放重定向 ⚪ 低 referer_url 未校验
LOW-3 S1 公共层 弱口令哈希 ⚪ 低 md5(sha1(pwd)+固定盐)
LOW-4 V1 多处 存储型/反射型 XSS ⚪ 低 Smarty 无转义
LOW-5 全部 导出 Excel/CSV 公式注入 ⚪ 低 值以 = 开头被当公式
LOW-6 S1 多语言/会议 ORDER BY 注入/存储 XSS ⚪ 低 受限, 疑似
LOW-7 S1 公共层 潜伏 SSRF(无活动调用者) ⚪ 低 pictureDown 仅疑似
LOW-8 S1 CRM allow_url 白名单过宽 ⚪ 低 实际危害有限
LOW-9 S1 安装 重装接管(条件性) ⚪ 低 database.php 缺失即被重装
LOW-10 V1 Extend 二次 eval ⚪ 低 Tree.class.php, 疑似
LOW-11 V2 ueditor 未授权 + catchimage SSRF ⚪ 低 部分可利用
LOW-12 V1 缓存 缓存投毒 ⚪ 低 疑似

三、严重漏洞 🔴

CRIT-1 【S1·全站】硬编码 API/JWT 密钥 → 任意用户令牌伪造, 认证体系整体击穿

位置:

  • s1/app/install/data/install.sql:91,95
  • s1/app/common/behavior/InitBase.php:201-202
  • s1/app/common.php:374, 1164

证据:

-- install.sql:91
INSERT INTO `#@__config` VALUES ('57','api_key','0','API加密KEY',... ,'l2V|gfZp{8`;jzR~6Y1_','0');
-- install.sql:95
INSERT INTO `#@__config` VALUES ('61','jwt_key','0','JWT加密KEY',... ,'l2V|DSFXXXgfZp{8`;FjzR~6Y1_','0');
// InitBase.php:201-202
define('API_KEY', empty($api_key) ? 'LingQiFei' : $api_key);
define('JWT_KEY', empty($jwt_key) ? 'LingQiFei' : $jwt_key);
// common.php:374
function get_access_token(){ return md5('LingQiFei' . API_KEY); }
// common.php:1164
$key = API_KEY . JWT_KEY;               // JWT HS256 签名密钥
// common.php:1174
"exp" => TIME_NOW + 30*24*3600,          // 令牌 30 天有效

利用方式:

  1. access_token = md5('LingQiFei' . API_KEY) 可离线计算, 直接通过 ApiBase::checkAccessToken。
  2. user_token 用 HS256 签名, 密钥 = API_KEY . JWT_KEY(默认安装下两个值均为公开固定串), 攻击者可离线为任意用户 id(如 1=管理员)铸造 JWT。AdminApiBase 直接以解码 token 中的 id 作为 SYS_USER_ID 并判定 IS_ROOT。
  3. 结果是 api/ 下全部接口可以任意用户身份调用, 直接击穿本报告中的所有 API 层 IDOR 防护。

影响: 未认证或低权限攻击者获取管理员权限, 完全接管系统与全部业务数据。

修复: 安装时用强随机数生成 api_key/jwt_key 并强制要求修改; 移除 'LingQiFei' 回退; 密钥变更后旧令牌立即失效; 密钥不应出现在公开仓库。


CRIT-2 【S1·CRM】RpcBase 认证自绕过 → 完全未授权访问

位置: s1/app/crm/controller/rpc/RpcBase.php:45-47

证据:

// 检查AccessToken
$this->param['access_token'] = get_access_token();  // 先把自己算的 token 覆盖请求值
$this->logicApiBase->checkAccessToken($this->param); // 校验必然成功

利用方式: 先强制把 access_token 覆盖为服务端计算值再校验, 校验结果恒为成功, 等于没有认证。继承该基类的 rpc 控制器(如 app/crm/controller/rpc/Customer.php::recover 公海回收)可被任何未登录访客直接调用, 触发批量数据变更。

影响: 完全未授权的数据变更入口。

修复: 不要覆盖客户端传入的 access_token; 移除该逻辑或改为从请求头读取并真正校验。


CRIT-3 【S1·微信】微信 Portal 未授权 + 微信签名校验绕过

位置: s1/app/weixin/controller/Portal.php:24, 56-63, 110-158

证据:

24  class Portal extends ControllerBase    // 未继承 WeixinBase, 无登录/RBAC 校验
56  public function index() {
58      if (!isset($this->param['echostr'])) {
59          $this->responseMsg();           // 不带 echostr 直接进消息处理, 不校验签名!
61      } else {
62          $this->valid();                 // 只有带 echostr 才调用 checkSignature()
110 public function responseMsg()          // 直接解析微信 XML 并处理, 全程无签名验证

利用方式: 微信消息推送路径(不带 echostr)完全不校验 sha1 签名, 攻击者可伪造任意微信 XML 报文 POST 到 /weixin/Portal/index, 驱动:

  • qrsceneRegLogin: 批量注册 lqf_<id>/123456 会员;
  • qrsceneBindSysUser: 把攻击者 openid 绑定到任意 sys_user, 之后扫码登录;
  • custom/template: 向任意 openid 群发消息;
  • get_access_token/get_user_list: 泄露 AccessToken 与粉丝列表。

影响: 全站最高危入口之一, 伪造微信回调可造成账号接管、消息轰炸、敏感信息泄露。

修复: Portal 应继承 WeixinBase; responseMsg/index 强制校验 checkSignature() 并拒绝非微信来源 IP; 管理类 action 增加登录+菜单权限校验。


CRIT-4 【S1·多语言包】语言文件 PHP 代码注入 + 路径穿越 → RCE

位置:

  • s1/app/lpt/common.php:126-165(writeConfigFile / arrayToString)
  • s1/app/lpt/logic/LptLanguageTool.php:34-58, 223-268

证据:

// common.php:138
$newContent = "<?php\nreturn " . $configString . ";\n";
// common.php:159
$output .= $indentStr . sprintf("'%s' => '%s',\n", $key, $value);   // 单引号未转义!
// LptLanguageTool.php:244
$langFile = $langDirectory . $LangTagName . '.php';                 // $LangTagName 可含 ../ 穿越
// LptLanguageTool.php:262
writeConfigFile($langFile, $newTagArray);

利用方式(组合链):

  1. 值来源为数据库 lpt_language_tag 表, LptLanguageTag.php:131-161 将用户 value 原样入库(无过滤);
  2. arrayToString() 拼接键值不转义单引号, 值含 ', 1); phpinfo(); // 时生成的 *.php 语言文件即为可执行后门;
  3. $LangTagName/$moduleName 可含 ../../../../public/shell 穿越到任意目录写 PHP 文件;
  4. 框架 include 语言文件即触发执行。

影响: 拥有 lpt 菜单权限的任意授权用户(非管理员)即可实现远程代码执行。

修复: 键值用 var_export/addslashes 转义; module_name/language_tag 白名单(/^[a-z_]+$/i)并禁止 ../; 写入前 realpath 校验。


CRIT-5 【V1·全站】老版本整体无防护: SQL 注入泛在 + 未授权 + 硬编码凭据

位置: v1/Crm/Action/*、v1/Framk/*(见各条目)

要点:

  • 全局输入过滤被注释: v1/Framk/_Function.php:24-26 对 $_POST/$_GET/$_COOKIE 的统一过滤被整段注释, 残留的 common_htmlspecialchars() 是 return $str 空函数 → 全部 $_POST 直拼 SQL。
  • SQL 注入泛在: 用户/角色/菜单/方法/客户/系统配置等业务类均原始拼接(见 HIGH-11/12):
    • v1/Crm/Action/User.class.php 用户管理: $sql 拼接 $_POST, 可提权/重置任意账号密码;
    • v1/Crm/Action/Role.class.php:66,101,122 角色管理 SQL 注入(权限核心表);
    • v1/Crm/Action/Menu.class.php:52、Method.class.php:54 授权名单表注入;
    • v1/Crm/Action/Sys.class.php:48,114、SysData.class.php:113 配置/表结构注入。
  • 硬编码凭据: v1/Crm/Config/Config.php:30-31 数据库 root/root 随源码分发; v1/Crm/Action/EmailSend.class.php:962 SMTP 654062906@139.com / 07fly123 硬编码。
  • 安装向导无保护: v1/install/ 无 lock 校验, 任何人可重新安装覆盖系统、注册新管理员。

影响: 默认部署下数据库弱口令 root/root + 泛在 SQL 注入 + 可重装, 系统完全失守。


CRIT-6 【V2·wap】移动端认证整体失效, 匿名以超级管理员身份运行

位置: v2/ERP/Action/wap/Auth.class.php:9-17

证据:

public function __construct() {
    //$this->check_login();                     // 登录校验被注释!
    $this->authorization();
    //@define( 'SYS_USER_ACCOUNT', $_SESSION["CRM"]["USER"]["account"] );
    @define( 'SYS_USER_ACCOUNT',$_SESSION["sys_user_acc"]);
    //@define( 'SYS_USER_ID', $_SESSION["CRM"]["USER"]["userID"] );   // 被注释
    define( 'SYS_USER_ID','1');                  // 硬编码为超级管理员 id=1!
    @define( 'SYS_USER_VIEW','1,4' );
    @define( 'SYS_CO_ID', '1' );
}

利用方式: check_login() 被注释, SYS_USER_ID 硬编码 '1'(超级管理员)。所有 wap/crm/*(11 个类)移动端端点经 _instance('Action/wap/Auth') 以超级管理员身份且不校验登录运行, 未认证即可访问/操作全部移动端 CRM 数据, 并可叠加下述 SQL 注入直接拖库。

影响: 匿名访问全部 CRM 移动端接口, 数据完全泄露。

修复: 恢复 check_login() 或改用服务端会话/令牌校验; 不得硬编码 SYS_USER_ID=1。


CRIT-7 【V2·上传】Upload 未授权任意文件上传 → RCE + 任意文件删除

位置: v2/ERP/Action/Upload.class.php:22-52, 68-76, 93-146

证据:

public function __construct() { $this->fp = _instance('Extend/File'); }   // 无任何鉴权
public function upload_images_editor() {
    $type = strstr($picname, '.');                  // 保留原始扩展名, 无白名单
    $pics = date("YmdHis") . $rand . $type;
    move_uploaded_file($files['tmp_name'], $pic_path);   // 移到 Web 可访问目录
}
public function upload_img_remove() {
    $imgfile = "." . $this->_REQUEST('imgfile');
    if($this->fp->unlink_file($imgfile)) { ... }     // 任意文件删除
}

利用方式: 类无任何鉴权, 上传 shell.php(保留 .php 扩展名)到 upload/images/<date>/ Web 可访问目录 → 未授权 RCE; upload_img_remove 的 imgfile 可控 → 任意文件删除。

修复: 上传类接入认证; 扩展名白名单 + MIME/内容校验; 删除文件 realpath 校验。


CRIT-8 【V1·安装向导】安装向导恒可达 → 写入 Config.php 注入 → 未认证 RCE

位置: v1/install/index.php:77-89, 229-274, 256-260; 配合 v1/index.php(无锁)

证据:

77  @include($rootpath . 'source/Config/Config.inc.php');   // v1/source 目录不存在 → include 失败
78  if(defined('FLYCRM')){ ...提示已安装... }               // 恒 false → 向导永远可达
86  $servername = isset($_POST['install']) ? trim($_POST['servername']) : 'localhost';
89  $dbpassword = isset($_POST['install']) ? trim($_POST['dbpassword']) : '';
...
256 'DBhost'    => '".$servername."',
259 'DBpsw'     => '".$dbpassword."',
271 $configfilenum = fopen($rootpath . "/Crm/Config/Config.php","w");
273 fwrite($configfilenum, $config_contents);

且 IsName/IsPass 校验(118-132 行)被整体注释。

利用方式: 仅 trim() 后直接拼进单引号 PHP 数组写入 Crm/Config/Config.php。POST servername 为 ';system($_GET[c]);// 闭合单引号即写入可执行 PHP; 配合 include 该配置即触发 完全未认证 RCE。安装表单还硬编码演示账号 admin/admin123456。

影响: 未认证远程代码执行, 完全接管服务器。

修复: 删除 v1/install/ 目录或加 lock 文件; 配置写入用 var_export; 恢复 IsName/IsPass 强校验。


CRIT-9 【V1·Data】Data 类未授权 → 重建超级管理员并全量授权

位置: v1/Crm/Action/Data.class.php:42-51(upgrade), 54-112(sys_role_init)

证据:

64  VALUES (1, 'SYS0001', '07fly', '07fly.com', '零起飞', '1', ...
79  $sqllist[]=" update `fly_sys_user` set roleID='1' where id='1'";

利用方式: 类无 __construct 鉴权; upgrade() 调 sys_role_init() 插入 id=1 超级管理员(07fly/07fly.com)并对 SYS_MENU/SYS_METHOD/SYS_AREA 全量授权。未认证访问即重置系统超级管理员并接管全部菜单权限。

影响: 未授权提权至系统最高权限。

修复: 为 Data 类补登录+超管鉴权; 移除/收紧 upgrade 类接口。


CRIT-10 【V1·登录】登录页 SQL 注入(未认证入口, 万能密码)

位置: v1/Crm/Action/Login.class.php:62(构造器 //_instance('Action/Auth') 被注释, 20 行)

证据:

62  $sql = "select * from fly_sys_user where account='$username' and password='$password'";

利用方式: 未认证登录入口, $username/$password 原样拼接, 单引号可逃逸, ' or '1'='1' -- 直接以首条用户(管理员)登录; 密码明文比对。登录失败时 $_POST 回显进模板另有反射 XSS(见 MED-11)。

影响: 未认证登录任意账号, 接管后台。

修复: 参数化查询 + 口令哈希比对; 登录页移除账号回显。


CRIT-11 【V2·会员】仅凭手机号重置登录/支付密码(无任何验证)

位置: v2/ERP/Action/home/Register.class.php:47-60(forget_password), 74-87(forget_password_pay)

证据:

60  $sql="update fly_member set password='$pwd' where mobile='$mobile'";
87  $sql="update fly_member set password_pay='$pwd' where mobile='$mobile'";

利用方式: Register 类无鉴权, forget_password 仅凭 POST mobile+pwd 直接 UPDATE, 无短信/原密码/图形验证码校验 → 重置任意手机号会员的登录密码与支付密码。api_sms_send(101-121)还直接返回短信验证码。注入面: $mobile/$pwd 原样拼接可再叠加 SQL 注入。

影响: 未认证接管任意会员账号(含商城资金相关支付密码)。

修复: 密码重置走短信验证码/原密码校验; 参数化 SQL; 删除/加固 api_sms_send 返回验证码的逻辑。


四、高危漏洞 🟠

HIGH-1 【S1·CRM】SQL 注入 —— 业绩统计(Workbench)

位置: s1/app/crm/logic/Workbench.php:471-486, 502-519; 入口 s1/app/crm/controller/Workbench.php:55-56

471  if(!empty($data['sys_user_id'])){
472      $user_ids=$data['sys_user_id'];                       // 用户可控, 未过滤
473      $where2user = " And owner_user_id in({$user_ids}) ";
479  $salcontract_sql = "SELECT {$statField},count(*) ... from {$sys_db_prefix}sal_contract
482      WHERE start_date>='$curr_years_start' ... {$where2user} GROUP BY xdate ORDER BY xdate asc";
486  $salcontract_list = Db::query($salcontract_sql);

触发: Workbench::index(listtype=kpistat&sys_user_id=1) UNION SELECT ..., 可报错/时间盲注拖库。需登录+工作台菜单权限(或配合 CRIT-1 伪造令牌)。

HIGH-2 【S1·公共层】SQL 注入 —— DDL 公共原语(TableField)

位置: s1/app/common/logic/TableField.php:33,68,103,147,158,184

33  $sql = "ALTER TABLE `$table` ADD COLUMN `$fieldname` varchar($maxlength) DEFAULT '{$default}' COMMENT '$desc'";
68  Db::execute($sql);

说明: $table/$fieldname/$maxlength/$default/$desc 全部裸拼接进 ALTER/DROP/CREATE。该逻辑被 30+ 个 CRM/业务逻辑类调用(动态建表/加字段), 已确认的调用方包括:

  • s1/app/crm/logic/CstFieldExt.php:62,85(扩展字段管理, 参数直接来自请求, max_length 为最直接注入点);
  • s1/app/workflow/logic/WorkflowDiyField.php:80,105,125(工作流自定义字段, 同源)。

影响: 数据库结构级 SQL 注入, 可执行任意 DDL, 属于系统级风险。

HIGH-3 【S1·工单】SQL 注入 —— 工单统计(Statistics)

位置: s1/app/workorder/logic/Statistics.php:55-57, 64-69, 107-109, 115-120, 160-162, 167-172

55  if(!empty($data['sys_user_id'])){
57      $where2user = " And deal_user_id in({$user_ids}) ";
64  $sql = "SELECT status,count(*) as value from {$sys_db_prefix}oa_service
66      WHERE create_time>=$start_time ... {$where2user} GROUP BY status";
69  $list = Db::query($sql);

触发: Web 端 workorder/controller/Statistics.php::show 与 API 端 api/Statistics.php::index 均可达。sys_user_id=1) or updatexml(1,concat(0x7e,(select password from sys_user limit 1)),1) -- 报错注入。

HIGH-4 【S1·CRM】越权/IDOR —— 客户全流程操作无归属校验

位置: s1/app/crm/logic/CstCustomer.php:166-182(del), 189-200(allot), 229-301(allotmore), 127-159(edit), 329-355(转公海/转私/转垃圾/保护), 383-409

166  public function cstCustomerDel($data = []){
168      if (!empty($data['id'])) { $where['id'] = ['in', $data['id']]; ... }
178      $result = $this->modelCstCustomer->deleteInfo($where, true);
189  public function cstCustomerAllot($data = []){
194      $where['id'] = ['in', $data['id']];
195      $updata['owner_user_id'] = $data['owner_user_id'];   // 把他人客户分配给自己
143  public function cstCustomerEdit($data = []){
        $result = $this->modelCstCustomer->setInfo($data);   // 全字段可改

说明: 删/改/分配/转公海/转私/转垃圾/保护均仅凭请求 id 直接操作, 无归属校验(即使对方是私海客户)。DATA_ORG_STATUS=false(InitBase.php:104)关闭了组织级数据隔离, 越权范围为全库。同类通病: CstClue.php(线索)、CstChance.php(机会)、CstTrace.php(跟进)、CstLinkman.php(联系人)、CstWebsite.php(网站维护)、SalContract.php:173-178,468-487(合同删改/改金额/改开票)。

HIGH-5 【S1·CRM】越权 —— 列表数据范围被用户参数覆盖

位置: s1/app/crm/logic/CstCustomer.php:525-553

525  $sys_user_id = !empty($data['sys_user_id']) ? $data['sys_user_id'] : SYS_USER_ID;
529  if ($data['listtype'] == 'selfson') {
530      $ids = $this->logicSysUser->getSysUserViewId($sys_user_id);  // 用传入 id 查其下级
551  if (isset($data['owner_user_id'])) {
553      $where['owner_user_id'] = ['=', $data['owner_user_id']];     // 精确过滤他人客户

说明: 以请求参数 sys_user_id 优先于当前登录用户, 可拉取目标人及其下属的客户列表; download(controller/CstCustomer.php:376-383)未传 listtype 时无任何范围过滤, 可直接导出全量客户。CstTrace.php:235、SalContract.php:354、CstChance.php 同款。

HIGH-6 【S1·CRM】越权 —— setField 任意字段更新(通用后门)

位置: s1/app/crm/logic/CrmBase.php:42-53

42  public function setField($model = null, $param = null){
48      $result = $obj->setFieldValue(['id' => (int)$param['id']], $param['name'], $param['value']);

说明: CrmBase 是所有 CRM 逻辑的基类, setField 允许按 id 更新任意字段(name/value 由用户指定, 无字段白名单、无归属校验), 如把他人客户 owner_user_id 改给自己。

HIGH-7 【S1·CRM】存储型 XSS(系统性)

位置:

  • s1/app/crm/view/cst_customer/show.html:184(及 show_public/show_rubbish、cst_clue/show.html 等, baidu.template <%:= %> 不转义输出)
  • s1/app/crm/view/cst_customer/detail.html:6,26,30,34,38,42(TP5 {$info.name} 等不转义)
184  <td width="100"><%:=data[i]['{$vo.field_name}']%></td>   <!-- baidu.template 不转义写法 -->
6    <h3>{$info.name}</h3>
26   {$info.linkman}

说明: 客户 name/linkman/mobile/address/remark 及扩展字段均为用户可控, 列表/详情页原样回显。app/crm 下 grep 不到任何 htmlspecialchars 使用, 55 个 list 模板 + 若干 detail/add/edit 同款, 属系统性存储型 XSS。可窃取后台会话执行任意操作。

HIGH-8 【S1·CRM】文件上传路径穿越 + 任意文件删除

位置: s1/app/crm/logic/Upload.php:39-46,77-88,100-110 + s1/app/crm/controller/CstCustomer.php:420-444(upload_public)

Upload.php:40  $this->files_path = $this->files_path . $data['uploadfilepath'] . DS;  // 直接拼接, 可含 ../
Upload.php:44  $object = $object_info->move($this->files_path, '');                   // 保留原始文件名+扩展名, 无校验
Upload.php:82  unlink($data['pathname']);                                            // 任意文件删除

说明: upload_public 透传 uploadfilepath, 可 ../ 穿越向任意目录写文件(如向 public/ 写入 .php 马); del 分支可直接 unlink 任意文件(如删除 app/database.php 触发重装)。配合 CRIT-1 伪造令牌影响更大。

HIGH-9 【S1·admin】未授权访问 —— rpc.Comm / rpc.CronJob 匿名可达

位置: s1/app/admin/controller/rpc/Comm.php:19,27-53、s1/app/admin/controller/rpc/CronJob.php:19,28-100

19  class Comm extends ControllerBase          // 无任何鉴权
29  $filename = PATH_DATA . 'column.json';
53  file_put_contents($filename, $newContent); // 任意 JSON 写入
19  class CronJob extends ControllerBase
32  $where['deal_user_id'] = $this->param['deal_user_id'];   // 任意用户读消息
89  public function sys_msg_send(){ $this->logicSysMsg->sysMsgSend(); }

说明: 两个 RPC 控制器继承 ControllerBase(无登录/权限校验, 未像 RpcBase 那样调用 checkAccessToken), 通过 ?s=admin/rpc.Comm/getTableColumn、?s=admin/rpc.CronJob/sys_msg_send 匿名可达: 向 data/column.json 写任意 JSON、按任意 deal_user_id 读他人消息、系统消息/微信模板消息群发。代码注释中自带调用 URL。

HIGH-10 【S1·admin】越权 —— allow_url 子串放行 + 插件方法调用

位置: s1/app/install/data/install.sql:82 + s1/app/admin/logic/AdminBase.php:62-70

INSERT INTO ... ('33','allow_url',...,'0:file/pictureupload\r\n1:addon/execute\r\n2:admin/index/index\r\n3:admin/index/main','100');
64  foreach ($allow_url_list as $v) {
65      if (strpos(strtolower($v), strtolower($url)) !== false) { return $pass_data; }   // 子串匹配

说明: allow_url 内置 addon/execute, 且鉴权用 strpos 子串匹配(请求 URL 是白名单项的子串即放行)。任意已登录但无菜单权限的账号可访问 ?s=admin/addon/execute&addon_name=X&controller_name=Y&action_name=Z 调用任意插件控制器方法。

HIGH-11 【V1】任意文件上传 + 目录穿越 → 组合 RCE

位置:

  • v1/Crm/Action/Upload.class.php:54,60,87-93(strstr($picname, '.') 保留原始扩展名, move_uploaded_file 无白名单)
  • v1/Framk/Dispatcher.class.php:55-63,87,105(URL 段首字母小写即拼入 actionDir, .. 可穿越 → require_once 任意 .class.php 文件包含)
// Upload.class.php:54
$type = strstr($picname, '.');                       // 保留 .php
$pics = date("YmdHis") . $rand . $type;
move_uploaded_file($_FILES['mypic']['tmp_name'], $pic_path);
// Dispatcher.class.php:55-57
if($firstLetter == strtolower($firstLetter)){
    $this->actionDir .= $value . S;                  // `..` 首字母为 '.', 可作目录段
// 组合: 上传后门 → 通过目录穿越文件包含/直接访问执行

HIGH-12 【V1】SQL 注入 —— 列名/值/排序(多处)

位置: v1/Crm/Action/User.class.php:45、Customer.class.php:47、Sys.class.php:114、Role.class.php:66,101、Menu.class.php:52、Method.class.php:54

// User.class.php:45  (列名注入, 无需引号)
$where_str .= " and $searchKeyword like '%$searchValue%'";
// Role.class.php:66 (值注入)
$sql = "insert into fly_sys_role(sort,name,intro) values('$sort','$name','$intro')";
// Role.class.php:101 (更新)
$sql = "update fly_sys_role set name= '$_POST[name]', intro= '$_POST[intro]' where id='$id'";

说明: 用户/角色/菜单/方法/客户搜索等均原始拼接 $_POST/$_REQUEST(全局过滤已注释, 见 CRIT-5)。列名位置无需引号即可注入; 用户管理可提权为管理员、重置任意账号密码。

HIGH-13 【V1·wxapp】未授权访问 —— WxApi 认证被注释

位置: v1/Crm/Action/wxapp/WxApi.class.php:16-34,71-72

16  public function __construct() {
17      //$username = $this->_REQUEST("username");
18      //$password = $this->_REQUEST("password");     // 账号密码校验被注释
71  public function customer_show(){
72      $assArr = $this->L('Customer')->customer();     // 直接查全量客户

说明: /Api/customer_show 未授权即可拉取客户列表(对比其它 Wx* 类均保留 _instance('Action/Auth'), 唯独 Api 移除了认证)。

HIGH-14 【V2】多处 SQL 注入(含未授权入口)

位置:

  • v2/ERP/Action/wap/crm/CstCustomer.class.php:41-66(searchKeyword/name/orderDirection 直拼, wap Auth 失效 → 未认证注入)
  • v2/ERP/Action/home/Login.class.php:30-47(前台微信会员登录 $account/$password 拼接 + 密码明文入 cookie)
  • v2/ERP/Action/home/Api.class.php:22-51(构造器为空, 完全未授权, where id in ($id) 注入)
  • v2/ERP/Action/sysmanage/Login.class.php:54($account 拼接, md5($password))
  • v2/ERP/Action/admin/GoodsOrder.class.php:51-54(列名 searchKeyword 注入)
  • v2/ERP/Action/erp/FinReceRecord.class.php:45-48,62-63(值拼接 + orderDirection)
// wap/crm/CstCustomer.class.php:44,52,65
$where_str .= " and c.name like '%$searchKeyword%'";      // 未认证
$order_by .= " c.next_time $orderDirection";
$sql = "select c.* from cst_customer as c where $where_str $order_by limit $beginRecord,$pageSize";

HIGH-15 【S1·工作流】审批/流程接口越权 —— 任意审批 + RPC 泄露

位置:

  • s1/app/workflow/logic/WorkflowBusiness.php:172-198, 337-352(workflowBusinessApproval 按 wbhid 直接更新审批历史)
  • s1/app/workflow/controller/rpc/RpcBase.php:44-48(仅静态可预测 token 校验)
  • s1/app/workflow/controller/rpc/RpcApi.php:47-51, 60-66, 75-79(workFlowBusinessAdd / 按任意 business_id/id 读业务详情与审批历史)
// WorkflowBusiness.php:179,195
switch ($data['deal_status']) { case '1': $this->workflowBusinessApprovalPass($data); ... }
$result = $this->modelWorkflowBusinessHistory->updateInfo(['id' => $data['wbhid']], $up_his);

利用方式: 审批以请求 wbhid 定位记录, 从不校验当前用户是否为该记录办理人。任意登录用户 POST 任意 wbhid + deal_status 可代替他人通过/拒绝/转交审批(转交处 deal_user_id=$data['forward_user_id'] 可指任意人); RPC 端 workFlowBusinessAdd 接受任意参数发起流程、按任意 id 读取业务详情与审批历史, 无归属校验。

影响: 伪造审批结果、转交任意人、泄露审批链数据。

HIGH-16 【S1·工作流】SQL 注入 —— 自定义表单表名 nid 拼接

位置: s1/app/workflow/logic/WorkflowDiyForm.php:56-60

57  $data['ext_table'] = 'workflow_diy_' . $data['nid'];
60  $rtn = $this->tablefield->add_table(SYS_DB_PREFIX . $data['ext_table'], $this->getAddTableSql(...));

利用方式: nid 仅 require|unique 校验即拼接为 CREATE TABLE 表名。表名可控本身即高危设计(用他人 nid 建同名表/覆盖已有扩展表), 结合 TableField DDL 注入原语(HIGH-2)可达任意 DDL。

影响: 自定义表单建表流程表名/结构注入, 潜在 DDL 提权。

HIGH-17 【S1·工作流】越权/IDOR —— 自定义数据与 OA 单据全链无归属校验

位置:

  • s1/app/workflow/logic/WorkflowDiyData.php:159-165, 186-188, 198-203(增改删任意行, create_user_id/owner_user_id 可伪造)
  • s1/app/workflow/logic/OaLeave.php:69-81, 88-103(编辑/删除任意请假单, 连带删审批历史)
  • s1/app/workflow/logic/OaReimburse.php:105-113, 139-151(编辑/删除任意报销单, create_user_id 可伪造)
  • s1/app/workflow/logic/OaBorrow.php:211,232,280、OaBusinessTrip.php:216,237,282、OaConsult.php:217,238,284(按请求 id 直改 status)
  • s1/app/workflow/logic/WorkflowBusiness.php:52-122(apply_user_id 外部可控, 可伪造申请人)
// WorkflowDiyData.php:160-165
$adddata['create_user_id'] = !empty($data['create_user_id']) ? $data['create_user_id'] : SYS_USER_ID;
$result = Db::name($table['ext_table'])->insert($adddata);   // 全量写入 POST 字段
// OaBorrow.php:211
$result = $this->modelOaBorrow->setFieldValue(['id' => $data['id']], 'status', $data['status']);

利用方式: 批量赋值 + 主键 id 定位 + 无归属条件 → 任何登录用户可改/删/伪造他人请假单、报销单、借用单、自定义表单行, 并以他人名义发起审批流程。

HIGH-18 【V1】权限体系 SQL 注入 —— User/Role/Menu/Method(可提权)

位置: v1/Crm/Action/User.class.php:123-127(user_add), 146-151(user_modify)、Role.class.php:66,101,122、Menu.class.php:52-53、Method.class.php:54-55

125  values('$_POST[account]','$_POST

 此处含有隐藏内容,需要正确输入密码后可见!

','$_POST[name]',...); 146 $sql= "update fly_sys_user set name='$_POST[name]',password='$_POST

 此处含有隐藏内容,需要正确输入密码后可见!

',... roleID='$_POST[roleID]' where id='$id'";

利用方式: 14 个字段原始 $_POST 直拼可报错注入拖库; user_modify 的 id 可指定任意账号并把 roleID 改为管理员(提权), 密码明文入库; Role/Menu/Method 可篡改 fly_sys_method(NEED 授权名单)进一步扩大越权面。

HIGH-19 【V1】CRM 业务模块 SQL 注入(15 个文件全链)

位置: v1/Crm/Action/ 下 Customer.class.php:165-170、CstChance:118-123、CstService:104-107、CstTrace:122-125、CstTalk:74-77、CstFiling:98-101、CstLinkman:88-93、CstWebsite:113-122、CstDict:60-61、SalOrder:155-159、SalContract:162-167、PosOrder:115-120、Supplier:116-119、Product:88-91、FinBankAccount:51-52

// CstService.class.php:104-107
$sql = "insert into cst_service(cusID,services,servicesmodel,linkmanID,bdt,tlen,status,content,intro,adt,create_userID)
        values('$cusID','$services','$servicesmodel','$linkmanID','$_POST[bdt]','$_POST[tlen]',...);";

利用方式: 业务模块普遍绕过 _REQUEST() 直接使用原始 $_POST(单引号完整逃逸), 可拖取全部客户/合同/订单/供应商/银行账户或改库; 各 _modify/_del(where id in ($id))同型。

HIGH-20 【V2·后台】登录 SQL 注入 → 认证绕过

位置: v2/ERP/Action/sysmanage/Login.class.php:22-24(认证注释), 54

22  public function __construct()
23      //$this->auth=_instance('Action/sysmanage/Auth');   // 认证被注释
54  $sql = "select * from fly_sys_user where account='$account' and password='" . md5($password) . "'";

利用方式: $account 经 _REQUEST() 过滤(#()-- 被删但单引号保留), 提交 ' or '1'='1 + 空密码可绕过登录; 登录后 $_SESSION["sys_user_id"] 置为首条用户(管理员)id。_REQUEST 过滤规则见 HIGH-23。

HIGH-21 【V2·会员】登录后明文口令入 Cookie + 整行口令泄露

位置: v2/ERP/Action/home/Login.class.php:30, 40-44, 46、v2/ERP/Action/home/WxMember.class.php:28-40

// Login.class.php:40-41  —— 明文口令直接写入 Cookie
setcookie("member_acct",$one["account"],time()+360000,"/");
setcookie("member_pwd",$one["password"],time()+360000,"/");
// WxMember.class.php:30-32 —— 整行记录 print_r 输出, 含 password/password_pay
$sql="select * from fly_member where mobile='$mobile'";
print_r($one);

利用方式: 会员密码明文比对(无 md5), 登录后明文口令写 Cookie(任何 XSS/同域脚本可窃取); member_mobile_isexit 对任意 mobile 查询并 print_r 输出整行(含登录/支付密码), 可枚举手机号批量抓取。

HIGH-22 【V2·SmsMb/Sms】未授权 + SQL 注入全链

位置: v2/ERP/Action/sysmanage/SmsMb.class.php:10(认证注释), 26, 58, 91, 101, 130、Sms.class.php:87

26  $where_str .=" and $searchKeyword like '%$searchValue%'";   // 列名+值拼接
91  ... where id=".$_GET['id'];                                 // 原生 $_GET 直拼 UPDATE
101 $sql="delete from fly_config_sms_mb where id in (".$id.")";

利用方式: 构造器认证被注释(任意访客可达), searchKeyword(列名位)、tpl_id、$_GET['id'] 直接拼 SQL; id 含 1) or 1=1 -- 批量删改短信模板。同型还有 admin/Notice.class.php:18-19 等十几个后台类认证被注释。

HIGH-23 【V2·架构】数据库层零转义/零参数化(全注入根因)

位置: v2/Framk/Database.class.php:42-65, 108-125, 174-185, 194-200, 354

$strSql .= ", `$key`='$value'";
$strSql = "UPDATE `$table` SET $strSql WHERE $where";
$result = $this->db->exec($strSql);

说明: 框架全部查询/更新方法对值零转义、零预处理; 配套 Framk/Action.class.php:39-53 的全局唯一"过滤器"只删 #()--(单引号/反引号保留), Framk/_Function.php 的 common_htmlspecialchars() 为空实现且全局过滤被整段注释。任何业务类把用户输入传入 findOne/update/insert/delete/execSql 即构成注入, 是 HIGH-14/20/21/22 全部注入的公共根因。


五、中危漏洞 🟡

MED-1 【S1·admin】暴力破解 —— API 登录无验证码/无锁定

s1/app/admin/controller/api/Login.php:26-46 中 checkAccessToken 被注释, 无图形验证码、无失败锁定/频率限制, 可无限爆破 admin 口令; 密码哈希为 md5(sha1(pwd).key)(见 LOW-3), 无 bcrypt 拉伸。

MED-2 【S1·全站】CSRF —— 后台变更操作无令牌

后台视图未使用 __token__, ControllerBase::_initialize(s1/app/common/controller/ControllerBase.php:30-42)无 CSRF 校验; TP5 _method 变量伪装(core/library/think/Request.php:524-525)可绕过 IS_POST 判断。已登录管理员被诱导访问恶意页面即可跨站执行配置修改/用户删除/口令重置。

MED-3 【S1·工单/OA/会议/微信】越权 —— 任意字段修改接口

  • s1/app/workorder/logic/WorkorderBase.php:30-40(setModelField)
  • s1/app/meeting/logic/MeetingBase.php:27-55(setField)
  • s1/app/weixin/logic/WeixinBase.php:27-70

set_field/set_visible/set_sort 类 action 遍布各模块, name/value/id 全由用户指定, 无字段白名单、无归属校验, 可将任意记录任意字段改掉。

MED-4 【S1·办公用品/会议】越权/IDOR

  • s1/app/ofsu/logic/OfsuOut.php:128-161,280-316(领料出库 删/改/导出 无归属校验, getOfsuOutListDown 可导出全量)
  • s1/app/ofsu/logic/OfsuPurchase.php、s1/app/meeting/logic/MetReserve.php 同类。

MED-5 【S1·微信】任意文件读取(路径穿越)

s1/app/weixin/model/WeixinReplyKey.php:67-104:$data['image_file'] 直接拼接 PATH_PICTURE 后读取并上传微信素材库, 构造 image_file=../../../../application/database.php 可把敏感文件(含数据库配置)内容外带。

MED-6 【S1·CRM】任意文件解析(导入)

s1/app/crm/logic/CstCustomer.php:639-708:pathname 完全由请求提供, 可指定服务器任意存在的 xls/xlsx 被 PHPExcel 解析; 旧版 PHPExcel 解析 XML 存在 XXE/公式注入面。

MED-7 【S1·多语言包】硬编码百度翻译 Key

s1/app/lpt/common.php:55-56:

$appid = "20211204001018767";    // 正式 API Key 公开在源码
$key   = "_dYZPAcOfjKk6zHUgmeR"; // 密钥

且使用 http:// 明文传输原文与签名。

MED-8 【S1·admin】升级包路径穿越/解压覆盖

s1/app/admin/logic/Upgrade.php:226-240:version 参数直接拼入 zip 路径(可含 ../), 解压后将目录整体 copy 到 ROOT_PATH 覆盖框架/应用文件 → 植入任意 PHP。需管理员权限。

MED-9 【V1】硬编码数据库/邮件凭据

  • v1/Crm/Config/Config.php:30-31:DBuser=root, DBpsw=root
  • v1/Crm/Action/EmailSend.class.php:962: SMTP 654062906@139.com / 07fly123
  • v2/Framk/_Config.php:26-30: DBuser=root, DBpsw=root

MED-10 【S1·工作流】存储型 XSS —— 审批意见/业务摘要/自定义表单值

  • s1/app/workflow/view/workflow_business_history/detail.html:99,131({$vo.suggestion_feedback} 原样输出, 任意审批人提交 <script> 载荷, 管理者查看审批记录即触发)
  • s1/app/workflow/service/WorkflowBusiness.php:158-161 + view/workflow_business/detail.html:6(业务摘要把自定义字段值——用户可填含富文本——直接拼进 summary_html 原样输出)
  • s1/app/workflow/view/workflow_diy_data/detail.html:9({$info[$vo.field_name]|default=''} 不转义)

MED-11 【V1·登录】反射型 XSS

v1/Crm/Action/Login.class.php 登录失败时 $_POST 参数原样回显进模板(login.html 及 Smarty #{$var}# 不转义), 未认证入口即可构造 ?account=<script>... 触发脚本执行。


六、低危漏洞 ⚪

编号 位置 说明
LOW-1 s1/app/config.php:20 app_debug => true 生产开启调试模式, 异常堆栈/SQL/配置(含密钥)随错误页泄露
LOW-2 s1/app/admin/controller/Login.php:42-43,83-84 开放重定向, referer_url 未校验协议/域名, 登录后跳转钓鱼站
LOW-3 s1/app/common.php:165-170 弱口令哈希 data_md5: md5(sha1($str).'lingqifei'), 默认盐硬编码, 无迭代拉伸
LOW-4 v1/Crm/View/templates/customer/customer_show_one.html:18,46,72 存储型 XSS, Smarty #{$one.name}# 等不转义
LOW-5 s1/app/extend.php:103、v1/v2 导出 Excel/CSV 公式注入, 值以 = + - @ 开头被 Excel 当公式执行
LOW-6 s1/app/lpt/logic/LptLanguageTag.php:106-122、s1/app/meeting/logic/MetReserve.php:118 ORDER BY 注入(受限, 框架 parseKey 严格模式部分缓解)/ 预订名称存储型 XSS(疑似)
LOW-7 s1/app/common/logic/File.php:311-326 潜伏 SSRF, pictureDown 从富文本提取 URL 用 file_get_contents 拉取, 当前无活动调用者
LOW-8 s1/app/crm/config.php:22 allow_url 白名单过宽(crm/comm/suggest_search;...;recover), 实际危害有限
LOW-9 s1/app/install/controller/Index.php:41-44 重装仅判断 app/database.php 是否存在, 该文件丢失即被重装接管(条件性)
LOW-10 v1/Crm/Extend/Tree.class.php:155-156 @extract($a); eval("\$nstr=\"$str\";") 二次 eval(疑似, 需先写库)
LOW-11 v2/ERP/Extend/ueditor/php/controller.php + config.json ueditor 未授权访问, catchimage 远程图片抓取 SSRF/内网探测(PHP 上传已被扩展名白名单阻断, 部分可利用)
LOW-12 v1/Framk/Cache.class.php:61,65-66 缓存投毒, 缓存文件按 SQL md5 命名、var_export 写盘(疑似, 叠加风险)

七、未发现项

  • 框架级 RCE(TP5.0): s1/core/library/think/App.php:377-381 控制器名正则 /^[A-Za-z](\w|\.)*$/ 已阻断经典 invokefunction/filter 注入链, 未发现可利用的框架级 RCE。
  • 直接命令执行调用: 三套版本业务代码中未发现 system/exec/shell_exec/passthru/proc_open 的直接调用; RCE 均经"文件上传 + include"或"配置文件写入 + include"链间接达成。
  • 反序列化 RCE: s1 仅 Service.php:53/ServiceBase.php:47 对数据库中的 driver config 调用 unserialize()(输入非请求直接参数); v1/v2 未发现用户输入直接进入 unserialize, 无 POP gadget 链。
  • s1 硬编码数据库口令: 仓库未提交 app/database.php(安装时生成); v1/v2 存在默认 root/root(已单列 CRIT-5/MED-9)。
  • CRM 模块 SSRF: app/crm 未检索到用户可控 URL 的服务端抓取原语; SSRF 潜伏点(File::pictureDown、v1 File::down_remote_file、v2 ueditor catchimage)均标注"仅疑似/潜伏"。

八、修复建议(按优先级)

P0 — 立即修复(认证/授权根因)

  1. 轮换并随机化 api_key/jwt_key, 删除 'LingQiFei' 回退(InitBase.php:201-202、install.sql:91,95); 密钥从数据库配置迁移到环境变量/加密存储。
  2. 修复 RpcBase 认证自绕过(crm/controller/rpc/RpcBase.php:45-47, workflow 同型 rpc/RpcBase.php:44-48), 改为从请求读取并真正校验 access_token, 绑定操作人身份。
  3. 微信 Portal 强制签名校验(weixin/controller/Portal.php), 继承 WeixinBase, 管理类 action 加登录+菜单权限。
  4. v2 wap 恢复 check_login(), 删除 SYS_USER_ID=1 硬编码(wap/Auth.class.php); v2 Upload 接入认证; v2 全部后台类补回被注释的 _instance('Action/Auth')。
  5. v1 删除 install/ 目录(或加 lock 保护), 关闭安装向导 RCE 面(CRIT-8); Data 类补鉴权(CRIT-9); 登录页参数化(CRIT-10); 全站恢复全局输入过滤。
  6. v2 会员密码重置加验证(home/Register.class.php, CRIT-11): 短信/原密码校验, 参数化 SQL, 删除返回验证码的接口。

P1 — 高优先级

  1. SQL 注入参数化: TableField.php(DDL)、Workbench.php、workorder/Statistics.php、WorkflowDiyForm.php(nid 建表)、v1/v2 全部 $_POST/$_GET 拼接点 → 改用参数化查询/字段白名单。
  2. 数据归属校验: 为 CRM/工单/办公用品/会议/工作流(审批 wbhid、DiyData、OaLeave/OaReimburse/OaBorrow 等)各 edit/del/allot/handle/set_field 增加创建人/办理人归属校验, 或开启 DATA_ORG_STATUS 组织级隔离; 拒绝客户端传入 sys_user_id/owner_user_id/create_user_id/apply_user_id。
  3. 上传安全: 扩展名/MIME 白名单 + 内容校验, realpath 校验路径防穿越, 删除任意文件逻辑加目录约束。
  4. lpt 语言文件: 值转义(var_export), module_name/language_tag 白名单防穿越, 移除硬编码百度 Key。

P2 — 中优先级

  1. API 登录限流 + 验证码 + 失败锁定(api/Login.php); 弱口令哈希升级为 password_hash/bcrypt。
  2. CSRF Token 全局启用(后台变更类操作)。
  3. app_debug 生产关闭, 错误信息脱敏。
  4. 轮换所有硬编码 SMTP/DB 凭据(139.com SMTP、root/root)。

P3 — 低优先级

  1. 模板输出统一转义(TP5 {$var} 用 {$var|htmlspecialchars}, baidu.template 用转义语法, v1 Smarty 开启自动转义)。
  2. referer_url 白名单校验; 导出函数对 =+-@ 开头值加前缀。
  3. ueditor catchimage 关闭或域名白名单; 移除 Tree 二次 eval; 加固重装检测(database.php 之外加独立锁)。

附录: 版本结构速查

FLY-CRM/
├── s1/   模块化版(推荐架构)  ThinkPHP 5.0.24  | app/{admin,common,crm,workorder,ofsu,meeting,material,lpt,weixin,workflow}
├── v1/   旧版整体式(DWZ)    PHP5.2+ | Crm/ + Framk/ + install/(无保护)
├── v2/   旧版整体式(HAdmin)  PHP5.4+ | ERP/ + Framk/ + wap/ + install/(有lock)
└── 附带: 企业服务行业CRM-业务流程.png、CRM2.jpg、安装说明.txt

本报告基于静态源码审计与调用链推演, 未对生产系统执行实际攻击验证。全部发现均逐行核对过源码行号; 部分"疑似/潜伏"项(如 LOW-6/LOW-10/LOW-12 及 SSRF 潜伏点)建议在部署环境做针对性验证后确认。审计覆盖: s1 框架层 + CRM/工单/办公用品/会议/多语言/微信/工作流/admin(含公共 TableField DDL 原语), v1(DWZ 全量重点 + 汇总), v2(HAdmin + wap + 会员商城, 全量重点 + 汇总)。

正文完
 0
评论(没有评论)