[网络安全] DVWA之CSRF攻击姿势及解题详析合集
CSRF
CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的Web应用程序安全漏洞,它利用了用户在已认证的网站中的身份,通过欺骗用户发起非预期的请求。 攻击者会构造一个恶意网页,使用户在浏览器中访问该网页时,自动向目标网站发送了未经用户授权的请求。
CSRF攻击的原理是利用了Web应用程序对用户请求的信任,攻击者构造一个恶意请求并诱使用户触发,从而达到攻击的目的。
常见的CSRF攻击包括修改用户密码、发送电子邮件、进行资金转账等。
Low level
源代码
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// 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);
}
?>
代码审计
- 首先检查
$_GET['Change']
是否存在,以确定是否有密码更改请求。 - 代码获取了输入的新密码和确认密码。
- 判断新密码和确认密码是否匹配。
- 如果匹配,则对新密码进行处理:
mysqli_real_escape_string
函数用于对新密码进行数据库转义,以防止SQL注入攻击。md5
函数用于对新密码进行MD5哈希加密。
- 更新数据库中用户密码的语句被构建,并执行更新操作。
- 根据操作结果向用户提供相应的反馈信息。
此外,在代码中还调用了一个名为dvwaCurrentUser
的函数,该函数返回当前用户的用户名。
姿势
分别输入1,回显如下:
发现参数以GET方式提交
于是可修改参数为password_new=2&password_conf=2
再打开链接:
由页面回显可知,密码修改成功
即成功实施了CSRF
Medium level
源代码
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
// 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>";
}
}
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);
}
?>
代码审计
- 首先,代码检查是否设置了名为
Change
的GET参数,以确定是否有密码更改请求。 - 代码使用
stripos()
函数检查请求来源是否在同一服务器上,以确保请求来自可信任的来源。 - 如果请求来源合法,则获取新密码和确认密码的输入。
- 检查新密码和确认密码是否匹配。
- 如果密码匹配,则对新密码进行处理:
- 首先,使用
mysqli_real_escape_string()
函数对新密码进行数据库转义,以防止SQL注入攻击。 - 然后,使用
md5()
函数对新密码进行MD5哈希加密。请注意,MD5已经不再被认为是一种安全的哈希算法。
- 首先,使用
- 构建更新数据库中用户密码的SQL语句,并执行更新操作。
- 根据操作结果向用户提供相应的反馈信息。
此外,在代码中还调用了一个名为dvwaCurrentUser()
的函数,该函数返回当前用户的用户名。
姿势
与Low级别不同的是,Medium级别源代码中加入了该语句
stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ])
即判断 HTTP_REFERER中是否包含SERVER_NAME,即检查请求来源是否在同一服务器上。
HTTP_REFERER是Referer参数值,即来源地址
SERVER_NAME是host参数及主机ip名
思路:在dvwa的www目录下
写入一个html文件,该文件包含CSRF链接,抓包使请求包包含该文件即可
故写一个html文件,命名为主机地址.html
内容为:
<img src=“http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=2&password_conf=2&Change=Change#” border=“0” style=“display:none;”/>
主机地址可在 cmd
中输入 ipconfig
得到
抓包后修改参数:
发包后,从右侧回显可知,密码修改成功。
High level
源代码
<?php
$change = false;
$request_type = "html";
$return_message = "Request Failed";
if ($_SERVER['REQUEST_METHOD'] == "POST" && array_key_exists ("CONTENT_TYPE", $_SERVER) && $_SERVER['CONTENT_TYPE'] == "application/json") {
$data = json_decode(file_get_contents('php://input'), true);
$request_type = "json";
if (array_key_exists("HTTP_USER_TOKEN", $_SERVER) &&
array_key_exists("password_new", $data) &&
array_key_exists("password_conf", $data) &&
array_key_exists("Change", $data)) {
$token = $_SERVER['HTTP_USER_TOKEN'];
$pass_new = $data["password_new"];
$pass_conf = $data["password_conf"];
$change = true;
}
} else {
if (array_key_exists("user_token", $_REQUEST) &&
array_key_exists("password_new", $_REQUEST) &&
array_key_exists("password_conf", $_REQUEST) &&
array_key_exists("Change", $_REQUEST)) {
$token = $_REQUEST["user_token"];
$pass_new = $_REQUEST["password_new"];
$pass_conf = $_REQUEST["password_conf"];
$change = true;
}
}
if ($change) {
// Check Anti-CSRF token
checkToken( $token, $_SESSION[ 'session_token' ], 'index.php' );
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = mysqli_real_escape_string ($GLOBALS["___mysqli_ston"], $pass_new);
$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 );
// Feedback for the user
$return_message = "Password Changed.";
}
else {
// Issue with passwords matching
$return_message = "Passwords did not match.";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
if ($request_type == "json") {
generateSessionToken();
header ("Content-Type: application/json");
print json_encode (array("Message" =>$return_message));
exit;
} else {
echo "<pre>" . $return_message . "</pre>";
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
代码审计
- 首先,代码检查请求是否为POST方法,并且内容类型为application/json。如果满足条件,则将请求解析为JSON格式,并获取相应的参数。
- 如果不满足JSON请求条件,则检查非JSON请求的参数。
- 如果满足密码更改的条件,则进行以下操作:
- 检查防跨站请求伪造(Anti-CSRF)令牌。
- 检查新密码和确认密码是否匹配。
- 对新密码进行数据库转义和MD5哈希加密。
- 构建更新数据库中用户密码的SQL语句,并执行更新操作。
- 根据操作结果向用户提供相应的反馈信息。
- 关闭数据库连接。
- 根据请求类型,生成新的Anti-CSRF令牌并返回响应。
姿势
由代码审计得:
generateSessionToken()函数用于生成一个随机的token,并将其存储在会话(session)中,确保每个用户都有一个唯一的token。
checkToken()函数用于验证传递给服务器的token是否与存储在会话中的token匹配。这个函数在处理密码更改请求之前被调用,以确保只有合法的请求被处理。
通过这两个步骤,服务器会先验证token的有效性,只有在验证成功后才会处理用户的密码更改请求。
所以在发起请求之前应获取服务器返回的user_token,再利用user_token绕过验证。
method 1
在Burp中安装CSRF Token Tracker
添加主机名、抓包得到的token名即token值
再重新抓包,重放包,任意修改密码均成功,插件里的token值会自动更新。
method 2
利用 DVWA 中的 xss(stored)
,实现token的弹出。
在 xss(stored) 页面中输入1,1并抓包
修改textName参数为<iframe src="../csrf/" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)>
此时放包并关闭拦截,页面弹出token:80edcbac43cd9594f29999a0692a608f3
接着抓CSRF页面的包:
修改密码及token,放包:
由页面回显可知,成功修改密码。
Impossible level
源代码
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$pass_curr = $_GET[ 'password_current' ];
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Sanitise current password input
$pass_curr = stripslashes( $pass_curr );
$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)) ? "" : ""));
$pass_curr = md5( $pass_curr );
// Check that the current password is correct
$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 );
$data->execute();
// Do both new passwords match and does the current password match the user?
if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
// It does!
$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 );
// Update database with new password
$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();
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match or current password incorrect.</pre>";
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
代码审计
- 首先,代码通过检查
$_GET['Change']
是否存在来判断是否有密码更改请求。 - 接下来,代码调用了
checkToken
函数来验证Anti-CSRF令牌。checkToken
函数会比较请求中的user_token
和会话中的session_token
是否匹配,以确认请求的合法性。 - 代码获取了输入的当前密码、新密码和确认密码,并对当前密码进行了一系列处理:
stripslashes
函数用于去除当前密码中的反斜杠。mysqli_real_escape_string
函数用于对当前密码进行数据库转义,防止SQL注入攻击。md5
函数用于将当前密码进行MD5哈希加密。
- 代码执行了数据库查询,检查用户输入的当前密码是否正确。
- 如果新密码和确认密码相匹配,并且当前密码正确,则更新数据库中的密码为新密码。
- 最后,根据操作结果输出相应的反馈信息。
此外,在代码中还调用了两个额外的函数:
generateSessionToken
函数用于生成并设置Anti-CSRF令牌,以确保每次渲染表单时都会生成一个新的令牌,并将其存储在会话中。dvwaCurrentUser
函数返回当前用户的用户名。
总结
以上为 [网络安全] DVWA之CSRF攻击姿势及解题详析合集,考察CSRF
、Burp使用
及PHP代码审计
等相关知识。
我是秋说,我们下次见。