lmxcms代码审计(PHP)

admin 2022年4月27日20:34:11评论51 views字数 10742阅读35分48秒阅读模式

前言

今天看到php的代码审计,忽然兴致使然,想从新学习一遍php的mvc代码审计,审计的是lmxcms,适合新手,采用的mvc架构

前台SQL注入(留言板)

位于留言板处,index.php?m=Book&a=index,对应的是Book控制器的index方法,寻找到Book控制器的index方法

public function index(){
    if(isset($_POST['setbook'])){//提交留言
        $data = $this->checkData();
        if($this->bookModel->add($data)){
            $this->setBookTime(); //存储提交时间
            rewrite::succ($this->l['book_ok']);
        }else{
            rewrite::error($this->l['book_error']);
        }
    }
    //判断是否调用留言数据
    if($GLOBALS['public']['isbookdata']){
            //判断是否只调用审核
            $where = '';
            if($GLOBALS['public']['bookDisplay']) $where = 'ischeck=1';
            $count = $this->bookModel->count($where);
            $page = new page($count,$GLOBALS['public']['booknum']);
            $data = $this->bookModel->getData($page->returnLimit(),$where);
            $this->smarty->assign('list',$data);
            $this->smarty->assign('num',$count);
            $this->smarty->assign('page',$page->html());
    }
    $this->smarty->display('book/index.html');
}

首先需要传入setbook参数,主要是看$data = $this->checkData();这个函数

private function checkData(){
    $arr['name'] = '';
    $arr['content'] = '';
    $arr['mail'] = '';
    $arr['tel'] = '';
    $arr['ip'] = getip();
    //验证短时间内过多留言
    if($this->bookModel->is_ip($arr['ip'],$this->config['book_out_time']) >= $this->config['book_out_time_num']){
        rewrite::error($this->l['book_outtime']);
    }
    $this->bookTime(); //验证提交间隔时间
    $_POST = filter_strs($_POST);
    $data = p(1,1,1); //验证前台数据  转义并且过滤非法字符
    $data = array_merge($arr,$data);   //合并arr和data数组
    if(!$data['name']) rewrite::js_back($this->l['book_name_must']);
    if(!$data['content']) rewrite::js_back($this->l['book_content_must']);
    //过滤html代码  预防XSS
    foreach($data as $k => $v){
        $data[$k] = string::delHtml($v); 
    }
    unset($data['setbook']);
    return $data;
}

$_POST用filter_strs方法进行处理,然后把p方法的值赋给$data,并且合并arr和data数组。先来看filter_strs方法

function filter_strs($data){
    if(!$data) return $data;
    if(is_array($data)){
        foreach($data as &$v){
            $v = filter_strs($v);
        }
    }else{
        $data = urldecode($data);   //url解码
        $data = strip_tags($data);  //去除html标签和php标记
        $data = str_replace('%','',$data);   //%替换为空
    }
    return $data;
}

判断

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;
}

我们调用的时候是传入三个1,对应的是post方法,然后经过filter_sql方法处理,并且遍历了data,对其值进行了addslashes处理,但是没有对他的键进行处理。先看filter_sql方法

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.'】数据非法');
                exit();
            }
        }
    }
}

过滤了一些关键字,并且结合前面说的addslashes方法,预防了我们的sql注入,但是遗漏的地方就是没有对键名进行过滤。看完了过滤,看看他具体怎么调用。

lmxcms代码审计(PHP)

获取到了

public function add($data){
    $data['time'] = time();
    return parent::addModel($data);
}

继续往下跟进

protected function addModel($data){
   return parent::addDB($this->tab[0],$data);
}

继续跟进

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)";
    echo $sql;
    $this->query($sql);
    return mysql_insert_id();
}

这里只对值进行了单引号的包裹,上面的echo sql是我加入的,为了输出掉哟的语句方便测试

lmxcms代码审计(PHP)

现在看是正常的,但是记不记得之前是有把arr和data数组合并的一个操作?我们可以尝试一下传入其他参数看看

lmxcms代码审计(PHP)

提示报错没有a这个参数,我们可以看一下数据表里具体有什么字段

lmxcms代码审计(PHP)

这里的ischeck是是否显示,我们直接选1,不然前台看不到,然后直接构造payload即可

lmxcms代码审计(PHP)

前台SQL注入(搜索框)

抓了一个搜索框的包,index.php?m=Search&a=index&classid=5&tem=index&field=title&keywords=1 ,应该是在Search控制器下的index方法

lmxcms代码审计(PHP)

这里数据是在check里进行验证的,直接跟进check方法

private function check(){
    //获取get数据
    $_GET = filter_strs($_GET);
    $data = p(2,1,1);
    $this->param['keywords'] = string::delHtml($data['keywords']);
    if(!$this->param['keywords'] && $this->config['search_isnull']){
        rewrite::error($this->l['search_is_keywords']);
    }
    $this->param['classid'] = (int)$data['classid'];
    $this->param['mid'] = (int)$data['mid'];
    if(!$this->param['classid'] && !$this->param['mid']) rewrite::error($this->l['search_is_param']);
    if($this->param['classid'] && !isset($GLOBALS['allclass'][$this->param['classid']])){
        rewrite::error($this->l['search_is_classid']);
    }
    if($this->param['mid'] && !isset($GLOBALS['allmodule'][$this->param['mid']])){
        rewrite::error($this->l['search_is_mid']);
    }
    $this->param['tem'] = $data['tem'];
    $this->param['field'] = $data['field'];
    $this->param['time'] = $data['time'] ? $data['time'] : $this->config['search_time'];
    $this->param['tuijian'] = $data['tuijian'];
    $this->param['remen'] = $data['remen'];
}

对get获取的数据进行了一系列操作,filter_strs具体的代码前面放了,这就不放了,然后依旧是p,防止了sql注入,必须要传入keywords,除了keywords之外,其他有几个参数是可以加的。知道了获取的参数以及过滤的方法之后,来看程序调用的过程

lmxcms代码审计(PHP)

getSerachField方法如下,主要是初始化了一些参数的值,来看下一个方法

public function getSerachField($arr){
    $arr['tem'] = $arr['tem'] ? $arr['tem'] : 'index';
    $arr['ischild'] = $arr['ischild'] ? true : false;
    $arr['field'] = $arr['field'] ? $arr['field'] : 'title';
    if($arr['time'])$arr['time'] = time() - $arr['time'] * 24 * 3600;
    return $arr;
}

searchCoutn方法如下

public function searchCoutn($searchInfo){
    $param = $this->sqlStr($searchInfo);
    $param['force'] = 'title';
    return parent::countModel($param);
}

跟进countModel

protected function countModel($param=array()){
    return parent::countDB($this->tab['0'],$param);
}

继续跟进

protected function countDB($tab,$param){
    $We = $this->where($param);
    $sql="SELECT count(1) FROM ".DB_PRE."$tab $We";
    $result=$this->query($sql);
    $data = mysql_fetch_row($result);
    $this->result($result);
    return $data['0'];
}

这里还是老方法,继续把他sql语句输出出来看看,当我们get传入如下参数时

index.php?m=Search&a=index&classid=5&tem=index&field=title&keywords=1&tuijian=2

sql语句如下

SELECT count(1FROM lmx_product_data  WHERE time > 1619446530 AND tuijian=2 AND classid in(11,12,13,14,5)  AND (title like '%1%')  ORDER BY id desc

构造payload如下

lmxcms代码审计(PHP)

这里奇怪的是我sleep(1)是十秒,不应该是1秒吗,0.1的话他就变成1秒了,很奇怪

后台任意文件删除

如下位置抓包

lmxcms代码审计(PHP)

POST /admin.php?m=File&a=delete HTTP/1.1
Host: lmxcms.com
Content-Length: 149
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://lmxcms.com
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://lmxcms.com/admin.php?m=File&a=imageMain&type=0
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: Hm_lvt_590a74fa0c262c903817f1bb7292db93=1650975259; Hm_lpvt_590a74fa0c262c903817f1bb7292db93=1650975259; PHPSESSID=jt97lo3n30a5fpehtj8upavr00; _dd_s=logs=1&id=72c3c0b9-9826-4e49-8501-83a901c685f7&created=1650977821212&expire=1650984809538
Connection: close

type=0&delImages=%E5%88%A0%E9%99%A4%E9%80%89%E4%B8%AD%E5%9B%BE%E7%89%87&fid%5B%5D=2%23%23%23%23%23%2Ffile%2Fslide%2F20140827%2F201408271523025580.jpg

位于File控制器的delete方法

public function delete(){
    if(!$_POST['fid']) rewrite::js_back('请选择要删除的文件');
    $this->fileModel->delete($_POST);
    addlog('删除文件、图片');
    rewrite::succ('删除成功');
}

跟进delete方法

public function delete($data){
    $param['where'][] = 'type='.$data['type'];
    foreach($data['fid'as $k => $v){
        $fileInfo = explode('#####',$v);
        $fid[] = $fileInfo[0];
        $path[] = trim($fileInfo[1],'/');
    }
    $fid = implode(',',$fid);
    $param['where'][] = 'fid in('.$fid.')';
    if(parent::deleteModel($param)){
        //删除文件
        foreach($path as $v){
            file::unLink(ROOT_PATH.$v);
        }
    }
}

没有啥过滤,只要value以#####开头即可,把刚刚包里的fid参数url解码,结果如下

fid[]=2#####/file/slide/20140827/201408271523025580.jpg

所以我们直接测试一下,在根目录下创建个test.txt,删除成功

lmxcms代码审计(PHP)

后台任意文件写入+读取

在后台模板管理处,对文件进行编辑抓包

GET /admin.php?m=Template&a=editfile&dir=default/error.html HTTP/1.1
Host: lmxcms.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://lmxcms.com/admin.php?m=Template&a=opendir&dir=default
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: Hm_lvt_590a74fa0c262c903817f1bb7292db93=1650975259; Hm_lpvt_590a74fa0c262c903817f1bb7292db93=1650975259; PHPSESSID=jt97lo3n30a5fpehtj8upavr00; _dd_s=logs=1&id=72c3c0b9-9826-4e49-8501-83a901c685f7&created=1650977821212&expire=1650985309347
Connection: close

dir参数我们手动修改成../index.php,发现可以读取文件,其实这里应该是可以编辑的。我们现来审计下源码

lmxcms代码审计(PHP)

editfile方法如下

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();
    }
    $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');
}

跟进put方法,直接调用file_put_contents把data写到文件里,那这里可以直接构造写入文件

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



-END-

lmxcms代码审计(PHP)如果本文对您有帮助,来个点赞在看就是对我们莫大的鼓励。lmxcms代码审计(PHP)



  推荐关注:





lmxcms代码审计(PHP)
弱口令安全实验室正式成立于2022年1月,是思而听(山东)网络科技有限公司旗下的网络安全研究团队,专注于网络攻防技术渗透测试硬件安全安全开发网络安全赛事以及网安线上教育领域研究几大板块。
团队社群氛围浓厚,同时背靠思而听(山东)网络科技有限公司旗下的“知行网络安全教育平台”作为社区,容纳同好在安全领域不断进行技术提升以及技术分享,从而形成良好闭环。

团队全员均持CISP-PTE(注册信息安全专业人员-渗透测试工程师)认证,积极参与着各类网络安全赛事并屡获佳绩,同时多次高水准的完成了国家级、省部级攻防演习活动以及相关重报工作,均得到甲方的一致青睐与肯定。

欢迎对网络安全技术感兴趣的你来加入我们实验室,可在公众号内依次点击【关于我们】-【加入我们】来获取联系方式~




原文始发于微信公众号(弱口令安全实验室):lmxcms代码审计(PHP)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月27日20:34:11
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   lmxcms代码审计(PHP)http://cn-sec.com/archives/952826.html

发表评论

匿名网友 填写信息