Dvwa High通关指南(建议收藏)
学习网络攻防技术一定离不开靶场练习,Dvwa是一个非常经典的靶场,涵盖csrf、sql注入、文件包含等漏洞环境,并有Low、Medium、High、Impossible四种不同的安全等级,适合新手练习,通过该靶场可以由浅入深的学习漏洞原理和代码审计。
本文是i春秋论坛版主「Adian大蝈蝈」表哥直接在Dvwa high进行测试的完整攻略,对靶场练习是一个非常好的指导,感兴趣的小伙伴快来学习吧。
DVWA共有14个漏洞选项,我们逐一来看:
- Brute Force
- Command Injection
- CSRF
- File Inclusion
- File Upload
- Insecure CAPTCHA
- SQL Injection
- SQL Injection (Blind)
- Weak Session IDs
- XSS (DOM)
- XSS (Reflected)
- XSS (Stored)
- CSP Bypass
- JavaScript
Brute Force
我们先来看看high.php
<?phpif( 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 tokengenerateSessionToken();?>
High级别的猜解加了一个防止CSRF的验证token,使用了stripslashes( )等函数来转义或过滤,虽然加大了猜解的难度,但是还是可以猜解的。
我们先正常抓包:
找到Redirections选中always允许重定向:
最后在Options中找到Grep-Extract模块,点击Add,并设置筛选条件,得到user_token。
然后设置payload,带token参数的paylaod直接把token粘贴进去就可以了,其他照常。
然后开始猜解,关于其他文章提到的线程设置为1,新版本的burpsuite设置了Pitchfork之后,就默认为1不可更改,所以这个问题不再叙述了。
Command Injection(命令执行)
我们先简单的试一下:
可见$被过滤了,在看一下代码:
<?phpif( 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>";}?>
代码可以看见将$;()都进行了转换,转成了空字符串,这也就导致了我们输入的这些能同时执行其他命令的符号都无法使用了。
但是仔细看过滤 "| ",如果我们把后面的空格删去直接执行,也是可以执行的。
CSRF
还是看源代码high.php
<?phpif( isset( $_GET[ 'Change' ] ) ) { // Check Anti-CSRF 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 $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);}// Generate Anti-CSRF tokengenerateSessionToken();?>
由上面的代码可见,High级别的代码加入了Anti-CSRF token机制,用户每次访问改密码时,服务器会返回一个随机的token,提交的参数带有正确的token才能执行,我们可以利用burp的插件CSRFTokenTracker绕过token验证,这里我借用一下其他人的图片。
装好之后,设置好名称和内容就可以直接去repeater里面测试了,每次的token会自动刷新。
File Inclusion(文件包含)
<?php// The page we wish to display$file = $_GET[ 'page' ];// Input validationif( !fnmatch( "file*", $file ) && $file != "include.php" ) { // This isn't the page we want! echo "ERROR: File not found!"; exit;}?
这次的代码量很小,大概解读一下:
if( !fnmatch( "file*", $file ) && $file != "include.php" )
如果没有这个文件或者这个文件不是include.php,那么就不会执行,但是我们可以使用file协议绕过。
File Upload(文件上传)
先看一下high.php:
<?phpif( isset( $_POST[ 'Upload' ] ) ) { // Where are we going to be writing to? $target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/"; $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] ); // File information $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ]; $uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1); $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ]; $uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ]; // Is it an image? if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) && ( $uploaded_size < 100000 ) && getimagesize( $uploaded_tmp ) ) { // Can we move the file to the upload folder? if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) { // No $html .= '<pre>Your image was not uploaded.</pre>'; } else { // Yes! $html .= "<pre>{$target_path} succesfully uploaded!</pre>"; } } else { // Invalid file $html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>'; }}?>
可以看到getimagesize( )函数来验证有没有相关的文件头等等,所以直接改格式不行,需要一个图片马儿,也会判断最后的'.'后的内容必须是jpg,jpeg,png三者之一。
图片马的制作很简单,打开cmd:
copy shell.php/b+test.png/a hack.png
简单的用记事本打开图片,在里面加入一句话也可以,然后我们用00截断的方式来绕过上传。
Insecure CAPTCHA(不安全验证码)
Insecure CAPTCHA,意思是不安全的验证码,CAPTCHA是Completely Automated Public Turing Test to Tell Computers and Humans Apart(全自动区分计算机和人类的图灵测试)的简称。
recaptcha_check_answer($privkey,$remoteip, $challenge,$response)
看一下代码:
<?phpif( isset( $_POST[ 'Change' ] ) ) { // Hide the CAPTCHA form $hide_form = true; // Get input $pass_new = $_POST[ 'password_new' ]; $pass_conf = $_POST[ 'password_conf' ]; // Check CAPTCHA from 3rd party $resp = recaptcha_check_answer( $_DVWA[ 'recaptcha_private_key' ], $_POST['g-recaptcha-response'] ); if ( $resp || ( $_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3' && $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA' ) ){ // CAPTCHA was correct. Do both new passwords match? if ($pass_new == $pass_conf) { $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $pass_new = md5( $pass_new ); // Update database $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); // Feedback for user $html .= "<pre>Password Changed.</pre>"; } else { // Ops. Password mismatch $html .= "<pre>Both passwords must match.</pre>"; $hide_form = false; } } else { // What happens when the CAPTCHA was entered incorrectly $html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>"; $hide_form = false; return; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);}// Generate Anti-CSRF tokengenerateSessionToken();?>
可以判断出当$resp == False以及g-recaptcha-response != hidd3n_valu3或者HTTP_USER_AGENT != reCAPTCHA的时候,验证码为错误,$resp的值我们控制不了,是由recaptcha_check_answer( )决定的,所以我从g-recaptcha-response和HTTP_USER_AGENT入手。
我们更改HTTP_USER_AGENT的值为reCAPTCHA
添加g-recaptcha-response的值为hidd3n_valu3
就ok了
SQL Injection(SQL注入)
<?phpif( isset( $_SESSION [ 'id' ] ) ) { // Get input $id = $_SESSION[ 'id' ]; // Check database $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' ); // Get results while( $row = mysqli_fetch_assoc( $result ) ) { // Get values $first = $row["first_name"]; $last = $row["last_name"]; // Feedback for end user $html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); }?>
和文件包含一样的简洁,前端比low级多了一个弹出框:
由于多了一个页面,所以我们不能直接sqlmap-u这样的语法了,而且还有cookie和session的限制(可以填进去,看看usage)。
所以我们要用到--second-order,抓个包,将内容都放到1.txt中然后执行。
sqlmap -r 1.txt -p id --second-order "http://192.168.242.1/dvw/vulnerabilities/sqli/" --level 2
SQL Injection (Blind)
high.php
<?phpif( isset( $_COOKIE[ 'id' ] ) ) { // Get input $id = $_COOKIE[ 'id' ]; // Check database $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors // Get results $num = @mysqli_num_rows( $result ); // The '@' character suppresses errors if( $num > 0 ) { // Feedback for end user $html .= '<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 $html .= '<pre>User ID is MISSING from the database.</pre>'; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);}?>
抓包将cookie中参数id改为1’ and length(database( ))=4 #,显示存在,说明数据库名的长度为4个字符;
抓包将cookie中参数id改为1’ and length(substr(( select table_name from information_schema.tables where table_schema=database( ) limit 0,1),1))=9 #,显示存在,说明数据中的第一个表名长度为9个字符;
抓包将cookie中参数id改为1’ and (select count(column_name) from information_schema.columns where table_name=0×7573657273)=8 #,(0×7573657273 为users的16进制),显示存在,说明uers表有8个字段。
Weak Session IDs
high.php
<?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);}?>
看到$cookie_value就是md5加密了last_session_id_high,last_session_id_high这个值初始为0,逐个+1然后md5加密,所以这个cookie校验对我们无效,构造payload使用火狐提交。
XSS (DOM)
high.php
<?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; }}?>
提交后url为:
http://192.168.159.129/vulnerabilities/xss_d/?default=English
<option value=''>English</option>
我们在里面插入Javascipt语句:
<option value=''>English #<script>alert(/xss/)</script></option>
这样两个标签都闭合,我们来看看效果:
XSS (Reflected)
high.php
<?phpheader ("X-XSS-Protection: 0");// Is there any input?if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { // Get input $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] ); // Feedback for end user $html .= "<pre>Hello ${name}</pre>";}?>
居然直接正则把<script>过滤了,双写大小写绕过都不可以,但是我们还可以插别的标签,比如img比如body。
<img src=1.jpg>
我们可以看见,这个标签执行了。
XSS (Stored)
high.php
<?phpif( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // Sanitize message input $message = strip_tags( addslashes( $message ) ); $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $message = htmlspecialchars( $message ); // Sanitize name input $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name ); $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Update database $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); //mysql_close();}?>
和上面一样,正则过滤<script>,还是和上面一样,用img标签。
<img src=1.jpg onerror=alert(/adian/)>
Burpsuite抓包改参数:
弹出
CSP Bypass
Content Security Policy(CSP),内容(网页)安全策略,为了缓解潜在的跨站脚本问题(XSS攻击),浏览器的扩展程序系统引入了内容安全策略(CSP)这个概念。具体内容可以参见《Content Security Policy 入门教程》,类似白名单的一种机制。
<?php$headerCSP = "Content-Security-Policy: script-src 'self';";header($headerCSP);?><?phpif (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>';
high.js
function clickButton() { var s = document.createElement("script"); s.src = "source/jsonp.php?callback=solveSum"; document.body.appendChild(s);}function solveSum(obj) { if ("answer" in obj) { document.getElementById("answer").innerHTML = obj['answer']; }}var solve_button = document.getElementById ("solve");if (solve_button) { solve_button.addEventListener("click", function() { clickButton(); });}
在网上找到了一段代码:
if (isset ($_POST['include'])) {$page[ 'body' ] .= " " . $_POST['include'] . "";}
来接收参数,然后再构造payload就可以了。
Javascript
high.php
<?php$page[ 'body' ] .= <<<EOF<script src="/vulnerabilities/javascript/source/high.js"></script>EOF;?>
生成token的步骤总结:
- 执行token_part_1(“ABCD”,44)
- 执行token_part_2(“XX”)(有300s的延迟)
- 执行token_part_3
- 然后控制台把token输入进去就ok了