我是说怎么没见着smile师傅水群的身影了
之前看了phpoop师傅的PbootCMS漏洞合集之审计全过程文章,一直没有真正的审过一套cms,代码审计只是停留在ctf题的层面上。所以接下来准备认真审计一些cms。菜鸡文章,希望大佬们不要喷。
查看网站目录结构确定基本内容
某shop-V3.1.1
├─ cache 缓存目录(自动创建)
├─ data 数据目录
│ ├─ database 数据库文件备份目录
│ ├─ uploads 上传数据目录
├─ framework Tiny 框架目录
├─ install html模板
├─ logs 日志目录(自动创建)
├─ protected 应用保护代码目录
│ ├─ classes 自由扩展类目录,可自己配制
│ ├─ config 配制文件目录,可自己指定
│ ├─ controllers 控制器目录
│ ├─ extension 程序扩展目录
│ ├─ widgets 插件目录
│ ├─ views 视图目录
├─ runtime 运行时生成的编译目录(自动创建)
├─ static 共用的静态文件
├─ themes 主题目录
│ ├─ default 默认主题
│ ├─├─skins 皮肤目录
│ ├─├─widgets 专属主题的插件目录
│ ├─├─views 视图目录
├─ index.php 前端入口文件
URL 模式
URL 的访问方式是 index.php?con=Controller&act=Action
Action 有两种,一种是脚本处理类的 Action,此类 Action 是 Controller 的一个 function另一种是视图类的 Action(也就是视图),当 Controller 中不存在此 function 时,系统自 动会加载此 action 对应的视图文件,如果此视图也不存在,系统会跳转 404 页面。
开启伪静态时 URL 访问方式是 /Controller/Action
也可为/index.php/Controller/Action
了解系统参数与底层过滤情况
原生 GET,POST,REQUEST
根据var_dump
的结果可以看到原生GET,POST,REQUEST变量中的' " <>
等字符被转义了,通过debug跟踪代码以及全局搜索$_REQUEST
等字符我并没有找到对这些变量进行过滤的地方,可能是在渲染输出的时候对字符串进行了过滤。
系统外部变量获取函数
可以看到获取外部变量都调用了Req类,跟进
/framework/lib/util/request_class.php
<?php
/**
* Tiny - A PHP Framework For Web Artisans
* @author Tiny <tinylofty@gmail.com>
* @copyright Copyright(c) 2010-2014 http://www.tinyrise.com All rights reserved
* @version 1.0
*/
/**
* 封装$_GET $_POST 的类,使$_GET $_POST 有一个统一的出入口
* @class Req
* @note
*/
class Req
{
//对应处理$_GET
public static function get()
{
$num = func_num_args();
$args = func_get_args();
if($num==1)
{
if(isset($_GET[$args[0]])){
if(is_array($_GET[$args[0]]))return $_GET[$args[0]];
else return trim($_GET[$args[0]]);
}
return null;
}
else if($num>=2)
{
if($args[1]!==null)$_GET[$args[0]] = $args[1];
else if(isset($_GET[$args[0]])) unset($_GET[$args[0]]);
}
else
{
return $_GET;
}
}
//对应处理$_POST
public static function post()
{
$num = func_num_args();
$args = func_get_args();
if($num==1)
{
if(isset( $_POST[$args[0]])){
if(is_array( $_POST[$args[0]]))return $_POST[$args[0]];
else return trim( $_POST[$args[0]]);
}
return null;
}
else if($num>=2)
{
if($args[1]!==null) $_POST[$args[0]] = $args[1];
else if(isset($_POST[$args[0]])) unset($_POST[$args[0]]);
}
else
{
return $_POST;
}
}
//同时处理$_GET $_POST
public static function args()
{
$num = func_num_args();
$args = func_get_args();
if($num==1)
{
if(isset($_POST[$args[0]])){
if(is_array($_POST[$args[0]]))return $_POST[$args[0]];
else return trim($_POST[$args[0]]);
}
else{
if(isset($_GET[$args[0]])){
if(is_array($_GET[$args[0]]))return $_GET[$args[0]];
else return trim($_GET[$args[0]]);
}
}
return null;
}
else if($num>=2)
{
if($args[1]!==null)
{
$_POST[$args[0]] = $args[1];
$_GET[$args[0]] = $args[1];
}
else
{
if(isset($_GET[$args[0]])) unset($_GET[$args[0]]);
if(isset($_POST[$args[0]])) unset($_POST[$args[0]]);
}
}
else
{
return $_POST+$_GET;
}
}
public function only()
{
$hash= md5(serialize($_POST));
$safebox = Safebox::getInstance();
$__hash__ = $safebox->get('__HASH__');
if($hash != $__hash__)
{
$safebox->set('__HASH__',$hash);
return true;
}
else
{
return false;
}
}
}
?>
GET和POST变量分别对应了Req::get()、Req::post()、Req::args()
,且没有任何过滤,每次过滤都会调用Filter类,跟进/framework/lib/util/filter_class.php
该类的每一个方法都对应着不同的过滤功能。
查看系统DB类,了解数据库底层运行方式
/framework/web/model/module_class.php
和/framework/web/model/query_class.php
前者用于被控制器调用,后者用于viewAction
。基本上
Model
类的底层没有任何过滤,只是简单的进行类字符串拼接,所以只要能将'
或\
带入Model
类中,且没有被Filter
类过滤,即可构成注入。
系统情况初步集合
基本上除了Filter类,底层没有进行过于严格的过滤。只要调用了Req
类获取参数且在渲染赋值的过程中没有使用Filter
类进行过滤,那么就很容易造成xss。sql注入同理,底层Model
类没有专门进行过滤。另外Filter
类我只是简单的看了一下,具体在某个情况下调用的函数不排除被绕过的可能。
后台一处注入
前台看了好久,过滤的很严格找不到注入点,并且这个cms大部分功能都是后台的,所以我只好在后台找一下了,虽然没什么卵用,就当学习思路。/protected/crontrollers/goods.php
set_online方法中接收id参数进行商品上下架处理,却没有对id参数进行过滤,直接拼接进sql语句中。构造
id=12) and if(1=1,sleep(1),0)#
可以看到成功注入
同样的,后台有不少地方都没有进行过滤,就不一一列举。
后台两处任意文件删除+任意文件读取
第一处比较鸡肋,只能删除install.lock
文件/protected/crontrollers/plugin.php
可以看到路径前缀从
$this->widgetPath
中获取,跟进APP_CODE_ROOT
为应用开发路径可以看到
name
参数没有任何过滤,我们可以利用../../install
跳转到安装目录,将install.lock
删除然后重新安装。然后配合MySQL LOAD DATA 任意文件读取,读取服务器上的文件。任意文件读取
第二处可以删除任意文件$backs
变量没有任何过滤,直接和$database_path
拼接然后执行删除操作。所以我们可以利用../
删除任意文件同样可以删除
install.lock
配合MySQL LOAD DATA读取任意文件。