CVE-2023-1773 深度复现:从 REIM 回调未授权到信呼 OA 配置文件代码注入
一、漏洞入口根据 CVE 官网https://www.cve.org/CVERecord?idCVE-2023-1773的描述它将该漏洞归类为 RockOA 2.3.2 的webmainConfig.php相关代码注入漏洞一些漏洞数据库基本都复用了该表述但经源码审计漏洞根因更准确地说是配置保存逻辑未正确处理用户可控输入导致可控内容写入 PHP 配置文件并在后续包含加载时触发代码注入。真正的漏洞入口文件是webmain\system\cog\cogAction.php聚焦其中的savecongAjax()方法截取了关键部分public function savecongAjax() { if(getconfig(systype)demo)exit(演示上禁止设置); if($this-getsession(isadmin)!1)exit(非管理员不能操作); $str ?php if(!defined(\HOST\))die(\not access\); //[.$this-adminname.]在.$this-now.通过[系统→系统工具→系统设置]保存修改了配置文件 return array( .$str1. );; $bo file_put_contents($_confpath, $str); }首先这是需要管理员权限才能进行的操作为了使讲述逻辑更清晰先假设“已经登入了管理员账号”$this-adminname未经过滤直接拼接进了 PHP 源码当中若该对象用户可控将其改为\nphpinfo();//拼接之后得到的 PHP 代码就是?php if(!defined(\HOST\))die(\not access\); //[ phpinfo();//]在某时刻通过[系统→系统工具→系统设置]保存修改了配置文件 return array($str1);接着file_put_contents($_confpath, $str)这会将$str写入指定文件$_confpath当中。具体是哪个文件呢还是当前文件可以找到定义$_confpath $this-rock-strformat(?0/?1/?1Config.php, ROOT_PATH, PROJECT);追踪链条先确定$this-rock对象// include\Action.php $this-rock $GLOBALS[rock];取自全局变量$rock// config\config.php $rock new rockClass();查看类定义并且找到strformat方法public function strformat($str) { $len func_num_args(); $arr array(); for($i1; $i$len; $i)$arr[] func_get_arg($i); $s $this-stringformat($str, $arr); return $s; } public function stringformat($str, $arrarray()) { $s $str; for($i0; $icount($arr); $i){ $sstr_replace(?.$i., $arr[$i], $s); } return $s; }虽然strformat方法签名只写了一个形式参数但 PHP 允许你在调用时传入更多参数也就是我们看到的$this-rock-strformat(?0/?1/?1Config.php, ROOT_PATH, PROJECT);这时$str ?0/?1/?1Config.php后面的ROOT_PATH PROJECT不会丢失而是可以通过 PHP 的可变参数函数取出来源码中用到的就是$len func_num_args(); // 实际传入参数数量这里是 3 func_get_arg(0); // ?0/?1/?1Config.php func_get_arg(1); // ROOT_PATH func_get_arg(2); // PROJECT然后这段$arr array(); for($i1; $i$len; $i) { $arr[] func_get_arg($i); }创建了一个空数组接着将参数第 0 个参数除外一一放入数组当中。再传给$s $this-stringformat($str, $arr);带入具体参数即stringformat(?0/?1/?1Config.php, array(ROOT_PATH, PROJECT))这个方法实现的是替换操作即?0 - ROOT_PATH ?1 - PROJECT最终得到ROOT_PATH/PROJECT/PROJECTConfig.php这就是最终会被修改的文件。接下来就是找到ROOT_PATH PROJECT的具体指代了分别找到他们的定义define(ROOT_PATH,str_replace(\\,/,dirname(dirname(__FILE__)))); //系统跟目录路径 if(!defined(PROJECT))define(PROJECT, $rock-get(p, webmain));因此最终的结果就是web根目录/webmain/webmainConfig.phpwebmainConfig.php这个名字是不是很熟悉没错前面列举的漏洞数据库中的表述均提到了这个文件。二、webmainConfig.php这个文件为什么是关键呢上面我们提到file_put_contents函数会将一段未经过滤的数据写入webmainConfig.php文件中。现在假设$this-adminname这个用户真的可控那么一旦webmainConfig.php这个文件被其他文件包含即作为 PHP 代码使用而非文本就会出现安全问题。在 VScode 中进行搜索直接搜索文件名是找不到有用的结果的均只有注释的内容但是通过前面的分析我们知道$_confpath这个变量就是指代该文件再次搜索就可以找到在config\config.php文件中//引入配置文件 $_confpath $rock-strformat(?0/?1/?1Config.php, ROOT_PATH, PROJECT); if(file_exists($_confpath)){ $_tempconf require($_confpath); foreach($_tempconf as $_tkey$_tvs)$config[$_tkey] $_tvs; if(isempt($config[url]))$config[url] $rock-url(); if(!isempt($config[memory_limit]) function_exists(ini_set)) ini_set(memory_limit, $config[memory_limit]); if($config[timeout]-1 function_exists(set_time_limit))set_time_limit($config[timeout]); }require()就是 PHP 的文件包含函数之一。从文件名也可以看出这是一个配置文件也可能存在被其他文件包含的现象找到个最具代表性而且路由也方便的文件index.php这说明了只要访问index.php文件它就会包含webmainConfig.php。接下来就是要证明之前的假设$this-adminname用户可控成立了。三、$this-adminname之前看到的这段$str ?php if(!defined(\HOST\))die(\not access\); //[.$this-adminname.]在.$this-now.通过[系统→系统工具→系统设置]保存修改了配置文件 return array( .$str1. );;是在类cogClassAction之中这个类是Action的子类class cogClassAction extends Action在父类Action中可以看到定义以及相关方法public $adminid 0; public $adminuser ; public $adminname ; public $admintoken ; public $companyid 0; public $loadci 0; public $flow; protected $ajaxbool false; public function getlogin($lx0) { $this-ajaxbool $this-rock-jm-gettoken(ajaxbool, false); $this-adminid (int)$this-getsession(adminid,0); $this-adminuser $this-getsession(adminuser); $this-adminname $this-getsession(adminname); $this-admintoken $this-getsession(admintoken); $this-companyid $this-getsession(companyid); $this-setNowUser($this-adminid, $this-adminname, $this-adminuser); $agid $this-rock-get(agentid); if($agid!)$this-rock-savesession(array(wxqyagentid $agid)); $platsign $this-rock-get(platsign); if($platsign!)$this-rock-savesession(array(platsign $platsign)); if($lx0)$this-logincheck(); }聚焦$this-adminname $this-getsession(adminname);getsession在Action的父类mainAction有定义public function getsession($name,$dev) { return $this-rock-session($name, $dev); }$this-rock对象我们之前追踪过直接找到对应方法的定义public function session($name,$dev) { $val ; $name QOM.$name; if(isset($_SESSION[$name]))$val$_SESSION[$name]; if($this-isempt($val))$val$dev; return $val; }即可以表述成$this-adminname session(adminname, );只要在 SESSION 中定义了 name 字段那么$this-adminname就会被赋值成该字段的值。可是这个值一般都是在服务器端存储只做校验无法通过用户请求直接修改该值。四、SQLi这个 CVE 是由多个不安全操作共同导致的在文件webmain\task\api\reimplatAction.php中存在 SQL 注入if($msgtypeeditmobile){ $user arrvalue($data, user); $mobile arrvalue($data, mobile); $where user$user; $upstr mobile$mobile; $db-update($upstr, $where); $dbs m(admin); $dbs-update($upstr,$where); $uid $dbs-getmou(id,$where); m(userinfo)-update($upstr,id$uid); }聚焦$db-update()找到对应的链$db m(reimplat:dept); function m($name) { $cls NULL; $pats $nac ; $nas $name; $asq explode(:, $nas); if(count($asq)1){ $nas $asq[1]; $nac $asq[0]; $pats $nac./; $_pats .ROOT_PATH./.PROJECT./model/.$nac./.$nac..php; if(file_exists($_pats)){ include_once($_pats); $class .$nac.Model; $cls new $class($nas); } } $class .$nas.ClassModel; $path .ROOT_PATH./.PROJECT./model/.$pats..$nas.Model.php; if(file_exists($path)){ include_once($path); if($nac!)$class $nac._.$class; $cls new $class($nas); } if($clsNULL)$cls new sModel($nas); return $cls; }会包含下述两个文件Web根目录/webmain/model/reimplat/reimplat.php Web根目录/webmain/model/reimplat/deptModel.php并且返回$cls new sModel(dept); return $cls;即$db new sModel(dept);找到方法update()$this-db $GLOBALS[db]; public function update($arr,$where) { return $this-record($arr, $where); } public function record($arr, $where) { return $this-db-record($this-table, $arr, $where); }注意构造方法public function __construct($table) { $this-rock $GLOBALS[rock]; $this-db $GLOBALS[db]; $this-adminid $this-rock-adminid; $this-adminname $this-rock-adminname; $this-settable($table); $this-initModel(); }传输进来的“dept”是作为表名存在的。还需要找到全局变量$db的定义$this-db import(DB_DRIVE); $GLOBALS[db] $this-db;继续追踪DB_DRIVEdefine(DB_DRIVE, $config[db_drive]);继续追踪$config array( …… db_drive mysqli, //数据库操作驱动 …… );现在明白了 import 的实际上是$this-db import(mysqli);这个系统自己定义了import函数function import($name, $inbotrue) { $class .$name.Class; $path .ROOT_PATH./include/class/.$class..php; $cls NULL; if(file_exists($path)){ include_once($path); if($inbo){ $cls new $class(); } } return $cls; }所以实际包含的是Web根目录/include/class/mysqliClass.php找到对应的文件就可以看到record方法的定义了public function record($table,$array,$where) { $addbool true; if(!$this-isempt($where))$addboolfalse; $cont ; if(is_array($array)){ foreach($array as $key$val){ $cont.,$key.$this-toaddval($val).; } $cont substr($cont,1); }else{ $cont $array; } if($addbool){ $sqlinsert into $table set $cont; }else{ $where $this-getwhere($where); $sqlupdate $table set $cont where $where; } return $this-tranbegin($sql); }注意该方法并非直接在 mysqli.php 中而是在其父类中即文件include\class\mysql.php。整理一下得到$db-update($upstr, $where);等价于record($this-table, $upstr, $where);注意看其中的$sqlupdate $table set $cont where $where;没有过滤直接拼接了变量并且后续的 SQL 执行操作并没有涉及到防御return $this-tranbegin($sql); private function tranbegin($sql) { //if($this-errorbool)return false; if($this-conn null)$this-connect(); $this-iudcount; if(!$this-tran){ //$this-starttran(); //$this-trantrue; } $rsa $this-query($sql); $this-iudarr[]$rsa; if(!$rsa)$this-errorbool true; return $rsa; } public function query($sql, $ebotrue) { if($this-conn null)$this-connect(); if($this-conn null)exit(数据库的帐号/密码有错误!.$this-errormsg.); $sql trim($sql); $sql str_replace(array([Q],[q],{asqom}), array($this-perfix, $this-perfix,), $sql); $this-countsql; $this-sqlarr[] $sql; $this-nowsql $sql; $this-count 0; try { $rsbool $this-querysql($sql); } catch (Exception $e) { $rsbool false; $this-errormsg $e-getMessage(); } $this-nowerror false; if(!$rsbool)$this-nowerror true; $stabs .$this-perfix.log; if(!contain($sql, $stabs) !$rsbool)$this-errorlast $this-error(); //最后错误信息 //记录错误sql if(!$rsbool $ebo){ $txt [ERROR SQL].chr(10)..$sql..chr(10)..chr(10).[Reason].chr(10)..$this-error()..chr(10).; $efile $this-rock-debug($txt,.DB_DRIVE._sqlerr, true); $errmsg str_replace(,#39;, $this-error()); if(!contain($sql, $stabs)){ m(log)-addlogs(错误SQL,.$errmsg., 2, array( url $efile )); } } return $rsbool; } protected function querysql($sql){return false;}这就坐实了存在 SQLi。接下来只需要搞定$mobile变量的来源$mobile arrvalue($data, mobile);追踪$data变量$body $this-getpostdata(); $key $db-gethkey(); $bodystr $this-jm-strunlook($body, $key); $data json_decode($bodystr, true);可以看到通过 POST 接收数据后分别进行了解密和 JSON 反序列化操作。换言之在构造 POST 请求正文的时候先得进行 JSON 序列化操作然后再进行加密这样服务器端才能正常处理请求。先找到$this-jm对象$this-jm c(jm, true);继续追踪function c($name, $inbotrue, $param1, $param2) { $class .$name.Chajian; $path .ROOT_PATH./include/chajian/.$class..php; $cls NULL; if(file_exists($path)){ include_once($path); if($inbo)$cls new $class($param1, $param2); } return $cls; }带入具体参数$class jmChajian找到该类的定义就能找到对应的加密与解密方法public function strlook($data,$key) { if(isempt($data))return ; if($key)$key md5($this-jmsstr); $x 0; $len strlen($data); $l strlen($key); $char $str ; for ($i 0; $i $len; $i){ if ($x $l) { $x 0; } $char . $key[$x]; $x; } for ($i 0; $i $len; $i){ $str . chr(ord($data[$i]) (ord($char[$i])) % 256); } return $this-base64encode($str); } public function strunlook($data,$key) { if(isempt($data))return ; if($key)$key md5($this-jmsstr); $x 0; $data $this-base64decode($data); $len strlen($data); $l strlen($key); $char $str ; for ($i 0; $i $len; $i){ if ($x $l) { $x 0; } $char . substr($key, $x, 1); $x; } for ($i 0; $i $len; $i){ if (ord(substr($data, $i, 1)) ord(substr($char, $i, 1))){ $str . chr((ord(substr($data, $i, 1)) 256) - ord(substr($char, $i, 1))); }else{ $str . chr(ord(substr($data, $i, 1)) - ord(substr($char, $i, 1))); } } return $str; }不难发现这并不是标准的加密算法而是作者自己实现的。查看密钥来源public function gethkey() { $key $this-reimplat_huitoken; if(isempt($key))$key $this-reimplat_secret; if(isempt($key))$key $this-reimplat_cnum; return md5($key); }这三个属性reimplat_huitoken reimplat_secret reimplat_cnum是 REIM 平台参数。但是开发者在部署 OA 的时候并不一定采用该配套平台换言之这三个参数的值可能都为空那么返回值就是固定的了。代码可以精简成$key return md5()结果就是d41d8cd98f00b204e9800998ecf8427e综上在未配置 REIM 平台参数的部署状态下回调解密密钥退化为公开固定值这就是我们可以任意构造 POST 正文的关键。五、路由攻击链路很清楚了通过 SQLi 修改adminname接着通过savecongAjax()方法将修改后的数据写入webmainConfig.php文件中最后访问index.php就可以触发注入的代码。现在要解决的就是路由问题了即如何访问对应文件和方法。一般在index.php中会有对应逻辑?php include_once(config/config.php); $_uurl $rock-get(rewriteurl); $d ; $m index; $a default; if($_uurl ! ){ unset($_GET[m]);unset($_GET[d]);unset($_GET[a]); $m $_uurl; $_uurla explode(_, $_uurl); if(isset($_uurla[1])){$d $_uurla[0];$m $_uurla[1];} if(isset($_uurla[2])){$d $_uurla[0];$m $_uurla[1];$a $_uurla[2];} $_uurla explode(?,$_SERVER[REQUEST_URI]); if(isset($_uurla[1])){ $_uurla explode(, $_uurla[1]);foreach($_uurla as $_uurlas){ $_uurlasa explode(, $_uurlas); if(isset($_uurlasa[1]))$_GET[$_uurlasa[0]]$_uurlasa[1]; } } }else{ $m $rock-jm-gettoken(m, index); $d $rock-jm-gettoken(d); $a $rock-jm-gettoken(a, default); } $ajaxbool $rock-jm-gettoken(ajaxbool, false); $mode $rock-get(m, $m); if(!$config[install] $mode ! install)$rock-location(?minstall); include_once(include/View.php);可以看到还包含了一个文件include/View.php?php if(!isset($ajaxbool))$ajaxbool $rock-jm-gettoken(ajaxbool, false); $ajaxbool $rock-get(ajaxbool, $ajaxbool); $p PROJECT; if(!isset($m))$mindex; if(!isset($a))$adefault; if(!isset($d))$d; $m $rock-get(m, $m); $a $rock-get(a, $a); $d $rock-get(d, $d); define(M, $m); define(A, $a); define(D, $d); define(P, $p); $_m $m; if($rock-contain($m, |)){ $_mas explode(|, $m); $m $_mas[0]; $_m $_mas[1]; } include_once($rock-strformat(?0/?1/?1Action.php,ROOT_PATH, $p)); $rand date(YmdHis).rand(1000,9999); if(substr($d,-1)!/ $d!)$d./; $errormsg ; $methodbool true; $actpath $rock-strformat(?0/?1/?2?3,ROOT_PATH, $p, $d, $_m); define(ACTPATH, $actpath); $actfile $rock-strformat(?0/?1Action.php,$actpath, $m); $actfile1 $rock-strformat(?0/?1Action.php,$actpath, $_m); $actbstr null; if(file_exists($actfile1))include_once($actfile1); if(file_exists($actfile)){ include_once($actfile); $clsname .$m.ClassAction; $xhrock new $clsname(); $actname .$a.Action; if($ajaxbool true)$actname .$a.Ajax; if(method_exists($xhrock, $actname)){ $xhrock-beforeAction(); $actbstr $xhrock-$actname(); $xhrock-bodyMessage $actbstr; if(is_string($actbstr)){echo $actbstr;$xhrock-displayfalse;} if(is_array($actbstr)){echo json_encode($actbstr);$xhrock-displayfalse;} }else{ $methodbool false; if($ajaxbool false)echo .$actname. not found;; } $xhrock-afterAction(); }else{ echo actionfile not exists;; $xhrock new Action(); } $_showbool false; if($xhrock-display ($ajaxbool html || $ajaxbool false)){ $xhrock-smartydata[p] $p; $xhrock-smartydata[a] $a; $xhrock-smartydata[m] $m; $xhrock-smartydata[d] $d;

相关新闻

我用 zyplayer-doc 搭了一个个人知识库:从资料整理到 AI 问答的真实体验

我用 zyplayer-doc 搭了一个个人知识库:从资料整理到 AI 问答的真实体验

我用 zyplayer-doc 搭了一个个人知识库:从资料整理到 AI 问答的真实体验 以前我的资料很分散。 工作方案在在线文档里,技术笔记在 Markdown 文件夹里,PDF 在网盘里,截图在电脑桌面,合同和证件扫描件在另一个文件夹&…

2026/7/18 23:17:01阅读更多 →
如何5分钟完成Steam Deck多系统引导配置:可视化工具部署指南

如何5分钟完成Steam Deck多系统引导配置:可视化工具部署指南

如何5分钟完成Steam Deck多系统引导配置:可视化工具部署指南 【免费下载链接】SteamDeck_rEFInd Simple rEFInd install script for the Steam Deck (with GUI customization) 项目地址: https://gitcode.com/gh_mirrors/st/SteamDeck_rEFInd 还在为Steam De…

2026/7/18 23:15:01阅读更多 →
学术写作中的 RAG 技术应用:如何解决 AI 生成内容的文献引用问题

学术写作中的 RAG 技术应用:如何解决 AI 生成内容的文献引用问题

关键词RAG;学术写作;文献引用;AI 写作;PaperXie;自然语言处理一、问题背景:AI 写作的 “幻觉” 困境研究者使用原生大语言模型辅助论文撰写时,普遍存在核心缺陷:模型输出文本语句通顺…

2026/7/18 23:15:01阅读更多 →
2024年SAP实用网站资源大全与开发指南

2024年SAP实用网站资源大全与开发指南

1. SAP实用网站资源大全作为一名SAP顾问,我经常需要查阅各种官方文档和技术资料。经过多年实践,我整理出一套完整的SAP实用网站清单,这些资源覆盖了从基础学习到高级开发的各个层面。不同于网上那些过时的列表,以下都是2024年经过…

2026/7/19 1:03:15阅读更多 →
Linux Shell编程实战:需求驱动与自动化测试

Linux Shell编程实战:需求驱动与自动化测试

1. 需求驱动的Linux Shell编程概述在Linux系统管理和自动化任务处理中,Shell脚本是最直接有效的工具之一。不同于传统的教程式学习,需求驱动(Requirement-Driven)的Shell编程方法更注重从实际项目需求出发,通过解决具体问题来掌握核心技能。这…

2026/7/19 1:03:15阅读更多 →
Vercel 绑定自定义域名极简配置指南

Vercel 绑定自定义域名极简配置指南

Vercel 绑定自定义域名极简配置指南 将自定义域名绑定到 Vercel 部署的项目中,核心逻辑仅需两步:在 Vercel 声明域名,在域名服务商处指向 Vercel。以下是具体配置流程及常见避坑指南。 1. 在 Vercel 面板添加域名 进入 Vercel 项目管理后台&a…

2026/7/19 1:03:15阅读更多 →
需求驱动的Shell脚本开发实践与优化

需求驱动的Shell脚本开发实践与优化

1. 需求驱动的Shell编程理念在Linux系统管理中,Shell脚本一直扮演着不可替代的角色。但传统教学往往从语法开始,导致学习者陷入"知道命令却不会解决问题"的困境。需求驱动开发(Requirement-Driven Development)方法彻底改变了这一局面——它要…

2026/7/19 1:03:15阅读更多 →
Hibernate与NHibernate核心解析与实战对比

Hibernate与NHibernate核心解析与实战对比

1. Hibernate与NHibernate核心概念解析Hibernate和NHibernate作为业界主流的对象关系映射(ORM)框架,本质上解决了面向对象编程语言与关系型数据库之间的"阻抗不匹配"问题。我在实际项目中使用这两个框架已有8年时间,发现很多开发者对它们的理解…

2026/7/19 1:03:15阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/19 0:01:04阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/19 0:01:04阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/19 0:01:04阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/19 0:01:04阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/19 0:01:04阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/19 0:01:04阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/18 22:49:46阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/18 14:49:24阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/18 18:49:35阅读更多 →