學習網絡攻防技術一定離不開靶場練習,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( )等函數來轉義或過濾,雖然加大了猜解的難度,但是還是可以猜解的。
我們先正常抓包:

得到完整數據包之后,我們把需要猜解的參數范圍選中user_token和password,選擇Pitchfork測試類型。

找到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了