代码审计学习之某shop审计
2019-05-07 15:58:09

文章首发于先知社区:https://xz.aliyun.com/t/5077 [toc] 之前看了phpoop师傅的PbootCMS漏洞合集之审计全过程文章,一直没有真正的审过一套cms,代码审计只是停留在ctf题的层面上。所以接下来准备认真审计一些cms。菜鸡文章,希望大佬们不要喷。

查看网站目录结构确定基本内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
某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

image.png image.png 根据var_dump的结果可以看到原生GET,POST,REQUEST变量中的' " <>等字符被转义了,通过debug跟踪代码以及全局搜索$_REQUEST等字符我并没有找到对这些变量进行过滤的地方,可能是在渲染输出的时候对字符串进行了过滤。

系统外部变量获取函数

image.png 可以看到获取外部变量都调用了Req类,跟进/framework/lib/util/request_class.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<?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 image.png 该类的每一个方法都对应着不同的过滤功能。

查看系统DB类,了解数据库底层运行方式

/framework/web/model/module_class.php/framework/web/model/query_class.php 前者用于被控制器调用,后者用于viewActionimage.png 基本上Model类的底层没有任何过滤,只是简单的进行类字符串拼接,所以只要能将'\带入Model类中,且没有被Filter类过滤,即可构成注入。

系统情况初步集合

基本上除了Filter类,底层没有进行过于严格的过滤。 只要调用了Req类获取参数且在渲染赋值的过程中没有使用Filter类进行过滤,那么就很容易造成xss。 sql注入同理,底层Model类没有专门进行过滤。 另外Filter类我只是简单的看了一下,具体在某个情况下调用的函数不排除被绕过的可能。

后台一处注入

前台看了好久,过滤的很严格找不到注入点,并且这个cms大部分功能都是后台的,所以我只好在后台找一下了,虽然没什么卵用,就当学习思路。 /protected/crontrollers/goods.php set_online方法中接收id参数进行商品上下架处理,却没有对id参数进行过滤,直接拼接进sql语句中。 image.png image.png 构造id=12) and if(1=1,sleep(1),0)# image.png 可以看到成功注入 image.png 同样的,后台有不少地方都没有进行过滤,就不一一列举。 image.png

后台两处任意文件删除+任意文件读取

第一处比较鸡肋,只能删除install.lock文件 /protected/crontrollers/plugin.php image.png 可以看到路径前缀从$this->widgetPath中获取,跟进 image.png APP_CODE_ROOT为应用开发路径 image.png 可以看到name参数没有任何过滤,我们可以利用../../install跳转到安装目录,将install.lock删除然后重新安装。 然后配合MySQL LOAD DATA 任意文件读取,读取服务器上的文件。 任意文件读取 image.png image.png 第二处可以删除任意文件 image.png $backs变量没有任何过滤,直接和$database_path拼接然后执行删除操作。所以我们可以利用../删除任意文件 image.png 同样可以删除install.lock配合MySQL LOAD DATA读取任意文件。