靶场搭建
关于靶场的搭建,网上很多教程,我就不讲解了,这里推荐几个关于如何在kali系统搭建DVWA靶场教程供大家参考:
https://www.cnblogs.com/aeolian/p/11023238.html#autoid-1-0-0
http://kfbiji.com/article/bb63762f25b17b9c
顺便讲一下我搭建过程中遇到的错误解决经验。如果出现和下图一样的情况,已经搭建成功,但不能登录DVWA靶场,极有可能是因为config.inc.php这个文件中的配置错误,比如"db_password"或者"db_user"这两个的配置和在MySQL中的配置不一样,导致登录不了。解决方法就是在config.inc.php文件中要把错误的信息改为和MySQL中的配置一样,如果忘记密码用户名,可以到网上搜索怎样在MySQL中修改、重写用户名或者密码。
如果出现以下图片中这种情况,可以在config.inc.php文件中,把"db_server"中的“127.0.0.1”修改为“localhost”。
Brute Force
概述
在DVWA靶场中,共设立了四个安全级别来供大家学习,这四个级别分为low、Medium、High、impossible,接下来将对此级别分别进行演示。
low级别
在页面中随便输入用户名密码提交,然后打开burp suite进行抓包
把抓取到的包发送到 Intruder 模块进行破解
添加用户名和密码两个变量,攻击类型选择Cluster bomb
点击payloads,在payload type中选择Runtime file类型,然后选择已经写好的字典txt文件进行爆破(注:payload set 中的1是用户名爆破,2是密码爆破,2的操作和1一样)
弄好payload后,点击右上角的start attack开始爆破。
点击length,可以看到有一条的长度和别的是不一样的,这个就是我们爆破出来的正确的用户名和密码
拿爆破出来的用户名和密码去尝试登陆,是可以登陆成功的
low级别源代码:
<?php if( isset( $_GET[ 'Login' ] ) ) { // Get username $user = $_GET[ 'username' ]; // Get password $pass = $_GET[ 'password' ]; $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 echo "<p>Welcome to the password protected area {$user}</p>"; echo "<img src=\"{$avatar}\" />"; } else { // Login failed echo "<pre><br />Username and/or password incorrect.</pre>"; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
low级别的代码直接获取用户输入的用户名和密码,密码再经过MD5进行加密,所以杜绝了在密码处进行SQL注入的可能。这里对输入的用户名和密码没经过任何的过滤和检查等安全措施处理。
虽然在源代码中,它对密码进行MD5加密,杜绝了SQL注入的可能,但是它并没有对用户名进行任何的安全处理,所以我们可以在用户名这一栏尝试进行SQL注入。
在用户名这一栏中分别尝试输入以下的payload,每个payload都可以注入成功,从而绕过用户名和密码,登录成功。
SQL注入payload
admin'or '1'='1 admin' -- - admin' #
Medium 级别
源代码:
<?php if( isset( $_GET[ 'Login' ] ) ) { // Sanitise username input $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)) ? "" : "")); // Sanitise password input $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 ); // 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 echo "<p>Welcome to the password protected area {$user}</p>"; echo "<img src=\"{$avatar}\" />"; } else { // Login failed sleep( 2 ); echo "<pre><br />Username and/or password incorrect.</pre>"; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
观察Medium级别的源代码,发现它做了很多限制。mysqli_real_escape_string()函数会对特殊符号(\x00,\n,\r,\,‘,“,\x1a)进行转义,基本上抵御了SQL注入(拿在low级别测试的SQL注入payload去尝试,不能够登录,说明SQL注入已经失败)。
它还设置了一个sleep( 2 ),这个意思就是如果登录失败的话,就会延迟两秒后才能提交。
虽然在Medium级别中做了过滤、转义安全措施,但利用在low级别中的爆破方法还是能爆破成功的,只不过时间久了点。
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 echo "<p>Welcome to the password protected area {$user}</p>"; echo "<img src=\"{$avatar}\" />"; } else { // Login failed 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); } // Generate Anti-CSRF token generateSessionToken(); ?>
查看high级别的源代码,可以发现它在Medium级别的安全措施基础上增添了多个安全措施。
stripslashes()函数可以用来去掉反斜线字符("\");使用 Anti-CSRF token,用来随机生成token值发送到后台验证登录信息是否正确。
在High级别的代码中使用了Anti-CSRF token来抵御CSRF的攻击,使用了stripslashes函数和mysqli_real_esacpe_string来抵御SQL注入和XSS的攻击,这些安全措施都加大了爆破的难度。
我们使用之前的方法来测试,发现不会成功,长度都是一样的,状态全部为302(status为200时则成功)。
以前的方法之所以会失败,就是因为每次刷新登录页面的时候,他都会随机生成一个token值去到后台认证
所以我们可以去获取这个token值,在爆破的同时一并提交该值,这样就能爆破成功。
要获取token值可以用python脚本来获取(但本人能力不行,这个方法就算了...),还好burpsuite这个神器是可以获取token值的,只不过麻烦点。
在登录页面中输入正确的用户名,密码随便输入,然后bp抓包后,发送到Intruder 模块中,add添加password和user_token两个变量(注:这里的用户名必须是正确的,我们只爆破password和user_token),攻击类型选择Pitchfork方式(注:Pitchfork攻击类型是最多只能选择两个变量的)
接下来我们进行关于token值的设置,点击Options,在Grep-Extract中点击add
找到token,点击token值后,它就会自动生成红框中的值,然后点击ok
点击payloads,payload set中1的设置和之前一样,只不过得选择密码字典
payload set:2中,payload type选择Recursive grep类型,然后在红框中输入bp抓包时,抓取到的token值(也就是user_token的值)
点击开始爆破时会出现报错:recursive grep payloads cannot be used with multiple request threads,这是因为递归不能使用多线程,所以我们要把多线程设置为单线程
在Options中,把多线程改为1也就是单线程,同时在Options 中找到 Redirections设置为Always
设置完后,点击开始爆破,可以看到它在爆破的时候,会自动生成token值一起提交,爆破成功。
Impossible级别
源代码:
<?php if( isset( $_POST[ 'Login' ] ) && isset ($_POST['username']) && isset ($_POST['password']) ) { // Check Anti-CSRF token checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // Sanitise username input $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)) ? "" : "")); // Sanitise password input $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 ); // Default values $total_failed_login = 3; $lockout_time = 15; $account_locked = false; // Check the database (Check user information) $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(); // Check to see if the user has been locked out. if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) { // User locked out. Note, using this method would allow for user enumeration! //echo "<pre><br />This account has been locked due to too many incorrect logins.</pre>"; // Calculate when the user would be allowed to login again $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 />"; */ // Check to see if enough time has passed, if it hasn't locked the account if( $timenow < $timeout ) { $account_locked = true; // print "The account is locked<br />"; } } // Check the database (if username matches the password) $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 its a valid login... if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) { // Get users details $avatar = $row[ 'avatar' ]; $failed_login = $row[ 'failed_login' ]; $last_login = $row[ 'last_login' ]; // Login successful echo "<p>Welcome to the password protected area <em>{$user}</em></p>"; echo "<img src=\"{$avatar}\" />"; // Had the account been locked out since last login? 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>"; } // Reset bad login count $data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' ); $data->bindParam( ':user', $user, PDO::PARAM_STR ); $data->execute(); } else { // Login failed sleep( rand( 2, 4 ) ); // Give the user some feedback 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>"; // Update bad login count $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(); } // Set the last login time $data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' ); $data->bindParam( ':user', $user, PDO::PARAM_STR ); $data->execute(); } // Generate Anti-CSRF token generateSessionToken(); ?>
通过源代码可以看到,impossible级别在high级别的基础上对用户登录次数进行了限制,当用户登录失败3次后,后台就会锁住账号,在15分钟之内无法进行任何的操作,同时它也采用了更为安全的机制来抵御SQL注入。(这个级别看看就行了,厉害的大佬可以去尝试尝试)
成功锁住15分钟。。。