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