前言
AVDemo漏洞平台测试第二篇–白盒测试
前段时间写了第一篇,现在来补上第二篇。
目录:
测试环境
phpStudy + php 5.6
工具: Seay + RIPS + VCG
审计流程
在审计代码前,可以先简单看下网站结构
我们可以从中了解到程序的架构、大概的运行流程、包含那些配置文件等,
还能了解程序的业务逻辑,
当然大牛的话可能一眼就能知道程序怎么个走法,
下图这些目录需要重点关注,特别是配置目录。
审计方法
审计方法有那么几种: 通读全文法、敏感函数参数回溯法、定向功能分析法
从字面意思能够大概理解,这里我就不多解释了。
因为本人也是第一次审计框架,能力有限,
这里就直接配合工具来审计了。
这里首先使用三种工具交叉扫描,
工具这里我也先不介绍了,各有千秋吧!!
下面是三种工具检测的截图
Seay:
RIPS:
VCG:
1. 文件包含漏洞
使用三种工具交叉扫描发现,
可能存在文件包含漏洞,
我们跟进一下看看,
发现是在入口文件 index.php 出现的问题
然后根据以往的经验,这种 include($_GET['module'].'.inc')
拼接后缀名的可能存在绕过风险,
像这种情况的话我们可以使用00截断去截断他的拼接后缀,
但是00截断必须要 php < 5.3.4 才可以,
这里我们还有另外一种方法,就是使用 phar://
伪协议来绕过,
关于这个协议为什么能够绕过,这里简单讲一下,
这是一种与压缩包有关的协议,他可以打开一个压缩包,并且可以打开其中一个文件,读取某个文件的内容,
使用方法: phar://path/file[文件]/file[里面所压缩的文件]
这里我们来说说如何使用伪协议,
当然,我们知道,不管是什么协议,都需要有一个文件来给他读取,
这里我们就需要找一个上传点了,
这里我们可以直接使用正则快速定位 upload
上传类,
我们可以看到第5行,有个上传函数,
然后跟过去看看,
发现是 user目录 下的一个处理上传的php文件,
然后我们可以看到 首先有一个 is_pic 去判断上传的文件名,
如果上传成功后保存在 upload目录下,
文件命名为 u_时间戳_文件名
这样的格式。
然后我们再跟进查看 lib 函数库,
发现 is_pic 这个函数只是检查了文件后缀,
那么我们就有办法绕过了,
然后直接将一句话 shell.php 的 后缀 修改为 inc ,
然后将 shell.inc 压缩为 zip 文件,
然后再将 shell.zip 后缀修改为 sehll.jpg ,因为只能上传图片,
然后打开我们的开发者工具,然后上传,
这里可以看到他的一个执行时间,
但是还是时间格式,我们还需要把他转换成时间戳 ,
我们可以使用 strtotime 将时间转换成时间戳,
<?php
date_default_timezone_set('UTC');
echo strtotime('Tue, 30 Apr 2019 02:18:27 GMT');
?>
输出: 1556590707
现在我们知道文件名是 u_1556590707_shell.jpg
但是在包含的过程中却出现了错误,
我们直接去文件夹下看看上传的文件,
发现名字不一样,uploads 目录下的文件名是 1556590709
, 而我们的是 1556590707
,
这是服务器执行的时候有差异导致的,因为执行的时间相差也非常短,
所有我们可以尝试去猜测文件名,这里的话只需要加两位就可以了,
现在我们可以构造payload了,
根据上面的 phar:// 使用语法 ,
Payload为: phar://uploads/u_1556590709_shell.jpg/shell
因为上传的时候已经拼接了后缀,所以这里我们就不需要再加后缀名了
然后shell路径为:
http://www.vd.com/index.php?module=phar://uploads/u_1556707346_shell.jpg/shell
然后上菜刀就行了,
2. SQL注入
这里我就不一一截图了,三个工具都扫描到了这个sql注入,
那么我们现在就来看看是否真实存在,
Seay工具里说没有单引号保护,
根据经验来看,没有引号保护的更容易注入,因为不需要闭合,
有引号保护的,当我们在闭合引号的时候,可能会被过滤掉,导致无法注入,
这里我们跟过去看看 ,
发现这里查看留言详情的页面,
通过接受 id 的值,然后去数据库中查找这条留言,
然后我们再跟过去看看这个 sqlwaf的函数 ,
发现把一些注入常用的关键字替换为 sqlwaf ,
但是后面有些字符却替换为空
我们可以来简单的测试一下,and 1=1
,毫无疑问,被过滤了,
这时我们可以使用 ||
来绕过,
输入 a||nd 1=1
成功绕过,
后面的操作就跟黑盒测试中那一样了,就不演示了
3. 命令执行
我们看第6个,使用 shell_exec 执行 $cmd ,如果 $cmd 可控的话,那么可能会存在命令执行
我们跟过去看看
发现这是执行 ping 操作的代码,
通过接受 target 的值来进行ping操作,
最后使用 shell_exec 来执行 $cmd 的命令,
此操作没有任何过滤,存在绕过
我们可以直接使用命令管道符进行绕过,
类似的绕过还有 &
, &&
, ||
等等。
4. 任意文件读取
我们看到第14行,可能存在任意文件读取,
追踪一下代码,
发现是获取头像的代码,
然后我们再追踪一下参数 avatar
发现他是在上传头像的地方
我们来解读一下这段代码
if(is_pic($_FILES['upfile']['name'])){
$avatar = $uploaddir . '/u_'. time(). '_' . $_FILES['upfile']['name'];
if (move_uploaded_file($_FILES['upfile']['tmp_name'], $avatar)) {
//更新用户信息
$query = "UPDATE users SET user_avatar = '$avatar' WHERE user_id = '{$_SESSION['user_id']}'";
mysql_query($query, $conn) or die('update error!');
mysql_close($conn);
//刷新缓存
$_SESSION['avatar'] = $avatar;
header('Location: edit.php');
}
$avatar变量是上传图片的路径,
move_uploaded_file() 函数,将上传的文件移动到新位置
语法: move_uploaded_file(file,newloc)
参数:
-
file 必需。规定要移动的文件。
-
newloc 必需。规定文件的新位置。
然后将上传的文件进行更新操作,
$query 是设置sql语句进行更新操作,
我们再仔细看里,user_avatar = '$avatar'
他是获取图片的路径然后更新到数据库中,
如果我们能够控制这个地方,那么就能够造成任意文件读取。
既然有思路了,那么我们来试试,
首先开启burp,把流量全部记录下来,
然后进行登录,头像上传一系列操作,这是为了保证 Cookie 一致
后面在历史记录中找到登录、头像上传、头像获取这三个包,然后发送到Repeater
我们知道,上传文件处是把新的文件更新到数据库,并且更新的时候没任何过滤,
那么我们尝试去构造一下payload,
重新看一下这个更新语句
$query = "UPDATE users SET user_avatar = '$avatar' WHERE user_id = '{$_SESSION['user_id']}'";
我们需要闭合掉 $avatar 的单引号,
那么我们的payload应该是:
',user_avatar = '2' where user_name = 'test'#.jpg
想当于:
$query = "UPDATE users SET user_avatar = '',user_avatar = '1' where user_name = 'test'#.jpg WHERE user_id = '{$_SESSION['user_id']}'";
我们可以看到这串语句,把前面第一个 user_avatar 闭合掉了,
然后把要修改的用户设置为 test,并把后面注释掉了,
我们进数据库中查看一下,
发现成功修改。
现在,他获取图片的时候就拿到了这个 1 ,
那么我们可以根据这种情况把我们想要读取的文件路径写进去,
这样他在读取的时候,实际上就读取了某个文件,
我们可以尝试去读取 sys目录下的 config.php,
但是在尝试读取的时候出错了,我们去数据库中看看
发现路径变成了 uploads
这是因为,$avatar 存在路径的话,他会直接重命名,然后上传到 uploads 目录下 ,
这时我们可以使用十六进制进行绕过,
将 ../sys/config.php
转换成十六进制,
然后重新发包,发现数据库中已经变成了我们想要的路径。
这个时候,我们只需要将登录的Cookie和获取头像的Cookie改个名字,就行了
这是相当于重新登录,不然的话还是原来登录获取的,
只需要在原来Cookie处加一个 a 就行了,
根据Cookie组成,只要是数组英文字母都可以,
当然,如果登录的Cookie加了个 a ,那么获取头像处的 Cookie也要加个 a ,
Cookie要相同
改完后重新发送登录包,然后再发送获取头像的包,
这个时候就已经成功读取到了我们想要读取的文件内容
5. 逻辑越权
代码审计工具只能查找一些危险函数,
对于逻辑漏洞是扫描不出来的,
包括现在的一些web扫描工具,也是扫描不到逻辑漏洞,
这种漏洞需要人工去发现,
逻辑漏洞一般出现在,登录、支付、修改信息等等。
下面我们来看看这个越权漏洞
这个漏洞位于 updateName.php ,这是一个更新用户名的页面
我们再来看看这个漏洞,
代码:
include_once('../sys/config.php');
if (isset($_POST['submit']) && !empty($_POST['username']) ) {
if (strlen($_POST['username'])>16) {
$_SESSION['error_info'] = '用户名過長(用戶名長度<=16)';
header('Location: edit.php');
exit;
}
$clean_username = clean_input($_POST['username']);
$clean_user_id = clean_input($_POST['id']);
//判断用户名已是否存在
$query = "SELECT * FROM users WHERE user_name = '$clean_username'";
$data = mysql_query($query, $conn);
if (mysql_num_rows($data) == 1) {
$_SESSION['error_info'] = '用户名已存在';
header('Location: edit.php');
exit;
}
$query = "UPDATE users SET user_name = '$clean_username' WHERE user_id = '$clean_user_id'";
mysql_query($query, $conn) or die("update error!");
mysql_close($conn);
//刷新缓存
$_SESSION['username'] = $clean_username;
header('Location: edit.php');
}
else {
not_find($_SERVER['PHP_SELF']);
}
这个漏洞位于 $clean_username = clean_input($_POST['username']);
和 $clean_user_id = clean_input($_POST['id']);
通过接收 $_POST
的值,然后赋值给,$clean_username 和 $clean_user_id
我们知道,$_POST**
是可以构造出来的,使用hackbar就可以,或者抓包修改也行
当然,我们还需要绕过 isset($_POST['submit'])
和 !empty($_POST['username'])
这个两个也是接收 $_POST
的值,我们也可以构造去绕过,
接下来我们来尝试一下看看
首先把必要的参数填进去,
Payload: submit=1&username=aaa&id=9
submit只要有值就行,然后用户名我们修改为 aaa ,要修改 id=9 的用户 ,
然后我们将数据发送到 updateName.php
这个时候,我们可以看到,用户名成功被修改
6. IP伪造
可以看到第12和13行,可能存在IP伪造,
并且 HTTP_X_FORWARDED_FOR 和 HTTP_CLIENT_IP 这两个函数存在绕过,
所以我们很有必要跟过去看看。
首先来看看代码:
function get_client_ip(){
if ($_SERVER["HTTP_CLIENT_IP"] && strcasecmp($_SERVER["HTTP_CLIENT_IP"], "unknown")){
$ip = $_SERVER["HTTP_CLIENT_IP"];
}else if ($_SERVER["HTTP_X_FORWARDED_FOR"] && strcasecmp($_SERVER["HTTP_X_FORWARDED_FOR"], "unknown")){
$ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
}else if ($_SERVER["REMOTE_ADDR"] && strcasecmp($_SERVER["REMOTE_ADDR"], "unknown")){
$ip = $_SERVER["REMOTE_ADDR"];
}else if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], "unknown")){
$ip = $_SERVER['REMOTE_ADDR'];
}else{
$ip = "unknown";
}
return($ip);
}
get_client_ip 是一个获取ip的函数,
HTTP_CLIENT_IP 函数可以获取客户端的ip,这个ip存在于http请求的header中
HTTP_X_FORWARDED_FOR 也可以获取ip地址
strcasecmp 比较两个字符串(不区分大小写)函数
strcasecmp($_SERVER["HTTP_CLIENT_IP"], "unknown")
这句的意思就比较 ip 和 unknown 这两个字符串,是不是相同的,
整改代码的意思就是获取 ip ,然后如果没有的话,最后输出是 unknown
接下来,我们再来追踪一下 哪里调用了 get_client_ip 函数,
我们发现在 logCheck.php 调用了,
这是前台用户登录验证的一个页面。
现在来试试,
发现在闭合单引号的时候被过滤掉了,
我们回到注入点跟过滤函数,
$query = "UPDATE users SET login_ip = '$ip' WHERE user_id = '$row[user_id]'";
这个注入点是字符型注入,需要闭合单引号,
而过滤函数已经把单引号过滤了,
这个注入点已经没办法进行注入了。
不过我们依然可以进行ip伪造,
在某些爆破,登录,访问的时候可能会判断ip,
如果我们能够伪造,是可以绕过这些限制的,
总结
这个漏洞平台审计起来还是比较简单的,
不过还是要多审计,多看代码,
不然一扫描报一堆的漏洞,很难去发现那里有漏洞,那些没有漏洞,
那这个来说,如果文件包含路径后面拼了其他路径的,那么基本没戏。
就像下图一样那种,
代码还得要多看,以后的路还很长。