ajax、安全机制、跨域.png

一、ajax

可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
ajax-yl.png

XHR创建对象:

XMLHttpRequest 对象用于和服务器交换数据。(IE6、IE5不支持)

let xhr = new XMLHttpRequest();

XHR请求

向服务器发送请求:

  • open(method,url,async)

功能:规定请求的类型、URL 以及是否异步处理请求。
参数

  • method :请求的类型,GET 或 POST。
  • url:文件在服务器上的位置,该文件可以是任何类型的文件。
  • async:true(异步)或 false(同步)
  • send(string)

功能:将请求发送到服务器。
参数

  • string :仅用于 POST 请求。

GET请求

xhr.open("GET","/try/ajax/demo_get.php",true);
xhr.send();

POST请求

简单请求:

xhr.open("POST","/try/ajax/demo_post.php",true);
xhr.send();

POST 提交表单:
使用 setRequestHeader() 来添加 HTTP 头。然后在 send() 方法中规定希望发送的数据。

xhr.open("POST","/try/ajax/demo_post2.php",true);
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xhr.send("fname=Henry&lname=Ford");
  • setRequestHeader(header,value)

功能:向请求添加 HTTP 头。
参数

  • header:规定头的名称。
  • value:规定头的值。

XHR响应

如需获得来自服务器的响应,使用 XMLHttpRequest 对象的 responseText 或 responseXML 属性。

属性 描述 备注
responseText 获得字符串形式的响应数据。 如果来自服务器的响应并非XML,请使用responseText属性。
responseXML 获得XML形式的响应数据。 如果来自服务器的响应是 XML且需要作为XML对象进行解析,请使用 responseXML 属性。
xhr.responseText
xhr.responseXML

XHR属性

  • onreadystatechange

描述:存储函数(或函数名),每当 readyState 属性改变时,就会调用该函数。
参数

  • header:规定头的名称。
  • value:规定头的值。
  • readyState

描述:存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。
状态

  • 0: 请求未初始化,还没有调用send()方法
  • 1: 服务器连接已建立,已调用send()方法
  • 2: 请求已接收,send()方法执行完成
  • 3: 请求处理中,正在解析响应内容
  • 4: 请求已完成,且响应已就绪
  • status

状态

  • 1xx: 表示通知消息。
  • 2xx: 表示成功。
  • 200: 请求成功,已经正常处理完毕
  • 3xx: 表示重定向。

    • 301:永久性重定向。
    • 302:临时性重定向。
    • 304:请求被重定向到客户端本地缓存。
  • 4xx: 表示客户端差错。

    • 400:客户端请求存在语法错误。
    • 401:客户端请求没有经过授权。
    • 403:客户端的请求被服务器拒绝。
    • 404:客户端请求的URL在服务器端不存在。
  • 5xx 表示服务器差错

    • 500:服务端永久错误。
  • 当请求被发送到服务器时,我们需要执行一些基于响应的任务。
  • 每当 readyState 改变时,就会触发 onreadystatechange 事件。在 onreadystatechange 事件中,我们规定当服务器响应已做好被处理的准备时所执行的任务。
  • 当 readyState 等于 4 且状态为 200 时,表示响应已就绪。
xhr.onreadystatechange=function(){
    if (xmlhttp.readyState==4 && xmlhttp.status==200){
        document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
    }
}

注意: onreadystatechange 事件被触发 4 次(0 - 4), 分别是: 0-1、1-2、2-3、3-4,对应着 readyState 的每个变化。

手写ajax

callback()形式:

function ajax(url, callback) {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            callback(xhr.responseText);
        }
    };
    xhr.send();
}

let url = "http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1";
ajax(url, function (data) {
    console.log(JSON.parse(data));
});

promise形式

function ajax(url) {
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open("GET", url, true);
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {
                resolve(xhr.responseText);
            }
        };
        xhr.send();
    });
}

let url = "http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1";
ajax(url).then((res) => {
    console.log(JSON.parse(res));
}).catch((err) => {
    console.log(err);
});

二、没有同源策略限制的两大危险场景:

同源策略:是一种安全机制。同源策略限制从一个源加载的资源如何与来自另一个源的资源进行交互。
同源:协议、主机、端口号都相同。
跨域:三者有一个不同则是跨域。
非同源下:

  • CookieLocalStorageIndexDB无法读取
  • DOM无法获得
  • AJAX请求不能发送

跨域和跨站
同源策略作为浏览器的安全基石,其「同源」判断是比较严格的,相对而言,Cookie中的「同站」判断就比较宽松:只要两个 URL 的 eTLD+1 相同即可,不需要考虑协议和端口。其中,eTLD 表示有效顶级域名,注册于 Mozilla 维护的公共后缀列表(Public Suffix List)中,例如,.com、.co.uk、.github.io 等。eTLD+1 则表示,有效顶级域名+二级域名,例如 taobao.com 等。

举几个例子,www.taobao.com 和www.baidu.com是跨站,www.a.taobao.com 和www.b.taobao.com是同站,a.github.io 和 b.github.io 是跨站(注意是跨站)。

1. 没有同源策略限制的接口请求

CSRF攻击—跨站请求伪造

CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。

造成的问题:个人隐私泄露以及财产安全。包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账

CSRF攻击原理:

CSRF攻击.png
CSRF攻击是源于WEB的隐式身份验证机制!WEB的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的!

CSRF攻击原理

如果用户是登录状态,打开了如下这样的页面:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <title>csrf攻击</title>
    <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" name="viewport" />
</head>

<body style="display: none;">
    <form target="myIframe" id="csrf" action="https://www.kkkk1000.com/csrf/data/post_comment.php" metdod="POST">
        <input type="text" name="content" value="csrf攻击" />
    </form>

    <!-- iframe 用来防止页面跳转 -->
    <iframe id="myIframe" name="myIframe"></iframe>

    <script>
        var form = document.querySelector('#csrf');
        form.submit();
    </script>
</body>

</html>

就会自动在文章下发一条评论,这样就算完成了一次 CSRF 攻击。
当然,如果你把这个页面放服务器上,然后做成一个链接,用户点击这个链接,同样可以完成攻击。

CSRF特点
  • 攻击一般发起在第三方网站,而不是被攻击的网站。
  • 攻击是利用受害者在被攻击网站的登录凭证,冒充受害者提交操作,仅仅是“冒用”,而不是直接窃取数据。
  • 攻击者预测出被攻击的网站接口的所有参数,成功伪造请求。
CSRF防御措施:

1.SameSite属性
Cookie 的 SameSite 属性用来限制第三方 Cookie,从而减少安全风险,可以用来防止 CSRF 攻击和用户追踪。

2.同源检测
在 HTTP 协议中,每一个异步请求都会携带两个 Header ,用于标记来源域名:Origin Header,Referer Header。这两个 Header 在浏览器发起请求时,大多数情况会自动带上,并且不能由前端自定义内容。 服务器可以通过解析这两个 Header 中的域名,确定请求的来源域。

3.验证码
而验证码会强制用户必须与应用进行交互,才能完成最终请求,而且因为 CSRF 攻击无法获取到验证码,因此通常情况下,验证码能够很好地遏制 CSRF 攻击。

4.Token 验证:
在 HTTP 请求中以参数的形式加入一个随机产生的 Token,并在服务器端建立一个拦截器来验证这个 Token,如果请求中没有 Token 或者 Token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。

添加 Token 验证的步骤:
1、服务器将 Token 返回到前端
用户打开页面时,前端发起请求,服务器会返回一个 Token,该 Token 通过加密算法对数据进行加密,一般 Token 都包括随机字符串和时间戳的组合,同时 Token 会存在服务器的 Session 中。之后页面加载完成时,使用 JS 遍历整个 DOM 树,在 DOM 中所有地址是本站的aform标签中加入 Token,其他的请求就在编码时手动添加 Token 这个参数。
2、前端发请求时携带这个 Token
对于 GET 请求,Token 将附在请求地址之后,这样 URL 就变成http://url?token=tokenvalue。 而对于form标签发起的 POST 请求来说,要在form的最后加上:

<input type=”hidden” name=”token” value=”tokenvalue”/>

总之,就是前端发请求时把 Token 以参数的形式加入请求中。
3、服务器验证 Token 是否正确
当前端得到了 Token ,再次提交给服务器的时候,服务器需要判断 Token 的有效性,验证过程是先解密 Token,对比加密字符串以及时间戳,如果加密字符串一致且时间未过期,那么这个 Token 就是有效的。

xss—跨站脚本攻击

攻击者在网页中嵌入客户端脚本(例如JavaScript), 当用户浏览此网页时,脚本就会在用户的浏览器上执行,从而达到攻击者的目的.  比如获取用户的Cookie,导航到恶意网站,携带木马等。

XSS攻击方式:
  • 反射型

发出请求时,XSS代码出现在URL中,作为输入提交到服务器端,服务器端解析后响应,XSS代码随响应内容一起传回给浏览器,最后浏览器解析执行XSS代码。这个过程像一次反射,故叫反射型XSS。

演示:
index.ejs

<!DOCTYPE html>
<html>
<head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css'/>
</head>
<body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
    <div class="">  
        <%- xss %>
    </div>
</body>
</html>

index.js

var express = require('express');
var router = express.Router();

router.get('/', function (req, res, next) {
    res.set("X-XSS-Protection", 0); //允许攻击
    res.render('index', {title: 'Express', xss: req.query.xss});
});

module.exports = router;

请求

127.0.0.1:3000/?xss=<p%20onclick="alert(%27点我%27))">点我</p>
  • 存储型

存储型XSS和反射型XSS的差别仅在于,提交的代码会存储在服务器端(数据库,内存,文件系统等),下次请求目标页面时不用再提交XSS代码。

XSS防御措施:
  • 编码 :对用户输入的数据进行HTML Entity(字符实体)编码
  • 过滤:移除用户上传的DOM属性,如onerror等;移除用户上传的Style节点、Script节点、Iframe节点等。
  • 校正:避免直接对HTML Entity解码;使用DOM Parse转换,校正不配对的DOM标签。

XSS 一般利用js脚本读取用户浏览器中的cookie,而如果在服务器端对某个cookie设置了 httpOnly属性,则无法通过 JS 脚本 读取到该cookie的信息,但还是能通过Application中手动修改cookie,所以只是在一定程度上可以防止XSS攻击,不是绝对的安全。

2. 没有同源策略限制的Dom查询!

由于没有同源策略的限制,钓鱼网站可以直接拿到别的网站的Dom。

三、跨域的方式

哪些html标签能绕过跨域?
通过hrefsrc请求的资源不存在跨域问题,

<img src=跨域的图片地址/> //可用于统计打点,可使用第三方统计服务
<link href=跨域的css地址/>
<script src=跨域的js地址> </script>

//<link /><script>可使用CDN CDN一般都是外域
//<script>可实现JSONP

所有的跨域,都必须经过server端允许和配合,未经server端允许就实现跨域,说明浏览器有漏洞。

同源策略限制下接口请求的正确打开方式

ajax请求不能跨域,所以引入jsonp 和 cors

JSONP

JSONP的原理 :利用<script>标签中 src属性可以跨域的特性。
JSONP只支持GET请求,因为本质上<script>加载资源就是GET。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

具体实现:

  1. 本地写一个回调函数。
  2. 在远程执行这个回调函数,再将把远程的数据传到本地。远程服务器必须支持jsonp

示例:
jsonp.html

<body>
<script>
  function foo(data) {
    alert(data);
  }
</script>
<script src="http://localhost:8001/info?callback=foo"></script>
</body>

serve.js

app.get("/info", async (req, res) => {
  let funcName = req.query.callback;
  res.send(`${funcName}('你好')`);
  //foo('你好');
});

CORS

CORS是一个W3C标准,全称是"跨域资源共享"。CORS有两种请求,简单请求和非简单请求。

简单请求:

只要同时满足以下两大条件,就属于简单请求。
(1) 请求方法是以下三种方法之一:HEADGETPOST
(2) HTTP的头信息不超出以下几种字段:AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type
Content-Type 只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

原理:
浏览器若发现这次跨源ajax请求是简单请求,就自动在头信息之中,添加一个Origin字段,用来说明,本次请求来自哪个(协议 + 域名 + 端口)(前端实际上什么也不用干)。服务器根据这个值,决定是否同意这次请求。

  • 如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被 XMLHttpRequestonerror 回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
  • 如果Origin指定的源,在许可范围内,服务器返回的响应,会多出几个头信息字段。
app.get("/",function(req,res){
//设置服务器端返回的响应的头字段 
    res.header("Access-Control-Allow-Origin","*");
    res.send("你好");
})

Access-Control-Allow-Origin

该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

请求头 origin 对应响应头 Access-Control-Allow-Origin

复杂请求

在发送真正的请求前会提前发送一次options请求(嗅探、预检请求),返回码是204,预检测通过才会真正发出请求,这才返回200。如果options获得的回应是拒绝性质的(或者没有权限),会停止发送实际请求信息。这里通过前端发请求的时候增加一个额外的headers来触发非简单请求。XHR会根据返回的Access-Control-*等头信息判断是否有对指定站点的访问权限,检查该请求是否是可靠安全的。

"预检"请求的头信息

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

(1) Origin: 用来说明,本次请求来自哪个源
(2) Access-Control-Request-Method: 该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT
(3)Access-Control-Request-Headers: 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header

"预检"请求的响应

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

(1) Access-Control-Allow-Origin: 表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。与响应头 origin 对应。
(2) Access-Control-Allow-Methods: 表明服务器支持的所有跨域请求的方法。与响应头Access-Control-Request-Method对应。
(3)Access-Control-Allow-Headers: 表明服务器支持的所有头信息字段。与响应头Access-Control-Request-Headers对应。

一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

示例:

前端
fetch(`http://localhost:9871/api/cors?msg=helloCors`, {
  // 需要带上cookie
  credentials: 'include',
  // 这里添加额外的headers来触发非简单请求
  headers: {
    't': 'extra headers'
  }
}).then(res => {
  console.log(res)
})
后台
const cors= require('koa2-cors);
//配置 jsonp 的中间件
app.use(cors());

参考文章:https://segmentfault.com/a/11...
参考文章:http://www.ruanyifeng.com/blo...
*参考文章:跨站请求伪造—CSRF


梁柱
135 声望12 粉丝