<bdo id="2b3yk"><code id="2b3yk"></code></bdo>

  • <output id="2b3yk"><sup id="2b3yk"></sup></output>
    <output id="2b3yk"><ruby id="2b3yk"></ruby></output>
  • <code id="2b3yk"><delect id="2b3yk"></delect></code>

  • HTTP的同源策略與跨域資源共享(CORS)機制

    2019-02-27 46893人圍觀 ,發現 2 個不明物體 WEB安全

    *本文作者:x565178035,本文屬 FreeBuf 原創獎勵計劃,未經許可禁止轉載。

    同源策略

    準確的說,同源策略是指,瀏覽器內部在發起如下請求時,該來源必須是當前同源的HTTP資源:

    1. 以跨站點的方式調用XMLHttpRequest或者Fetch API。

    2. Web字體(用于CSS中@ font-face的跨域字體使用)

    3. WebGL textures

    4. 使用drawImage繪制到canvas的圖像/視頻幀。

    5. 樣式表(用于CSSOM訪問)

    注意:兩個URI同源當且僅當它們的協議://host:port相同。

    從第一點可以看到,瀏覽器限制從腳本內部發起跨域的HTTP請求——更準確的說,同源策略有的限制有兩種表現:(1)限制發起AJAX請求(XMLHttpRequest,Fetch);(2)攔截其他跨站請求的返回結果;這取決于請求是否為簡單請求。

    CORS

    跨域資源共享(Cross-Origin Resource Sharing, CORS)是一種解決跨域請求的方案,其機制是使用一組額外響應頭(Access-Control-Allow-Origin)和預檢請求(OPTIONS)來使瀏覽器有權使用非同源資源。大部分的現代瀏覽器符合該標準。

    簡單請求

    若請求滿足所有下述條件,則該請求可視為“簡單請求”:

    使用下列方法之一:

    GET

    HEAD

    POST

    并且Content-Type的值僅限于下列三者之一:

    text/plain

    multipart/form-data

    application/x-www-form-urlencoded

    Fetch 規范定義了對 CORS 安全的首部字段集合,也就是說,不得手動設置除以下集合之外的字段(否則不為簡單請求)。該集合為:

    Accept

    Accept-Language

    Content-Language

    Content-Type

    DPR

    Downlink

    Save-Data

    Viewport-Width

    Width

    并且請求中的任意XMLHttpRequestUpload 對象均沒有注冊任何事件監聽器;XMLHttpRequestUpload 對象可以使用 XMLHttpRequest.upload 屬性訪問。

    并且請求中沒有使用 ReadableStream 對象。

    簡單請求會直接發送請求而不會觸發預請求,但是不一定能拿到結果,這取決于請求的服務器Response的Access-Control-Allow-Origin內容。注意以上條件只要有一條不滿足則不為簡單請求。

    簡單請求跨域表現

    發起請求服務http://127.0.0.1:8000/ajax.html:

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>AJAX</title>
    </head>
    <script>
    function submitRequest() {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", "http://127.0.0.1:8888/get", true);
        xhr.withCredentials = true;
        xhr.send();
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4 && xhr.status === 200){
                alert(xhr.responseText);
            }
        }
    }
    </script>
    <button onclick="submitRequest()">AJAX</button>
    </html>

    非同源服務http://127.0.0.1:8888/:

    from flask import Flask, request, render_template_string, session
    
    app = Flask(__name__)
    app.secret_key='random_secret_key'
    
    @app.route('/get', methods=['GET'])
    def get():
        if session.get('user','')=='admin':
            return "Admin do something!"
        else:
            return "No Privilege..."
    
    @app.route('/login', methods=['GET'])
    def login():
        user=request.args.get("user", "Null")
        session["user"]=user
        template="""
        <h3> Login as {{ user }}... </h3>
        """
        return render_template_string(template, user=user)
    
    if __name__ == '__main__':
        app.run(host='127.0.0.1', port=8888, debug=True)

    發請求,可以看到請求確實已發送,并且可以帶cookie(withCredentials),但是js沒有拿到結果:

    AJAX請求結果(請求成功,回傳失敗,所以這也是GET型CSRF無法很好防范的原因):

    綜上,對于簡單跨域請求,若未正確配置則請求正常發送,不能獲取返回結果(瀏覽器攔截)。

    Origin和Access-Control-Allow-Origin

    可以看到在請求中存在Origin字段,它標記了來源,對應的Access-Control-Allow-Origin為回應包頭攜帶字段,它表示那些來源可以訪問本域,*表示所有來源(注意它不能與credentials一起使用)。

    使用CORS實現的支持跨域的非同源服務http://127.0.0.1:8888/:

    @app.route('/get', methods=['GET'])
    def get():
        if session.get('user','')=='admin':
            ret = "Admin do something!"
        else:
            ret = "No Privilege..."
        resp=make_response(ret)
        resp.headers['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000"
        resp.headers['Access-Control-Allow-Credentials'] = 'true'
        resp.headers['Access-Control-Allow-Methods'] = "POST, GET, OPTIONS, PUT, DELETE, PATCH"
        return resp

    其中還有幾個header:

    Access-Control-Allow-Credentials:如果請求需要帶cookie,該header必須為true,同時Access-Control-Allow-Origin不能為*,否則同樣拿不到結果;

    Access-Control-Allow-Methods:允許的請求方式

    Origin和Access-Control-Allow-Origin一個為請求攜帶的字段,一個為回應攜帶的字段,瀏覽器以此來判斷js是否可以接收回應。

    改造后前端終于能夠拿到結果:

    預檢請求

    若請求不為簡單請求,那么在發起該請求前必須使用OPTIONS發送預驗請求,服務器允許后才能發送實際請求(可以猜想這是為了防止CSRF)。

    當請求滿足一下任一條件時,該請求為非簡單請求:

    使用了下面任一 HTTP 方法:

    PUT

    DELETE

    CONNECT

    OPTIONS

    TRACE

    PATCH

    人為設置了對 CORS 安全的首部字段集合 之外的其他首部字段。

    Content-Type的值不屬于下列之一:

    application/x-www-form-urlencoded

    multipart/form-data

    text/plain

    請求中的XMLHttpRequestUpload 對象注冊了任意多個事件監聽器。

    請求中使用了ReadableStream對象。

    預檢請求跨域表現

    假設有服務器http://127.0.0.1:8888/json:

    @app.route('/json', methods=['GET','POST'])
    def json():
        if request.method == 'GET':
            return render_template('json.html', Evil="Benign")
        else:
            if session.get('user','')=='admin':
                print("session:",session)
                data=request.json
                ret='Admin do '+data["action"]
            else:
                ret="No Privilege2..."
            print(ret)
            return jsonify({'result': ret})

    ‘templates/json.html’內容為:

    <html>
    <title>{{ Evil }}</title>
    <center>
    <h1> Reset Password </h1>
    <head>
    <script type="text/javascript">
    function submitRequest() {
        var xhr = new XMLHttpRequest();
        xhr.open("POST", "http://127.0.0.1:8888/json", true);
        xhr.setRequestHeader("Accept", "*/*");
        xhr.setRequestHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");
        xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        xhr.withCredentials = true;
        xhr.send(JSON.stringify({"action":"change passwd..."}));
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4 && xhr.status === 200){
                alert(xhr.responseText);
            }
        }
    }
    </script>
    </head>
    <body>
    <button onclick="submitRequest()">Conform</button>
    </body>
    </html>

    同域不存在預檢請求:

    跨域出現OPTIONS請求,默認情況下跨域被阻止:

    Access-Control-Request-Method:字段說明請求的操作。

    允許跨域請求

    在OPTIONS和POST報頭加入Access-Control-Allow-Origin等字段

    @app.route('/json', methods=['GET','POST','OPTIONS'])
    def json():
        if request.method == 'GET':
            return render_template('json.html', Evil="Benign")
        else:
            if session.get('user','')=='admin':
                print("session:",session)
                data=request.json
                ret='Admin do '+data["action"]
            else:
                ret="No Privilege2..."
            resp=make_response(jsonify({'result': ret}))
            resp.headers['Access-Control-Allow-Origin'] = "http://127.0.0.1:8000"
            resp.headers['Access-Control-Allow-Credentials'] = 'true'
            resp.headers['Access-Control-Allow-Methods'] = "POST, GET, OPTIONS, PUT, DELETE, PATCH"
            resp.headers['Access-Control-Allow-Headers'] = "origin, content-type, accept, x-requested-with"
            return resp

    跨站成功,先發送OPTIONS,再發送POST,注意這兩個報頭必須都存在CORS字段。

    與CORS有關的HTTP頭

    請求

    Origin:<origin>:表示實際請求的源站

    Access-Control-Request-Method: <method>:用于預檢請求,表示真實的請求方法。

    Access-Control-Request-Headers: <field-name>[, <field-name>]*:用于預檢請求,表示真實請求所攜帶的首部字段(從抓包上來看chrome沒有按要求來啊Orz)

    響應

    Access-Control-Allow-Origin: <origin> | *:允許外域URI

    Access-Control-Allow-Credentials:false:是否允許瀏覽器讀取response內容(如cookie)

    Access-Control-Allow-Methods:用于預檢請求響應,表示允許使用的HTTP方法

    Access-Control-Allow-Headers:用于預檢請求響應,表示允許攜帶的頭部

    Access-Control-Expose-Headers:允許響應時能獲取的其他頭部(在跨域訪問時,XMLHttpRequest對象的getResponseHeader()方法只能拿到一些最基本的響應頭)

    Access-Control-Max-Age:preflight請求的最大響應時間

    參考鏈接

    Cross-Origin Resource Sharing(CORS)詳解,CORS詳解,CORS原理分析,https://www.cnblogs.com/demingblog/p/8393511.html

    HTTP訪問控制(CORS),https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

    *本文作者:x565178035,本文屬 FreeBuf 原創獎勵計劃,未經許可禁止轉載。

    發表評論

    已有 2 條評論

    取消
    Loading...
    css.php 宁夏卫视在线直播观看