本文最后更新于 2024-08-29,文章内容可能已经过时。

Vaudit审计

一、安装

下载连接:
https://github.com/virink/VAuditDemo/tree/master
1、上传vaudit压缩文件到/opt/lampp/htdocs

2、解压缩

unzip VAuditDemo-master.zip

3、修改目录名称

cd /opt/lampp/htdocs
cd VAuditDemo-master
mv VAuditDemo_Debug vaudit
cd ..
mv VAuditDemo-master vaudit

4、修改httpd.conf文件

vi /opt/lampp/etc/httpd.conf
添加一个端口
Listen 81

image-20240323105811462

image-20240323110143594

保存并退出。

5、编辑httpd-vhost.conf

vi /opt/lampp/etc/extra/httpd-vhosts.conf
把文件中的原来内容全部删除,并把下面的内容输入到文件中
<VirtualHost *:81>
    serverName localhost
    DocumentRoot "/opt/lampp/htdocs/vaudit/vaudit"
</VirtualHost>

6、重启lampp

/opt/lampp/xampp restart

7、访问项目首页

http://192.168.217.128:81/index.php

可以看到url跳转到系统安装的地址

http://192.168.217.128:81/install/install.php

image-20240323111111459

8、修改目录和文件权限

chmod o+w /opt/lampp/htdocs/vaudit/vaudit/sys
chmod o+w /opt/lampp/htdocs/vaudit/vaudit/uploads
chmod o+w /opt/lampp/htdocs/vaudit/vaudit/sys/config.php

再次访问页面,

http://192.168.217.128:81/install/install.php

看到下面的页面,说明修改成功

image-20240323111336374

在以上页面输入正确的数据库地址,用户名,密码点击安装按钮。数据库名称不用修改,就使用vauditdemo。

如果数据库已经存在,那么先删掉数据库,再点击安装按钮。

看到下面的页面,说明安装成功。

image-20240323112259014

二、代码整体结构

_├── about.inc                                     ---说明信息
├── admin                                           ---管理员目录
│   ├── captcha.php     							--生成图片验证码
│   ├── delUser.php									---删除用户
│   ├── index.php									--管理员的主页面
│   ├── logCheck.php                                 --检查管理员登录的用户名和密码
│   ├── login.php 									--登录页面
│   ├── manageAdmin.php								--添加新的管理员
│   ├── manageCom.php								--管理留言
│   ├── manage.php									--管理其他功能的入口页面
│   ├── manageUser.php								--管理普通用户
│   └── ping.php									--ping可以探测网络是否联通
├── css                                                   --- css文件
│   ├── bootstrap.css
│   ├── bootstrap.min.css
│   ├── bootswatch.less
│   ├── bootswatch.min.css
│   └── variables.less
├── footer.php                              ---页脚
├── header.php                           ----页头
├── images                                    ---图片目录
│   └── default.jpg
├── index.php                              ---入口文件
├── install                                      ---安装文件
│   ├── install.php
│   └── install.sql
├── js                                               ---js文件
│   ├── bootstrap.min.js
│   ├── bootswatch.js
│   ├── bsa.js
│   └── check.js
├── messageDetail.php           ---留言详情页面
├── message.php                       ---留言页面
├── messageSub.php               ---留言添加到数据库
├── search.php                           ----搜索页面
├── sys                                            ---配置目录
│   ├── config.php					--配置文件
│   ├── install.lock
│   └── lib.php							--库函数
├── uploads                              -----图片上传目录
└── user                                     -----普通用户目录
    ├── avatar.php						--管理用户头像
    ├── edit.php						--编辑用户信息
    ├── logCheck.php					--验证登录的用户名和密码
    ├── login.php						--登录页面
    ├── logout.php						--退出登录
    ├── regCheck.php					--注册新用户
    ├── reg.php							--注册页面
    ├── updateAvatar.php				--更新头像
    ├── updateName.php					--更新用户名
    ├── updatePass.php					--更新密码
    └── user.php						--用户界面

三、功能列表

1、注册新用户

2、用户登录

3、退出登录

4、修改用户名,用户密码,上传头像图片

5、用户发表留言

6、查看某个留言详细信息

7、根据关键字查询留言列表

8、查看留言列表

9、关于页面

10、管理员登录

11、添加管理员

12、删除管理员

13、管理员删除普通用户

14、管理员删除评论

15、管理员Ping的功能

四、漏洞审计

系统重新安装漏洞

在看到上面的安装成功页面后,如果再次访问安装的地址

http://192.168.217.128:81/install/install.php

可以使用抓包工具burp或者fiddler,以下是fiddler抓包结果

image-20240323180243747

可以看到虽然跳转到了index.php,但是install.php被调用后,还是返回了安装页面一些内容。这会导致一些敏感信息的泄露。

原因分析:

导致上面这个问题的根本原因在于,在判断了安装已经完毕后,虽然使用请求头

header( "Location: ../index.php" );

来让页面跳转,但是在php中,页面即使跳转,但是后面的代码也会依然继续执行。

image-20240323180905534

解决方案

在第5行增加die(),终止下面的代码继续执行

image-20240323181139113

所以即使安装完毕之后,可以通过burp抓包,看到install.php页面返回的安装表单。

可以根据安装表单,来自己构造一个安装请求,注入一句话木马到config.php

POST /install/install.php HTTP/1.1
Host: 192.168.218.128:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Cookie: PHPSESSID=p1t2n3j5ejheqf2hbohuhuk337
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 79

dbhost=localhost&dbuser=root&dbpass=1234&Submit=sub&dbname=aaaa;#";phpinfo();//

命令注入

通过管理员登录,并进入Ping的页面

image-20240323211742116

可以看到这个使用了管道符,执行了其他的系统命令

image-20240323211916416

产生的原因

ping.php中拿到用户提交的参数,并没有做任何过滤,直接使用了

image-20240323212055188

解决方案

可以考虑对传入的参数进行严格过滤,只能限制输入ip地址(正则校验)

SQL注入

查看留言详细信息页面

image-20240323212601799

messageDetail.php的代码

image-20240323212734738

看看这个sqlwaf的函数是怎么做的,参考lib.php文件

image-20240323212815250

上面这个函数对传入的参数做了一些防护。但是,这里存在着逻辑漏洞

由于把&&,|| 这些替换成"",那么我们可以在select这些关键字中间增加||来绕过,

例如

 selec||t * fro||m tablename

所以最后的payload是:

id=9 unio||n selec||t 1,database(),3,4 lim||it 1,1

存储型XSS

在管理员查看普通用户信息的地方,

image-20240323215255640

可以查看登录用户的IP地址

image-20240323215149957

manageUser.php代码

image-20240323215630504

user_name和login_id是从数据库中查询出来的,但是用户名经过了htmlspecialchars的转义,继续查找ip地址是怎么传入数据库的。

转到logCheck.php

image-20240323220107273

显示用get_client_ip()获取ip地址,再经过sqlwaf处理

来到lib.php看到了get_client_ip(),里面有个可以用请求头x-forwarded-for注入的点。

这里可以进行存储型XSS注入

image-20240323220232351

拦截住登录请求,添加一个请求头

x-forwarded-for: <script>alert(1)</script>

image-20240323220842246

看到数据库表中已经保存了xss脚本

image-20240323220945645

再次去页面查看ip地址,会弹出警告框

image-20240323221143604

根本原因

对请求头没有做充分的过滤防护

解决方法

针对从请求头中获取的ip地址,可以做精确的过滤处理

验证码绕过

管理员的登录页面存在验证码,当需要爆破密码进行登录时就需要识别验证码或者绕过验证码。

admin/login.php

image-20240323221921618

可以看到验证码生成后,放入session中

image-20240323222040766

登录验证,admin/logCheck.php

比较验证码的值

image-20240323221736473

具体操作步骤:

先打开firefox并且设置代理到burp

image-20240323222714327

​ 打开burp,设置拦截

image-20240323222747552

打开管理员登录页面

http://192.168.217.128:81/admin/login.php

并检查一下是否有cookie,如果有清空所有的cookie

输入用户名,密码,验证码,然后登录

image-20240323223129701

此时burp拦截了请求,可以看到请求中没有cookie,但是参数中有captcha=1714

image-20240323223336720

此时,把captcha=1714这个参数删掉

请求变成了,直接点击forward按钮放行

image-20240323223435589

看到下面这个页面说明验证码绕过成功

image-20240323223555183

原理分析

在比较验证码是否正确的时候,使用了代码

	if(@$_POST['captcha'] !== $_SESSION['captcha']){
		header('Location: login.php');
		exit;
	}

由于我们在拦截请求后,删除了参数captcha=1714,而且我们也删除了cookie中的PHPSESSID,所以

$_POST['captcha']获取到的是null , 此时  $_SESSION['captcha'] 得到的也是null
因此 if(null !==null) 这个判断是false,不会进入到这个if代码体内部。

这样就绕过了验证码判断

解决方案

在验证码比较的时候要判断是否得到是值是null,不管是用户的参数,还是session中获取的,只要没有值,或者不相等,都认为验证失败。

任意文件读取

avatar.php,这里的代码是从session中根据avatar的值获取内容,然后根据内容,读取文件

file_get_contents($_SESSION['avatar']);

image-20240323225239361

跟踪$_SESSION[‘avatar’]看看是在什么地方设置的值来到user/logCheck.php

image-20240323225547532

发现来自数据库表读取的值,那么再继续跟踪,看看是什么时候设置进入数据库表的。

来到updateAvatar.php中发现了其更新语句

image-20240323225913440

这个update语句的语法

UPDATE users SET user_avatar = '../images/default.jpg' WHERE user_id = 9

可以改成

UPDATE users SET user_avatar = '../images/default.jpg',user_avatar = '/etc/passwd' WHERE user_id = 9

更新结束可以看到,右面的一个值被更新成功

image-20240323230801983

根据这个更新规则,我们构造一个payload

', user_avatar = '../sys/config.php' WHERE user_name = 'zhangsan'#.jpg

重现步骤:

用普通用户zhangsan登录后,编辑用户信息页面,上传一个头像,打开burp拦截住上传请求

image-20240323231258442

image-20240323231339726

根据我们的payload,修改filename这个参数的值,使用我们的payload

', user_avatar = '../sys/config.php' WHERE user_name = 'zhangsan'#.jpg

image-20240323231601533

发现结果不对,

image-20240323233854817

原因是文件名中不能含有路径,那么我们把文件名中的路径做16进制编码,注意文件名转成16进制的时候,包裹文件名’…/sys/config.php’的单引号就可以去掉了,直接把…/sys/config.php转为16进制:0x2e2e2f7379732f636f6e6669672e706870

image-20240323235003683

更新成功

image-20240323234432349

接下来登录然后读取头像文件就可以看到config.php文件的内容。

image-20240323234752069

文件包含

index.php,使用了include函数包含about.inc文件,这里限制了后缀只能是.inc格式的,可以尝试使用伪协议绕过,例如phar://, 因为phar可以读取压缩文件中的文件

image-20240324155608428

利用步骤:

在上传文件处上传一个一句话木马,注意需要将木马文件后缀该为.inc,并将其压缩,压缩文件后缀该为.jpg格式。

木马文件test.inc

<?php  phpinfo();?>

把test.inc 放入压缩文件test.zip中,并且把test.zip的扩展名改为test.jpg

上传test.jpg,注意记住上传的具体时间,具体到秒

image-20240324162123244

根据服务端updateAvatar.php的上传文件的重命名规则

image-20240324162252679

上传后文件名称是

image-20240324162347198

根据我们记录的上传的时间,转换成整数形式的时间,并且拼接成上图的文件名称

payload是

phar://uploads/u_1711268439_test.jpg/test

访问一下index.php

http://192.168.109.128:81/index.php?module=phar://uploads/u_1711268439_test.jpg/test

可以看到木马test.inc中的php代码被执行了

image-20240324164217021

越权

编辑用户信息中的修改用户名

image-20240324164425135

image-20240324164934687

image-20240324165008639

在updateName.php中发现更新用户名时根据用户id是从浏览器传入的。这样就导致了可以修改任意用户用户名的越权问题。

查看数据库,lisi的id是10,

image-20240324165414083

image-20240324165947493

提交的请求如下

image-20240324170153715

可以看到数据库id=10的用户名原来是lisi,现在是wangwu

SQL注入+越权

登录成功后跳转,看到user.php中,

$query = "SELECT * FROM users WHERE user_name = '{$_SESSION['username']}'";
$_SESSION['user_id'] = $result['user_id'];

怀疑有sql注入风险,如果用户名中有单引号就可以造成注入。根据用户名查询到的user_id放入到session,那么,以后更新操作都根据user_id,就可能导致越权。

下面就是研究如何能让session的username有单引号出现。

查看regCheck.php,这里clean_input只是对单引号,双引号,反斜线做了防御

$clean_name = clean_input($_POST['user']);
$clean_pass = clean_input($_POST['passwd']);

那么这个insert语句就可以注册用户名含有单引号的账号

$query = "INSERT INTO users(user_name,user_pass,user_avatar,join_date) VALUES ('$clean_name',SHA('$clean_pass'),'$avatar','$date')";

再去看logCheck.php

$query = "SELECT * FROM users WHERE user_name = '$clean_name' AND user_pass = SHA('$clean_pass')";

从数据库中查询用户信息,包含用户名和用户的id,并且把从数据库查到的用户名放入session,那么session中的用户名就会存在单引号。

$_SESSION['username'] = $row['user_name'];

我们可以考虑先注册一个带有单引号的用户名,然后在去更改用户信息。就能够越权修改其他用户的信息了

注册的用户名长度不能超过16个字符长度,所以可以使用

'||1 limit 1,1#

image-20240610185843873
这里在数据库中的形式就是这样:
这样虽然使用 '||1 limit 1,1#这个账号登录,实际浏览器在数据库中拿到的却是wang\这个用户的账号的信息。这样就修改这个账户的密码就是修改wang\这个用户的密码
image-20240610190141214

二次注入

先观察messageSub.php,添加留言到数据库的代码,如果从session取出的username中包含有反斜线,那么这个insert语句中,values中的username左边的单引号就会跟clean_message 变量左边的单引号形成闭合,我们就可以考虑通过message输入项进行sql注入

image-20240325163219559

message的值经过了clean_input函数处理,追踪一下clean_input函数的代码,可以看到,先用stringslashes去掉输入值中的反斜线,然后mysql_real_escape_string来处理输入值中的单引号,双引号,反斜线,NULL等字符,并没有把一些数据库的关键字过滤掉

image-20240325163503757

看到这里,我们考虑的利用步骤:

(1)先尝试注册一个名字结尾是反斜线的用户看看是否能成功

来到regCheck.php,查看注册的代码

image-20240325164656782

这里仅仅使用了clean_input函数来简单的处理,所以可以注册一个名字结尾有反斜线的用户

image-20240325164532236

发现能够注册成功

image-20240325164810323

退出,重新登录一下,让用户名刷新到session中

image-20240325165346487

看到这个报错,不用理会,直接点击留言。

image-20240325165422569

点击“发留言”按钮,可以来到发留言的页面

image-20240325164855008

根据对添加新留言到数据的insert语句的分析

 INSERT INTO comment(user_name,comment_text,pub_date) VALUES ('{$_SESSION['username']}','$clean_message',now());

针对留言内容,我们可以构造如下的payload

,(select database()),now())#

image-20240325165740286

点击“留言”按钮,返回到留言列表,会直接看到数据库名字

image-20240325165812876

接下来在爆库的字段:

点击“留言”按钮,返回到留言列表,会直接看到数据库字段的名字

,(select GROUP_CONCAT(table_name) from information_schema.tables where table_schema=database()),now())#

image-20240610184123377