PbootCMS代码审计
PbootCMS安装
我们将站点文件解压到www目录下,在phpstudy里添加相应站点,最后访问站点即可,不过这里出现了问题,这里我们需要打开站点对应版本的php.ini,将sqlite3
扩展前面的注释取消即可
我们这里的版本是php5.5,修改代码如下
接着我们可以正常访问网站首页
不过这里我们还是使用mysql进行测试,首先我们需要新建一个数据库pbootcms
,接着在网站的static\backup\sql
目录下有一个sql文件,我们在刚创建的数据库里导入,最终数据库情况如下
数据库处理完毕之后我们还需要在在代码中配置数据库信息
在代码审计之前我们还需要熟悉一下站点的目录结构
PbootCMS-V1.2.1
├─ apps 应用程序
│ ├─ admin 后台模块
│ ├─ api api模块
│ ├─ common 公共模块
│ ├─ home 前台模块
├─ config 配置文件
│ ├─ config.php 配置文件
│ ├─ database.php 数据库配置文件
│ ├─ route.php 用户自定义路由规则
├─ core 框架核心
│ ├─ function 框架公共函数库
│ │ ├─ handle.php 助手函数库1
│ │ ├─ helper.php 助手函数库2
├─ template html模板
├─ admin.php 管理端入口文件
├─ api.php api入口文件
├─ index.php 前端入口文件
熟悉mvc框架比较重要的是熟悉其路由方式
在pbootcms.com\apps\common\route.php
目录下,我们可以自定义路由
其中的home在根目录下的index文件中进行了绑定,当我们访问index.php时就会访问home目录
同样的admin也在根目录下的admin.php文件中进行了绑定
这里我们拿到一个前端页面的url进行分析,站点的联系我们页面http://pbootcms.com/index.php/about/11
,这里的about调用的路径如下,其中scode是传递的参数
我们也可以在AboutController.php中自定义方法
接着我们添加路由,让我们在前台可以调用该方法,这里我们不传递参数所以就没有写scode
接着而我们在前台进行访问,成功输出AboutController
刚才我们使用的是自定义路由的方式,还有一种路由方式,我们在路由文件中可以看到,有些路径没有再路由文件中添加路由
我们可以看到,message没有再前端路由中添加,为了测试我们就在MessageController.php
中写一个自定义函数
然后我们在前端页面中访问http://pbootcms.com/index.php/Message/test2
,这种方式为动态路由
PbootCMS内核分析
我们方便测试,我们在数据库中新建一个表
在表内添加数据
接下来我们就测试一下对数据库的增删改查,我们还是在MessageController中进行测试
在进行数据库操作之前我们还需要了解一些参数的函数,最基本的接收参数传递方法($_GET等)不安全,基本都不使用,这里使用框架中的参数传递方法
接着我们在前端访问message的test2方法并传递参数,查看结果
接着我们尝试传递特殊字符,通过返回的结果我们可以知道,框架中自带的参数传递的方法已经把特殊字符过滤了
我们继续对这些接收参数的函数进行测试,这次我们尝试传递数组,很多框架会过滤数组的value值,但是key值可能会被忽略,所以这里我们传递参数的是,在key和value中分别带入特殊字符,我们可以看到,在key中的特殊字符没有进行过滤,原原本本输出了出来,但是value中的值是已经被过滤的
接着我们针对get、post、request三个方法进行分析,我们在这三个接收参数的位置下断点
重新发送我们刚才的测试请求包,在get处断了下来,我们F7进入函数,内容如下,首先是一个condition的数组,接着在返回的位置执行了filter函数,我们继续跟进分析
filter具体的函数内容过于冗长,就不再这里展示了,经过我们的分析得知
接着我们同样的对post方法进行分析,其过滤流程和get方法完全一样只是接收参数额方法使用的是$_POST,request也是一样。
PbootCMS数据库的增删改查
pbootcms下的数据库操作方法都是在model下的ParserModel.php文件中
查询
我们在这个文件中添加一个新的方法来辅助我们进行测试,该方法用来获取用户的数据
接着我们在Message的控制调用该函数
我们在前端页面进行访问http://pbootcms.com/index.php/message/test2?id=1
,成功查询到用户数据
增加
增加的操作,我们使用同样的方法在ParserModel中编写代码
接着我们继续在message控制器中接收前端传递的参数
在数据库中我们可以看到数据已经插入成功
修改
基本操作都是一样的,在修改用户的时候需要传递两个参数,一个是用来做条件的id,一个是更新的数据
继续在控制器中接收参数并执行修改用户的方法
在前端传递相应的参数,页面中显示的结果为修改数据成功
在数据库中我们查看id为4的数据,就是我们之前插入的数据,现在已经被修改
删除
首先还是编写相应的方法来操作数据库
接收参数的代码
前端传递需要删除用的id
查看数据库,id对应的用户已经被删除
分析数据库查询操作
这里我们首先下断跟踪分析查询数据库的操作,我们在控制器中编写查询数据的代码并下断点
接着前端传入数据,在断点位置我们F7进入,可以看到进入到了我们之前编写的查询操作的方法中
这里执行的基本操作流程如下
分析数据库插入操作
报错payload
数据库操作的代码中存在漏洞,导致我们可以进行报错注入,首先我们在控制器中编写如下代码
$id = get('id');
var_dump($id);
$result = $this->model->getUser($id);
var_dump($result);
接着我们在前端进行测试,当我们输入?id=1 and 1
时,数据同样被查询出来并且没有被过滤
接着我们尝试使用如下payload让其报错
http://pbootcms.com/index.php/message/test2?id=1) and updatexupdatexmlml(1,conconcatcat(0x70x7ee,(SELECT user()),0x70x7ee),1);%23%23%23
Insert注入漏洞
insert注入漏洞一般就发生在前端向后端发送数据并向数据库中插入数据的情况下,这里我们我们就在页面中的留言部分进行测试,在测试之前我们首先把验证码的部分注释掉,方便我们后续测试
接着我们前端在留言界面传输传输数据,使用burp拦截并打开调试
接着我们针对其插入数据的主要代码进行分析,首先我们遇到第一个需要主要分析的函数是addMessage,这个函数主要的功能就将数据插入到数据库中,我么F7进入这个函数
接着我们就可以看到其执行了数据库操作的代码,这里我们主要跟进insert函数
进入insert之后的部分代码我么之前已经分析过,这个地方是判断我们传输的数据是否是单条数据,如果$data的数据中还包含了数组,那么就会判定为多条数据
其中我们传递的数据在$data中的表现形式如下
{"contacts":"test","mobile":"test","content":"test","acode":"cn","user_ip":2130706433,"user_os":"Windows7","user_bs":"Chrome","recontent":"","status":0,"create_user":"guest","update_user":"guest"}
如果我们在前端传递数组形式的数据
我么再后端接收到的数据内容如下,是以数组的形式存在
在insert函数中,有这一段代码,我们自定义的keycont','create_teim--a;l
拼接到了$key_string变量中
接着$key_string又和其他时间参数拼接到了一起
接着将带有可控key的变量被赋值给了$this->sql['field']
最终会在这里执行我们刚才拼接过带有我们自定义key的语句
接着在buildSql中将%field%
替换为,$this->sql['field'],最终导致了sql注入。
首页注入漏洞
在将注入漏洞之前我们首先分析一下后台的加密过程,我们在后台页面进行登录并抓包,在这里我们可以看到其提交到的地址为admin.php/index/login
我们在代码中定位到登录函数所在地
在login函数的这一样代码中我们找到了加密函数
我们进入加密函数可以看到,对传入的密码进行了两次md5加密
接着我们就开始进行首页注入漏洞的分析,这里我们首先输入测试代码
这里有一个关键点,那就是第一个等号一定要进行url编码
接着进入到parserAfter方法中进行分析,找到指定列表这一行
在该方法中,判断了$_GET请求的参数中,如果有数组的key开头为"ext_",就在where中添加以该key为键get(key)的值得数据,这里会存在一个问题,当我们输入的值得第一个等号不是url编码,那么会造成key:ext_price,value:123123=123123,当我们第一个等号使用了url编码,key:ext_price=123123,value:123123
接着在这行代码调用where2参数,我们就跟进进行分析
在这个函数的执行流程中,知道最后才调用了我们之前传递的参数where2,在这个函数中是where
接着F7跟进,直到进入到第三个where方法中,可以看到where参数是我们前端传递的值,这里我们之前分析过,where中没有进行任何过滤,直接进行了sql语句的拼接
在这一段代码中,将我们传入的数据进行拼接
where语句拼凑到最后如下
到最后sql语句如下,其中包含我们前端传递进入的代码
SELECT a.*,b.name as sortname,b.filename as sortfilename,c.name as subsortname,c.filename as subfilename,d.type,e.* FROM ay_content a LEFT JOIN ay_content_sort b ON a.scode=b.scode LEFT JOIN ay_content_sort c ON a.subscode=c.scode LEFT JOIN ay_model d ON b.mcode=d.mcode LEFT JOIN ay_content_ext e ON a.id=e.contentid WHERE(a.scode in ('5','6','7') OR a.subscode='5') AND(a.acode='cn' AND a.status=1 AND d.type=2) AND(ext_price=123123 like '%123123%' ) ORDER BY date DESC,sorting ASC,id DESC LIMIT 4
最后我们使用构造过的POC进行验证,即可完成注入
http://pbootcms.com/index.php?ext_price%3D1/**/and/**/updatexml(1,concat(0x7e,(SELECT/**/distinct/**/concat(0x23,username,0x3a,substr(password,15,20),0x23)/**/FROM/**/ay_user/**/limit/**/0,1),0x7e),1));%23=123123
这里我我们还需要注意,查询到的加密密码只有20位,还需要获得20到32位的值,执行如下POC
http://pbootcms.com/index.php?ext_price%3D1/**/and/**/updatexml(1,concat(0x7e,(SELECT/**/distinct/**/concat(0x23,username,0x3a,substr(password,20,32),0x23)/**/FROM/**/ay_user/**/limit/**/0,1),0x7e),1));%23=123123
最终我们分析,造成注入的根本原因还是我们之前已经分析过的
1.数组的key没有进行过滤
2.where函数进行sql语句拼接的时候没有进行过滤
搜索注入漏洞
首先我们访问http://pbootcms.com/index.php/Search
进入到搜索页面
接下来我们就定位代码所在的位置
在搜索结果标签那一行代码进行下断,然后进行F7跟进,最开始的代码中先获取了keyword的值
我么又找到get方法接收前端参数的代码,看到如下代码我们已经很熟悉了,又是将key放入到where2中
剩下的代码分析就和上一个漏洞分析思路已经是一样了