ajax、跨域與安全機制

ajax、安全機制、跨域.png

1、ajax

能夠在不從新加載整個網頁的狀況下,對網頁的某部分進行更新。
ajax-yl.pngphp

XHR建立對象:

XMLHttpRequest 對象用於和服務器交換數據。(IE六、IE5不支持)css

let xhr = new XMLHttpRequest();

XHR請求

向服務器發送請求:html

  • open(method,url,async)

功能:規定請求的類型、URL 以及是否異步處理請求。
參數前端

  • method :請求的類型,GET 或 POST。
  • url:文件在服務器上的位置,該文件能夠是任何類型的文件。
  • async:true(異步)或 false(同步)
  • send(string)

功能:將請求發送到服務器。
參數git

  • string :僅用於 POST 請求。

GET請求

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

POST請求

簡單請求:github

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

POST 提交表單:
使用 setRequestHeader() 來添加 HTTP 頭。而後在 send() 方法中規定但願發送的數據。ajax

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 屬性改變時,就會調用該函數。
參數express

  • 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-二、2-三、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);
});

2、沒有同源策略限制的兩大危險場景:

同源策略:是一種安全機制。同源策略限制從一個源加載的資源如何與來自另外一個源的資源進行交互。
同源:協議、主機、端口號都相同。
跨域:三者有一個不一樣則是跨域。
非同源下:

  • 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 驗證的步驟:
一、服務器將 Token 返回到前端
用戶打開頁面時,前端發起請求,服務器會返回一個 Token,該 Token 經過加密算法對數據進行加密,通常 Token 都包括隨機字符串和時間戳的組合,同時 Token 會存在服務器的 Session 中。以後頁面加載完成時,使用 JS 遍歷整個 DOM 樹,在 DOM 中全部地址是本站的aform標籤中加入 Token,其餘的請求就在編碼時手動添加 Token 這個參數。
二、前端發請求時攜帶這個 Token
對於 GET 請求,Token 將附在請求地址以後,這樣 URL 就變成http://url?token=tokenvalue。 而對於form標籤發起的 POST 請求來講,要在form的最後加上:

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

總之,就是前端發請求時把 Token 以參數的形式加入請求中。
三、服務器驗證 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。

3、跨域的方式

哪些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

相關文章
相關標籤/搜索