DVWA通关笔记 DVWA 是一个入门的 Web 安全学习靶场,结合源码去学习的话,是个入门安全的好靶场,这个靶场是我刚入行的时候练习的,当时还没有记录的习惯,所以这里只提供部分题解,等以后有时间了再慢慢补上吧!
环境配置 DVWA、kali2020.4、火狐浏览器、中国蚁剑
firefox渗透浏览器下载: 链接:https://pan.baidu.com/s/1zBSl8CyJN6HHbsYC1JpOlQ 提取码:zj13
环境的搭建毫无意义,应该注重漏洞本身,这边建议直接docker拉一个镜像
dockerfile# 拉取镜像
docker pull sqreen/dvwa
# 部署安装
docker run -d -t -p 8888:80 sqreen/dvwaBrute Force 暴力破解 在 Web 安全领域暴力破解是一个基础技能,不仅需要好的字典,还需要具有灵活编写脚本的能力。
Low 暴力破解一般就是加线程,然后就是看字典大不大、好不好。写的话有点浪费时间。这里提供思路吧
查看源码
phpif( isset( $_GET[ 'Login' ] ) ) {
# 获取用户名和密码
$user = $_GET[ 'username' ];
$pass = $_GET[ 'password' ];
$pass = md5( $pass );
# 查询验证用户名和密码
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysql_query( $query ) or die( '
' . mysql_error() . '' );
if( $result && mysql_num_rows( $result ) == 1 ) {
# 输出头像和用户名
$avatar = mysql_result( $result, 0, "avatar" );
echo "
Welcome to the password protected area {$user}
";}
else {
登录失败
}
mysql_close();
}源码审计,发现用户名和密码都没有进行过滤,直接单线程字典跑一下即可
Medium 查看源码
php// 对用户名和密码进行了过滤
$user = $_GET[ 'username' ];
$user = mysql_real_escape_string( $user );
$pass = $_GET[ 'password' ];
$pass = mysql_real_escape_string( $pass );
$pass = md5( $pass );
// 验证用户名和密码
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
if( $result && mysql_num_rows( $result ) == 1 ) {
登录成功
}
else {
sleep( 2 );
登录失败
}代码审计发现这里做了一点小改动,登录失败的时候会延时 2 秒,这样爆破的速度会慢一些,爆破问题不大
High 查看源码
php// 检测用户的 token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// 过滤用户名和密码
$user = $checkToken_GET[ 'username' ];
$user = stripslashes( $user );
$user = mysql_real_escape_string( $user );
$pass = $_GET[ 'password' ];
$pass = stripslashes( $pass );
$pass = mysql_real_escape_string( $pass );
$pass = md5( $pass );
// 数据匹配
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysql_query( $query ) or die( '
' . mysql_error() . '' );
if( $result && mysql_num_rows( $result ) == 1 ) {
登录成功
}
else {
sleep( rand( 0, 3 ) );
登录失败
}这一关增加了 token 的检测,下面简单介绍下bp爆破过程
bp拦截包,选择Pitchfork模式,并且给要破解的项带上美元符号
给第一个项加playload
给第二个项加playload
匹配的值是需要我们去抓到的
首先将线程的值改为1
然后到options中Grep-Extract中添加匹配规则
选中并复制
粘贴到这里
找到Redirections模块设置允许重定向,选择always
爆破准备环节完成,开始爆破。
爆破成功
Command Injection 用户可以执行恶意代码语句,在实战中危害比较高,也称作命令执行,一般属于高危漏洞。
Low 查看源码
php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];
// 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
echo "
{$cmd}";
}
?>注意到这里有两个函数,stristr和php_uname
stristr函数搜索字符串在另一字符串中的第一次出现,返回字符串的剩余部分(从匹配点),如果未找到所搜索的字符串,则返回 FALSE。参数string规定被搜索的字符串,参数search规定要搜索的字符串(如果该参数是数字,则搜索匹配该数字对应的 ASCII 值的字符),可选参数before_true为布尔型,默认为"false" ,如果设置为 "true",函数将返回 search 参数第一次出现之前的字符串部分。
php_uname(mode)这个函数会返回运行php的操作系统的相关描述,参数mode可取值”a” (此为默认,包含序列”s n r v m”里的所有模式),”s ”(返回操作系统名称),”n”(返回主机名),” r”(返回版本名称),”v”(返回版本信息), ”m”(返回机器类型)。
这里通过判断操作系统执行不同ping命令,但是对ip参数并未做任何的过滤,导致任意命令注入。
结果验证
Medium 查看源码
php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];
// Set blacklist
$substitutions = array(
'&&' => '',
';' => '',
);
// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
// 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
echo "
{$cmd}";
}
?>相比Low级别的代码,这里对ip参数做了一定过滤,str_replace把&& 、;替换为空字符,因为被过滤的只有&&与;,所以可以使用&绕过。由于使用的是str_replace把”&&” 、”;”替换为空字符,因此可以采用以下方式绕过:127.0.0.1&;&ipconfig
High 查看源码
php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = trim($_REQUEST[ 'ip' ]);
// Set blacklist
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
// 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
echo "
{$cmd}";
}
?>这里进一步完善了黑名单,把”| ”(注意这里|后有一个空格)替换为空字符,于是 可以用”|”绕过。
CSRF 跨站请求伪造 Medium 首先尝试低级别的payload
http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=2&password_conf=2&Change=Change#失败了,抓包看看正常修改密码和利用csrf修改密码的包有什么不一样 正常修改密码 csrf修改 发现利用csrf构造的包差一个referer头,查看源代码可知,中级确实对referer做出了认证, 所以只需要将正常修改密码的referer信息添加到包里面就好 修改成功
High 正常修改密码的样子
可以看到高级增加了token验证,并且每次都会改变,所以这里使用XSS漏洞先获取token值切换到存储型XSS,payload:
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "
ID: {$id}";
First name: {$first}
Surname: {$last}
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>由以上源码可知,这个级别在sql语句后面加了个limit函数,不要怕,可以直接用#注释,并且这里输入ID值会自动跳转到另一个页面,防止了自动化的SQL注入。当然我们本就不是以脚本小子的身份来做这道题,所以防止自动化对于本就是手工注入的我们没有意义。话不多说,直接开始。
这个界面有点熟悉,经过我的简单判断后,发现它竟然和Low级别一摸一样,就加了个花里胡哨的limit函数,一个注释符就解决了,这里就不加赘述了,直接看Low级别。
Impossible 又到了这个不可能级别,我们来看看它的源码
php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$id = $_GET[ 'id' ];
// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();
// Make sure only 1 result is returned
if( $data->rowCount() == 1 ) {
// Get values
$first = $row[ 'first_name' ];
$last = $row[ 'last_name' ];
// Feedback for end user
echo "
ID: {$id}";
First name: {$first}
Surname: {$last}
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>不可能级别永远的神,这里竟然使用了PDO技术,直接杜绝了SQL注入。没意思,溜之。
SQL Injection (Blind) sql盲注,这边直接sqlmap跑或者python脚本
Low pythonsqlmap -u "http://127.0.0.1/vulnerabilities/sqli_blind/?id=1*&Submit=Submit#" --cookie="你的cookie" --dbms=MySQL --technique=B --random-agent --flush-session -v 3因为登录之后的页面存在sql注入,所以要指定cookie
Medium pythonsqlmap -u "http://127.0.0.1:8888/vulnerabilities/sqli_blind/" --cookie="你的cookie" --data="id=1*&Submit=Submit" --dbms=MySQL --technique=B --random-agent --flush-session -v 3High 从cookie 中获取 id 然后倒入到数据库中查询
pythonsqlmap -u "http://127.0.0.1:8888/vulnerabilities/sqli_blind/" --cookie="id=1*;你的cookie" --dbms=MySQL --technique=B --random-agent --flush-session -v 3Weak Session IDs 脆弱的 Session 会话认证伪造
Low 查看源码
phpif ($_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);
}发现 dvwaSession 的值每次生成 +1 ,直接遍历 dvwaSession 来获取用户信息
Medium 查看源码
phpif ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = time();
setcookie("dvwaSession", $cookie_value);
}发现这里根据 time() 时间戳来生成 dvwaSession 的值,这里时间戳是有规律的,直接利用在线时间戳生成转换即可
High 查看源码
PHPif ($_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);
}就多了个md5编码加密,任然可以猜解遍历
Impossible phpif ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = sha1(mt_rand() . time() . "Impossible");
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], true, true);
}dvwaSession 的值为 sha1(随机数+时间+“impossbile”)
XSS-Reflected Low 1、先将安全等级跳到low 2、查看源代码发现没有任何的安全过滤措施 3、直接尝试一般的xss攻击
javascript 成功 获取cookie
javascriptMedium 1、先把安全等级跳到medium 2、先尝试一般xss攻击发现不行 3、查看源码发现这里使用str_replace函数对参数进行了简单的替换,过滤 4、我们可以直接用大小写绕过
javascript成功
High 1、先把安全等级跳到high 2、这里因为安全等级过高就不客套了直接查看源码,发现他这次使用了preg_replace正则表达式对
Medium 把等级调到Medium,查看源码 这里把我们的script给if没了(如果发现了script则输入结果自动转为English),但是这是小问题,可以像做反射型和存储型一样换个标签构造一下就ok
gohttp://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=
High 先把等级跳到high,然后查看源码 这里直接在服务器后端判断,指定default的值必须为select选择菜单中的值,这里就要用到URL中的一个特殊字符#,这个字符后的数据不会发送到服务器端,相当于绕过了后端的过滤,不需要与服务端进行交互,直接在客户端处理数据的阶段执行。
gohttp://127.0.0.1/dvwa/vulnerabilities/xss_d/?#default=
Impossible 不可能级别,查看源码 Don't need to do anything, protction handled on the client side
不需要做任何事情,保护就在客户端
XSS-Stored 相关概念 存储型XSS(stored)又被称为持久性XSS,存储型XSS是最危险的一种跨站脚本。允许用户存储数据的Web应用程序都可能会出现存储型XSS漏洞,当攻击者提交一段XSS代码后,被服务器端接收并存储,当攻击者再次访问某个页面时,这段XSS代码被程序读出来响应给浏览器,造成XSS跨站攻击,这就是存储型XSS。存储型XSS与反射型XSS、DOM型XSS相比,具有更高的隐蔽性,危害性也更大。
Low 1、首先将安全级别调为low 尝试一般的脚本输入 成功,每次刷新该页面都会显示该弹窗
Medium 先调一下安全级别 这次按照low的方式再次尝试,失败,用大小写绕过仍然失败,查看源码发现这里使用htmlspecialchars函数对Massage参数进行了html实体转义,这个场景与我上一篇文章DVWA-XSS-Reflected(low、medium、high、Impossible)中的Impossible相同,故这里直接避开这个玩意,改在name里写入脚本。
在name里写入发现有字符限制导致我们的脚本语句不能完整输入
High 下面我们把安全等级继续加大调到high,然后查看源码 这次直接在name用正则表达式把咱的script给过滤了,那咱就用上一篇文章用的老办法直接改个标签就完事(这里记住输入之前像medium中那样改一下字符限制) 成功
Impossible 又到了这个特别牛的级别,直接查看源码 好家伙,直接把两个输入点都给html实体转义。
Content Security Policy (CSP) Bypass CSP是什么 Content Security Policy(CSP),内容(网页)安全策略,为了缓解潜在的跨站脚本问题(XSS攻击),浏览器的扩展程序系统引入了内容安全策略(CSP)这个概念。在先前的XSS攻击介绍中,主要都是利用函数过滤/转义输入中的特殊字符,标签,文本来应对攻击。CSP则是另外一种常用的应对XSS攻击的策略。CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。
两种方法可以启用 CSP
一种是通过 HTTP 响应头信息的Content-Security-Policy字段。一种是通过网页的标签。
例如
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;";phpscript-src, //脚本:只信任当前域名
object-src: //不信任任何URL,即不加载任何资源
style-src, //样式表:只信任cdn.example.org和third-party.org
child-src: //必须使用HTTPS协议加载。这个已从Web标准中删除,新版本浏览器可能不支持。启用CSP后,不符合 CSP 的外部资源就会被阻止加载。
Low 查看源码
php
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.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://hastebin.com/raw/ohulaquzex
?>
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
";
}
$page[ 'body' ] .= '
';分析源码不难看出白名单网址为
self
https://pastebin.com
example.com
code.jquery.com
https://ssl.google-analytics.com其中 pastebin.com 是一个快速分享文本内容的网站,这个内容我们是可控的,可以在这里面插入 XSS 攻击语句:
javascriptalert(document.cookie)然后将网址https://pastebin.com/raw/2K8HVwTf填写到文本框中,提交即可触发XSS
Medium 查看源码
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");
#
?>
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
';http头信息中的script-src的合法来源发生了变化,说明如下
phpunsafe-inline //允许使用内联资源,如内联< script>元素,javascript:URL,内联事件处理程序(如onclick)和内联< style>元素。必须包括单引号。
nonce-source //仅允许特定的内联脚本块,nonce=“TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA”直接输入以下代码
javascript
注入成功
High 查看源码
php
$headerCSP = "Content-Security-Policy: script-src 'self';";
header($headerCSP);
?>
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
';javascriptfunction 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();
});
}CSP 规则限制,只能引用允许self 的脚本执行,self是指本页面加载的脚本,服务器只信任自己的域名,只允许加载本界面的JavaScript代码,这样的话自能从客户端本身动手脚了
这关的突破点在于自己给参数,创造传参
POST 提交的 include 参数直接放到了 body 源码中
所以这里我们可以自己修改include 来进行弹窗
javascriptinclude=
Impossible
header("Content-Type: application/json; charset=UTF-8");
$outp = array ("answer" => "15");
echo "solveSum (".json_encode($outp).")";
?>
echo "solveSum (".json_encode($outp).")";
function solveSum(obj) {
if ("answer" in obj) {
document.getElementById("answer").innerHTML = obj['answer'];
}
}这关直接把js写死了,只能回调 JS 里面的 solveSum 函数,所以就没办法啦
JavaScript Attacks 这是一种比较新颖的玩法,通过捕获js中的漏洞进行,成功提交success就算赢
Low 查看源码
javascript
$page[ 'body' ] .= << /* 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< 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(); EOF; ?>看着挺吓人,其实没什么东西,就是从github上弄了个md5运算的脚本然后在前端生成了一个token F12审计一波,发现这里实际提交的是ChangeMe,不是succes 所以我们直接修改token的值,以实现提交succes的目标,通过console得到succes的token 然后修改填入即可 成功提交 Medium 查看源码 php//medium.php
$page[ 'body' ] .= << EOF; ?>javascript//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") }分析源码可知js代码中调用do_elsesomething()函数,而do_elsesomething()函数中有生成token的代码,其中将传入参数e、前端表单输入的phrase值以及”XX”字符串进行拼接再调用do_something()函数进行字符串翻转处理。 了解token的规律后,直接构造payload提交即可 shelltoken=XXsseccusXX&phrase=success&send=Submit High 查看源码,发现前端js代码被加密混淆 解密工具解密http://deobfuscatejavascript.com/# 解密后的源码 javascriptfunction 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"),但是由于设置了延时,所以其实是先执行token_part_1("ABCD", 44);,再执行token_part_2("XX"),最后点击click的时候就会执行token_part_3。 无论我们想调试所有代码还是只调试指定的片段,主要的问题是如何将我们的反混淆代码插入到**http://localhost/dvwa/vulnerabilities/javascript/source/high.js**文件中;如果不解决这个问题,那么对混淆代码的调试将变得不可能,因为在控制权传递给真正的功能片段之前,之前的无意义代码可以执行数百万次操作。有一种方法可以摆脱这种情况——此外,即时更改文件并在重新加载页面后保存这些更改的功能直接在浏览器开发人员工具中。参考[如何在页面重新加载后保留浏览器开发人员工具中的更改](https://miloserdov.org/?p=4672)”。 插入反混淆代码,设置断点 将此页面添加到“保存为覆盖”并重新加载页面 最后到控制台里面设置 phrase 的值 javascriptdocument.getElementById("phrase").value = "success";然后就可以通关了 Impossible You can never trust anything that comes from the user or prevent them from messing with it and so there is no impossible level.你永远不能相信来自用户的任何东西或阻止他们弄乱它,所以没有不可能的水平。 没有绝对安全,除非你关服。 至此,DVWA靶场全部通关。