技术干货 | 记一次详细的代码审计

admin 2022年3月28日09:24:46评论67 views字数 5722阅读19分4秒阅读模式

本公众号发布的文章均转载自互联网或经作者投稿授权的原创,文末已注明出处,其内容和图片版权归原网站或作者本人所有,并不代表安世加的观点,若有无意侵权或转载不当之处请联系我们处理,谢谢合作!


欢迎各位添加微信号:asj-jacky

加入安世加 交流群 和大佬们一起交流安全技术



前言

本篇是对极致CMSv1.7漏洞的一些新的发现,先从MVC开始到漏洞的发掘利用

01

MVC篇

首先,先打开index.php

<?php
// +----------------------------------------------------------------------// | FrPHP { a friendly PHP Framework } // +----------------------------------------------------------------------// | Copyright (c) 2018-2099 http://frphp.jizhicms.com All rights reserved.// +----------------------------------------------------------------------// | Author: 留恋风 <[email protected]>// +----------------------------------------------------------------------// | Date:2018/02// +----------------------------------------------------------------------


// 应用目录为当前目录define('APP_PATH', __DIR__ . '/');
// 开启调试模式//define('APP_DEBUG', true);
//定义项目目录define('APP_HOME','Home');
//定义项目模板文件目录define('HOME_VIEW','template');
//定义项目模板公共文件目录define('Tpl_common','');
//定义项目控制器文件目录define('HOME_CONTROLLER','c');
//定义项目模型文件目录define('HOME_MODEL','m');

//定义项目默认方法define('DefaultAction','jizhi');
//取消logdefine('StopLog',false);
//定义静态文件路径define('Tpl_style','/static/');
// 加载框架文件require(APP_PATH . 'FrPHP/Fr.php');
// 就这么简单~

先是define定义,然后加载框架,所以直接去加载的框架看看,是再FrPHP/Fr.php,打开后,拖到最后,发现

技术干货 | 记一次详细的代码审计

先加载了配置文件,因为配置文件里是用单引号包裹,就没什么好看的。然后再会触发FrPHP的eun方法,跟进查看

技术干货 | 记一次详细的代码审计

前三个很简单,是配置数据库,和对传入的数据的简单处理,需要注意的是route()路由处理,对它进行跟进

技术干货 | 记一次详细的代码审计

在141行接收url传入的参数,然后在206行对控制器和方法名进行define赋值

技术干货 | 记一次详细的代码审计

然后在212行对传入的url进行处理,把输入的url前面传入的index.php先舍去

技术干货 | 记一次详细的代码审计

然后在231行开始,会对传入的url拆分,变为数组,如我url输入:index.php/Home/ce,会被拆分为Home和ce然后在243行赋值给控制器名和方法名

技术干货 | 记一次详细的代码审计

但是在257行会先判断插件中是否存在该控制器,但是该目录默认为空,所以会先进入if判断中,然后再进入263行,判断在默认文件中是否存在该控制器,即在Homec中是否存在,若是不存在则会把控制器名默认为Home,因为这个默认设置,所以后面会存在一个SQL注入

技术干货 | 记一次详细的代码审计

然后再调用我们指定的类的方法

技术干货 | 记一次详细的代码审计

02

漏洞篇

SQL注入

在HomecHomeController.php中的jizhi中

技术干货 | 记一次详细的代码审计

看到在101行存在把$url变为数组,然后取出进行sql注入。而$url则是由这里:

技术干货 | 记一次详细的代码审计

$url只是对url栏输入的进行接收,所以我们先来看下接收的方式:

技术干货 | 记一次详细的代码审计

看出只是对接收index.php后面的。回到第101行,看到会先以/来进行分割,然后取出数组的第一个进入find中,执行sql语句,所以我们考虑怎么对其进行攻击利用,但是我们不能更换/Home/否则会无法执行jizhi,但是我们回到MVC篇中看到的,在第264行中,我们可以看到如果我们输入的不存在,会默认controllerName为Home

技术干货 | 记一次详细的代码审计

所以这就意味着我们可以随意的写入,都会最终默认转换为Home,所以直接进行SQL注入:

技术干货 | 记一次详细的代码审计

然后因为没有过滤,就是正常的sql注入了,HomeController.php中的jizhi_details中还有一处sql注入,是通过传值来进行sql注入的,难度不打,有兴趣的可以自行去看看

03

XSS

1

漏洞在HomecUserController.php的release中,主要利用代码如下:

if($_POST){    $data = $this->frparam();    ...    $w = get_fields_data($data,$w['molds']);        switch($w['molds']){                case 'article':                    if(!$data['body']){                        if($this->frparam('ajax')){                            JsonReturn(['code'=>1,'msg'=>'内容不能为空!']);                        }else{                            Error('内容不能为空!');                        }                    }                    if(!$data['title']){                        if($this->frparam('ajax')){                            JsonReturn(['code'=>1,'msg'=>'标题不能为空!']);                        }else{                            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;            ...    }    ...    if($this->frparam('id')){                $a = M($w['molds'])->update(['id'=>$this->frparam('id')],$w);                if(!$a){                     if($this->frparam('ajax')){                        JsonReturn(['code'=>1,'msg'=>'未修改内容,不能提交!']);                    }else{                        Error('未修改内容,不能提交!');                    }                }                if($this->frparam('ajax')){                    JsonReturn(['code'=>0,'msg'=>'修改成功!','url'=>U('user/posts',['molds'=>$w['molds']])]);                }else{                    Success('修改成功!',U('user/posts',['molds'=>$w['molds']]));                }            }else{                $a = M($w['molds'])->add($w);                if(!$a){                    if($this->frparam('ajax')){                        JsonReturn(['code'=>1,'msg'=>'发布失败,请重试!']);                    }else{                        Error('发布失败,请重试!');                    }                }                if($this->frparam('ajax')){                    JsonReturn(['code'=>0,'msg'=>'发布成功!','url'=>U('user/posts',['molds'=>$w['molds']])]);                }else{                    Success('发布成功!',U('user/posts',['molds'=>$w['molds']]));                }            }}

很简单,就是先赋值给$data,然后$data会经过get_fields_data函数,该函数代码如下:

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

因为是先经过sql执行,所以可以构造$fields为空,所以可以直接把$data的值赋值给$w。

然后看到源代码的这一句$data['body'] = $this->frparam('body',4);,我们可以知道4是没有对html实体话的,所以可以对body进行XSS的注入,所以构造payload:

tid=2&article=asd&body=<script>alert(1)</script>&id=4&molds=article&title=qwe&sad=qwe
技术干货 | 记一次详细的代码审计

2

漏洞在HomecErrorController.php中

技术干货 | 记一次详细的代码审计

很简单,直接输入

?msg=<script>alert(1)</script>
技术干货 | 记一次详细的代码审计

任意文件删除

漏洞在AcSysController.php中的deletePicAll处:

技术干货 | 记一次详细的代码审计

首先,frparam函数功能是可以通过在浏览器传值进行对$data传值,该函数有一定过滤,在该处主要是对单引号何双引号过滤,然后后面会把$data数组中litpic检测是否为文件名,然后再删除文件。

首先,我们先来一段测试代码:

function ce(){        $data = $this->frparam('data',1);        if($data!='') {            $pictures = M('pictures')->findAll('id in(' . $data . ')');            var_dump($pictures);            $isall = true;
           foreach($pictures as $v){                if(strpos($v['litpic'],'http')===false){                    var_dump('.'.$v['litpic']);                }else{                    $isall = false;                }            }        }    }

然后先随意输入:

技术干货 | 记一次详细的代码审计

看到拼接后执行的语句,然后再来进行构造,因为过滤了单引号和双引号,所以用16进制,构造payload:

技术干货 | 记一次详细的代码审计

这样就能先实现任意文件删除

本文转自:先知社区


本文始发于微信公众号(安世加):技术干货 | 记一次详细的代码审计

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年3月28日09:24:46
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   技术干货 | 记一次详细的代码审计http://cn-sec.com/archives/517055.html

发表评论

匿名网友 填写信息