环境部署https://blog.csdn.net/2302_82189125/article/details/135834194
1.Brute Force
low
result
medium
同样的插入方法
high
爆破成功
但是有一个问题需要考虑,为什么要使用热加载,又为什么热加载要那样设置,而且要注释最后一行,接下来我们来分析一下
观察high跟前俩关的题目可以发现high难度多了一个参数 user_token
,猜测存在csrf-token
检验,所以使用热加载
用这个token的模板
接下来我们分析这个模板
1. 预请求获取页面 | <font style="color:rgb(0, 0, 0);">rsp, _, err = poc.HTTP(``)</font> |
发送一个GET请求(通常是登录页面),以获取包含最新Token的页面源码和会话Cookie。这是后续所有操作的基础。 |
---|---|---|
2. 提取会话Cookie | <font style="color:rgb(0, 0, 0);">cookie = poc.GetHTTPPacketHeader(rsp, "Set-Cookie")</font> |
从预请求的响应头中拿到<font style="color:rgb(0, 0, 0);">Set-Cookie</font> 值。这是维持会话状态的关键,确保后续登录请求被视为同一会话的一部分。 |
3. 解析HTML文档 | <font style="color:rgb(0, 0, 0);">node, err = xpath.LoadHTMLDocument(rsp)</font> |
将页面源码加载为一个可被XPath查询的文档对象模型(DOM),为精准定位Token输入框做准备。 |
4. 定位Token元素 | <font style="color:rgb(0, 0, 0);">tokenNode = xpath.FindOne(node, "//input[@name='token']")</font> |
使用XPath语法 <font style="color:rgb(0, 0, 0);">//input[@name='token']</font> 在DOM树中查找属性<font style="color:rgb(0, 0, 0);">name</font> 为<font style="color:rgb(0, 0, 0);">'token'</font> 的输入框(<font style="color:rgb(0, 0, 0);"><input></font> )元素。在DVWA中,这个Token的名称通常是 **<font style="color:rgb(0, 0, 0);">user_token</font>** ,所以这里的XPath可能需要调整为 <font style="color:rgb(0, 0, 0);">//input[@name='user_token']</font> 才能正确抓取。 |
5. 获取Token值 | <font style="color:rgb(0, 0, 0);">token = xpath.SelectAttr(tokenNode, "value")</font> |
从找到的Token输入框元素中提取其<font style="color:rgb(0, 0, 0);">value</font> 属性的值,这就是我们需要的、当前有效的Token字符串。 |
6. 替换请求参数 | <font style="color:rgb(0, 0, 0);">req = req.ReplaceAll("__TOKEN__", token)</font> |
将主登录请求数据包中的占位符 <font style="color:rgb(0, 0, 0);">__TOKEN__</font> 替换为刚刚获取到的真实Token值。 |
7. 更新请求Cookie | <font style="color:rgb(0, 0, 0);">req = poc.AppendHTTPPacketHeader(req, "Cookie", cookie)</font> |
将主登录请求的<font style="color:rgb(0, 0, 0);">Cookie</font> 头更新为最新获取的会话Cookie,保持会话连续性。 |
这里为什么要注释最后一行呢,我们来看看不注释最后一行会发生什么
会发现25个包全部302了
<font style="color:rgb(0, 0, 0);">req = poc.AppendHTTPPacketHeader(req, "Cookie", cookie)</font>
的逻辑是获取页面的set-cookie然后赋值给cookie,建立新的会话
让我们来看看正常的请求包
但其实正常的响应包是没有set-cookie
这个头的,所以这个模板拿不到cookie,就会尝试把新的cookie置空或者是更改格式之类 导致了错误 导致重定向
就出现了set-cookie让我们重新建立会话
让我们把最后一行代码改成
if cookie != "" && cookie != nil { req = poc.AppendHTTPPacketHeader(req, "Cookie", cookie) }
就是在cookie不等于空的时候再进行cookie的替换操作
发现成功爆破了密码出来
Set-Cookie的触发条件:
服务器通常只在以下情况下返回<font style="color:rgb(0, 0, 0);">Set-Cookie</font>
:
- 创建新会话时
- 会话过期需要刷新时
- 用户登录/注销时
- 安全策略要求更新会话时
但事实上还是发现会出现有一部分302 一部分200的情况
如果正确密码响应302了就会出现爆破失败的情况
这个是因为yakit高速请求之后服务端可能有时候会话失效的情况,解决这个可以降低并发线程
经测试 线程20降到5的时候 25个包会有3个失效 已经降到很低了 很大的改良了出错的情况 但是还是会出错,所以最好的解决方案是进行多次尝试
源码分析
<?php
// 检查是否提交了登录请求(是否点击了Login按钮)
if( isset( $_GET[ 'Login' ] ) ) {// Get username$user = $_GET[ 'username' ]; // 直接获取用户输入的用户名,未做任何过滤// Get password$pass = $_GET[ 'password' ]; // 直接获取用户输入的密码$pass = md5( $pass ); // 对密码进行MD5哈希加密(但数据库中存储的很可能就是MD5值,直接对比)// Check the database// 关键问题:直接将用户输入拼接到SQL查询语句中,存在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 ) {// Get users details$row = mysqli_fetch_assoc( $result ); // 获取查询结果数组$avatar = $row["avatar"]; // 从结果中获取头像路径// Login successfulecho "<p>Welcome to the password protected area {$user}</p>";echo "<img src=\"{$avatar}\" />"; // 显示欢迎信息和头像}else {// Login failedecho "<pre><br />Username and/or password incorrect.</pre>"; // 登录失败提示}// 关闭数据库连接((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
if( isset( $_GET[ 'Login' ] ) ) {// Get username$user = $_GET[ 'username' ]; // 直接获取用户输入的用户名,未做任何过滤// Get password$pass = $_GET[ 'password' ]; // 直接获取用户输入的密码$pass = md5( $pass );
这一段代码读取传入的值
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$query = "SELECT * FROM
users WHERE user = '$user' AND password = '$pass';";
查询数据库中有没有符合传入的值的数据
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>' );
<font style="color:rgb(0, 0, 0);">$GLOBALS["___mysqli_ston"]</font>
这个变量是 DVWA 特有的
这个变量是 DVWA 为了方便内部数据库连接管理而定义的。DVWA 将数据库连接对象存储在 PHP 的 <font style="color:rgb(0, 0, 0);">$GLOBALS</font>
超全局数组中,并命名为 <font style="color:rgb(0, 0, 0);">"___mysqli_ston"</font>
。这样,在DVWA的不同脚本和函数中,都能通过这个全局变量名来访问同一个数据库连接,而不需要每次都重新创建连接或传递连接对象
不需要每次都创建连接,节省了成本
// 检查查询结果是否恰好有一条记录if( $result && mysqli_num_rows( $result ) == 1 ) {// Get users details$row = mysqli_fetch_assoc( $result ); // 获取查询结果数组$avatar = $row["avatar"]; // 从结果中获取头像路径// Login successfulecho "<p>Welcome to the password protected area {$user}</p>";echo "<img src=\"{$avatar}\" />"; // 显示欢迎信息和头像}else {// Login failedecho "<pre><br />Username and/or password incorrect.</pre>"; // 登录失败提示}
这一段检验提交上去的账密是否存在
通过mysqli_fetch_assoc
获取这个用户的所有数据,用于返回在页面上
剩下就是一些欢迎 跟失败提示
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);}
实现数据库的断开
所以爆破就是碰撞数据库中的账密
<?phpif( isset( $_GET[ 'Login' ] ) ) {// Sanitise username input$user = $_GET[ 'username' ];$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));// Sanitise password input$pass = $_GET[ 'password' ];$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));$pass = md5( $pass );// Check the database$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );if( $result && mysqli_num_rows( $result ) == 1 ) {// Get users details$row = mysqli_fetch_assoc( $result );$avatar = $row["avatar"];// Login successful$html .= "<p>Welcome to the password protected area {$user}</p>";$html .= "<img src=\"{$avatar}\" />";}else {// Login failedsleep( 2 );$html .= "<pre><br />Username and/or password incorrect.</pre>";}((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}?>
$user = $_GET[ 'username' ];$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));// Sanitise password input$pass = $_GET[ 'password' ];$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));$pass = md5( $pass );
相比low.php
只有这里有区别 先校验了全局变量是否存在 然后把用户提交数据进行mysqli_real_escape_string
处理 过滤了'
,防御了一定的sql注入,后面是一个抛出错误
mysqli_real_escape_string
转义的相关字符如下
被转义的字符 | 含义 | 转义为 | 转义目的 |
---|---|---|---|
**<font style="color:rgb(0, 0, 0);">'</font>** (单引号) |
字符串分隔符 | <font style="color:rgb(0, 0, 0);">\'</font> |
防止提前终止字符串,插入恶意代码 |
**<font style="color:rgb(0, 0, 0);">"</font>** (双引号) |
字符串分隔符(若数据库使用双引号) | <font style="color:rgb(0, 0, 0);">\"</font> |
同上 |
``(反斜线) | 转义字符本身 | <font style="color:rgb(0, 0, 0);">\\</font> |
防止转义字符自身被误解,确保其作为普通字符 |
**<font style="color:rgb(0, 0, 0);">NULL</font>** (ASCII 0) |
字符串结束符 | <font style="color:rgb(0, 0, 0);">\0</font> |
防止在某些特定情况下被截断 |
**<font style="color:rgb(0, 0, 0);">\n</font>** (换行符) |
换行 | <font style="color:rgb(0, 0, 0);">\n</font> |
避免被解释为命令的一部分 |
**<font style="color:rgb(0, 0, 0);">\r</font>** (回车符) |
回车 | <font style="color:rgb(0, 0, 0);">\r</font> |
同上 |
**<font style="color:rgb(0, 0, 0);">Control-Z</font>** (ASCII 26) |
DOS文件结束符 | <font style="color:rgb(0, 0, 0);">\Z</font> |
防止在Windows系统等中被误认为文件结束 |
else {// Login failedsleep( 2 );$html .= "<pre><br />Username and/or password incorrect.</pre>";}
这里还多了一句 如果登陆失败就sleep2秒 无伤大雅
<?phpif( isset( $_GET[ 'Login' ] ) ) {// Check Anti-CSRF tokencheckToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );// Sanitise username input$user = $_GET[ 'username' ];$user = stripslashes( $user );$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));// Sanitise password input$pass = $_GET[ 'password' ];$pass = stripslashes( $pass );$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));$pass = md5( $pass );// Check database$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );if( $result && mysqli_num_rows( $result ) == 1 ) {// Get users details$row = mysqli_fetch_assoc( $result );$avatar = $row["avatar"];// Login successfulecho "<p>Welcome to the password protected area {$user}</p>";echo "<img src=\"{$avatar}\" />";}else {// Login failedsleep( rand( 0, 3 ) );echo "<pre><br />Username and/or password incorrect.</pre>";}((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}// Generate Anti-CSRF token
generateSessionToken();?>
加了csrftoken的校验
<font style="color:rgb(0, 0, 0);">stripslashes()</font>
反转义函数防御sql注入
Command Injection
由于windows部署dvwa 以whoami或calc执行成功为标志
low
<font style="color:rgb(0, 0, 0);">;</font>
:按顺序执行多条命令。<font style="color:rgb(0, 0, 0);">&&</font>
:只有前一个命令成功才执行后一个。<font style="color:rgb(0, 0, 0);">|</font>
:将前一个命令的输出作为后一个命令的输入。<font style="color:rgb(0, 0, 0);">
<font style="color:rgb(0, 0, 0);">或 </font>
$()`:命令替换。
源码
<?phpif( isset( $_POST[ 'Submit' ] ) ) {// Get input$target = $_REQUEST[ 'ip' ];// 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>";
}?>
linux ping -c 4
是为了防止一直ping下去
medium
<?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>";
}?>
细看并没有过滤的|
过滤的是|
,所以依旧|
即可