Browser Thematic series – Cross domain and cross site

The same-origin policy

Browsers have the same origin policy. Two urls are the same protocol, domain name, and port

One of the inconsistencies is across domains

The sample

In the current page, for example: https://sugarat.top/bigWeb/browser/cros.html

  • Protocol: HTTPS
  • Domain name: sugarat. Top
  • Port: 443 (HTTPS defaults to 443, HTTP defaults to 80)
URL Whether the same reason
sugarat.top The protocol, domain name, and port are the same
sugarat.top The protocol and port are inconsistent
sugarat.top:8080 Port inconsistency
ep.sugarat.top Inconsistent domain names
imgbed.sugarat.top Inconsistent domain names

Same-origin policy restriction

  1. DOM: Disables manipulation of DOM and JS objects on non-source pages
    • The main scenario here is iframes across domains. Non-homogeneous IFrames are restricted from accessing each other
  2. XmlHttpRequest: Disables the use of XHR objects to make HTTP requests to server addresses of different sources; that is, cross-domain Ajax requests cannot be sent
    • Mainly used to prevent CSRF (Cross-site request forgery) attacks
  3. LocalStorage: cookies, LocalStorage, and IndexDB cannot be read across domains

There are also non-homologous schemes that can communicate, which will be described later

Why the same-origin policy 🤔

Here’s a counterexample

Example 1: If iframe can cross domains, the following attack scenario will occur

  1. A fake websitehttps://a.com, internally nested with a full-screen IFrame tag pointing to a bank websitehttps://b.com
  2. Except for the domain name, users visited the fake site no different from the bank’s site
  3. Developers can inject input event listening scripts into fake websites for cross-domain accesshttps://b.comContents of nodes
  4. The user’s input can be listened to, allowing the fake site to obtain the user’s account and password

This attack is complete

Example 2: If Ajax can cross domains, you have the following attack scenarios

  1. The user is on the bank’s websitehttps://b.comAfter login, the website uses cookie authentication
  2. Attackers directly from the sitehttps://a.comMakes an attack request to the bank’s web site. This cross-domain request carries a cookie from the target site
  3. The bank server verifies the user’s cookie is correct and returns the corresponding response data
  4. At this point, user information leakage is caused, and users cannot perceive it

This attack is complete

The same origin policy is used to restrict cross-domain access mainly for the security of user information

Cross domain

Violating the same origin policy is cross-domain

impact

The two most common cross-domain scenarios are

  • Ajax cross-domain
  • The iframe cross-domain

Cross-domain sample

Ajax cross-domain

Executing the following code will see the following error message in the Console panel of the developer tools

Access to fetch at 'https://ep.sugarat.top/' from origin 'http://127.0.0.1:5500' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource. 
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Copy the code

<body>
    <button id="btn">click me</button>
    <script>
        const $btn = document.getElementById('btn')
        $btn.onclick = function () {
            fetch('https://ep.sugarat.top/', {
                method: 'get'})}</script>
</body>
Copy the code

The iframe cross-domain

DOM element information in iframe cannot be accessed across domains

<body>
    <iframe src="https://sugarat.top/" width="100%" height="1000px" frameborder="0"></iframe>
        
    <script>
        const iframe = document.getElementsByTagName('iframe')[0]
        console.log(iframe.contentWindow.document.children[0].outerHTML)
        // <html><head></head><body></body></html>
    </script>
</body>
Copy the code

cross-site

Cookies have a lot to do with this. Cookies actually follow the “same site” policy

What is co-station

As long as the eTLD+1 of two urls is the same, it is the same site, regardless of the protocol and port

ETLD: effective top-level domain (eTLD), which is registered in the Public Suffix List maintained by Mozilla, for example,.com,.co.uk,.github. IO, and.top

ETLD + 1: effective top-level domain + secondary domain name, such as taobao.com, baidu.com, sugarat. Top

Tips: The level 1, level 2 domain name here mainly refers to the computer network regulation, and the common business development referred to the level 1 and level 2 domain name is slightly different

In the current page, for example: https://sugarat.top/bigWeb/browser/cros.html

  • eTLD: .top
  • eTLD+1: sugarat.top
URL Whether the same station reason
sugarat.top ETLD + 1
ep.sugarat.top ETLD + 1
ep.sugarat.top:8080 ETLD + 1
baidu.com ETLD inconsistent

Any difference in eTLD+1 is cross-site

Impact on cookies

Because cookies follow the same site policy, many websites put some permissions, user behavior, subject, personal configuration information

So many sites will store this information under the secondary domain name, that is, the sub-domain name can share the configuration, authentication information

USES the example of taobao

Open taobao.com, you can see that its cookie has

We can also see these cookies under ai.Taobao

Preview the request

With the cross-domain solution of back-end enabled CORS, browsers divide requests into two types

  • A simple request
  • Complex request

A simple request

Condition that triggers a simple request ↓

1. Request methods are limited to:

  • GET
  • HEAD
  • POST

2. Content-type is limited to:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

Complex request

A ↓ request that is not simple is a complex request

For complex requests, a precheck request, called Options, is first issued to determine whether the server allows cross-domain

Access-control-response headers associated with precheck requests:

  • Access-control-allow-methods: Indicates all cross-domain requests supported by the server
  • Access-control-allow-headers: specifies the Headers supported by the server
  • Access-control-max-age: specifies the validity period of the precheck request, in seconds. During this period, no new precheck request is reissued

Solutions across domains

Tips: Use HTTP-Server for running front-end pages

jsonp

The principle of

Exploit the

The SRC attribute of the

The content captured by script is executed as a JS script

So we need the server to do a string concatenation on the callback callbackFunName

You can pass the required parameters through the URL

If need to send a get request to http://sugarat.top/path1/path2? param1=1

  1. The client registers a global methodfunction callbackFunName(res){}
  2. After receiving the request, the server retrieves the parameters of the URL
  3. The server returns a stringcallbackFunName({"name":"sugar","age":18})
  4. The client is directly parsed and executed as a JS script
  5. The method is calledcallbackFunNameAnd put the inside{"name":"sugar","age":18}Passed as an object

Only GET requests are supported

Simple Usage Example

Server code

// Take Node.js as an example
const http = require('http')
const app = http.createServer((req, res) = > {
    const jsonData = {
        name: 'sugar'.age: 18
    }
    res.end(`diyCallBackFun(The ${JSON.stringify(jsonData)}) `)
})
app.listen(3000)
Copy the code

Client code

<script>
    // Jsonp callback function
    function diyCallBackFun(data) {
    	console.log(data)
	}
</script>

<script>
let $srcipt = document.createElement('script')
$srcipt.src = 'http://localhost:3000/path1/path2? param1=1&param2=2'
document.body.appendChild($srcipt)
</script>
<! -- The final tag constructed -->
<! -- <script src="localhost:3000/path1/path2? param1=1&param2=2"></script> -->
Copy the code

Page to insert the above code and run to see the console output

Universal method encapsulation

/** * JSONP method *@param {string} Url request path *@param {string} CallbackName Global function name (method name for back-end concatenation) *@param {function} The callback function */ for the SUCCESS response
function jsonp(url, callbackName, success) {
   const $script = document.createElement('script')
   $script.src = url + `&callback=${callbackName}`
   $script.async = true
   $script.type = 'text/javascript'
   window[callbackName] = function (data) {
       success && success(data)
   }
   document.body.appendChild($script)
}
Copy the code

CORS

Cross-origin Resource Sharing

Allows browsers to send Ajax requests to cross-domain servers

The key to CORS communication is the server. As long as the server implements the CORS interface, cross-source communication is possible

The server can enable CORS by setting access-Control-Allow-Origin in the response header

The principle of

If the cross-domain AJAX request is a simple one, the browser automatically adds an Origin field to the header information to indicate which source the request came from

Such as: origin: http://localhost:8080

If the contents of Origin are not included in the request’s response header access-Control-Allow-Origin, the following error is thrown

Access-control-related response headers:

  • Access-control-allow-origin: This field is a mandatory field in CORS, and its value is the Origin field value at the time of the request.Split multiple domains, or*“Indicates that all requests are allowed
  • Access-control-expose-headers: Lists which Headers can be exposed externally as part of the response (XMLHttpRequest)
    • By default, only seven simple Response headers can be exposed to the outside world:
      • Cache-control: controls the Cache
      • Content-language: Indicates the Language group of the resource
      • Content-length: indicates the Length of the resource
      • Content-type: indicates the supported media Type
      • Expires: indicates the resource expiration time
      • Last-modified: indicates the time when the resource was Last Modified
      • Pragma: message instruction
  • Access-control-allow-credentials: The value type is Boolean, indicating whether cookies are allowed in cross-domain requests
    • CORS requests do not carry cookies by default
    • You also need to set the withCredentials attribute of the XHR (XMLHttpRequest) object to true

A simple example

Take Node.js as an example

const http = require('http')

let server = http.createServer(async (req, res) => {
    // ------- cross-domain support -----------
    // Allow the specified domain name
    res.setHeader('Access-Control-Allow-Origin'.The '*')
    // Header types allowed across domains
    res.setHeader("Access-Control-Allow-Headers"."*")
    // Allow cookies to be carried across domains
    res.setHeader("Access-Control-Allow-Credentials"."true")
    // The allowed method
    res.setHeader('Access-Control-Allow-Methods'.'PUT, GET, POST, DELETE, OPTIONS')

    let { method, url } = req
    // The precheck request is cleared
    if (method === 'OPTIONS') {
        return res.end()
    }
    console.log(method, url)
    res.end('success')})/ / start
server.listen(3000.err= > {
    console.log(`listen 3000 success`);
})
Copy the code

The reverse proxy

Because cross-domain is restricted to browsers

Back-end services are not affected

You can use Nginx,Node Server, Apache, and other technical solutions to do a forward request

Here are some examples

Nginx configuration

server {
    listen 80;
	listen 443 ssl http2;
    server_name test.sugarat.top;
    index index.php index.html index.htm default.php default.htm default.html;
    root /xxx/aaa;
    # omit other configurations
    location /api {
        proxy_pass http://a.b.com;
        # prevent cachingadd_header Cache-Control no-cache; }}Copy the code

Visit http://test.sugarat.top/api/user/login, actual it is nginx server access http://a.b.com/api/user/login

For more details on the proxy_pass attribute, see pit for proxy_pass URL Reverse proxy

Node Server

Node native HTTP module + AXIOS is used to forward the request

const http = require('http')
const axios = require('axios').default

// Where to forward to
const BASE_URL = 'http://www.baidu.com'
// Start the service port
const PORT = 3000

const app = http.createServer(async (req, res) => {
    const { url, method } = req
    console.log(url);
    // The precheck request is cleared
    if (method === 'OPTIONS') {
        return res.end()
    }
    // Get the parameters passed
    const reqData = await getBodyContent(req)
    console.log(reqData);
    const { data } = await axios.request({
        method,
        url,
        baseURL: BASE_URL,
        data: reqData
    })
    res.setHeader('Access-Control-Allow-Origin'.The '*')
    res.setHeader('Content-Type'.'application/json; charset=utf-8')
    res.end(JSON.stringify(data))
})

app.listen(PORT, () = > {
    console.log(`listen ${PORT} success`);
})

function getBodyContent(req) {
    return new Promise((resolve, reject) = > {
        let buffer = Buffer.alloc(0)

        req.on('data'.chunk= > {
            try {
                buffer = Buffer.concat([buffer, chunk])
            } catch (err) {
                console.error(err);                
            }
        })

        req.on('end'.() = > {
            let data = {}
            try {
                data = JSON.parse(buffer.toString('utf-8'))}catch (error) {
                data = {}
            } finally {
                resolve(data)
            }
        })
    })
}
Copy the code

The test page

<h1>test</h1>
<script>
    fetch('http://localhost:3000/sugrec? name=test').then(res= >res.json()).then(console.log)
</script>
Copy the code

The request was successfully forwarded

websocket

WebSocket Protocol is a new protocol for HTML5. It implements full duplex communication between browser and server, and allows cross-domain communication. It is a good implementation of server push technology

Use the sample

The client

<body>
    <p><span>Link Status:</span><span id="status">Disconnect the</span></p>
    <label for="content">content<input id="content" type="text">
    </label>
    <button id="send">send</button>
    <button id="close">Disconnect the</button>
    <script>
        const ws = new WebSocket('ws:localhost:3000'.'echo-protocol')
        let status = false
        const $status = document.getElementById('status')
        const $send = document.getElementById('send')
        const $close = document.getElementById('close')
        $send.onclick = function () {
            const text = document.getElementById('content').value
            console.log('Send:', text);
            ws.send(text)
        }
        ws.onopen = function (e) {
            console.log('connection open ... ');
            ws.send('Hello')
            status = true
            $status.textContent = 'Link successful'
        }
        $close.onclick = function () {
            ws.close()
        }
        ws.onmessage = function (e) {
            console.log('client received: ', e.data);
        }
        ws.onclose = function () {
            console.log('close');
            status = false
            $status.textContent = 'Disconnect'
        }
        ws.onerror = function (e) {
            console.error(e);
            status = false
            $status.textContent = 'Link error'
        }
    </script>
</body>
Copy the code

The service side

Node is used here, websocket module needs to be installed

const WebSocketServer = require('websocket').server;
const http = require('http');

const server = http.createServer(function (request, response) {
    console.log((new Date()) + ' Received request for ' + request.url);
    response.writeHead(200);
    response.end();
});
server.listen(3000.function () {
    console.log((new Date()) + ' Server is listening on port 3000');
});

const wsServer = new WebSocketServer({
    httpServer: server,
    autoAcceptConnections: false
});

function originIsAllowed(origin) {
    return true;
}

wsServer.on('request'.function (request) {
    if(! originIsAllowed(request.origin)) { request.reject();console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.');
        return;
    }

    var connection = request.accept('echo-protocol', request.origin);
    console.log((new Date()) + ' Connection accepted.');
    connection.on('message'.function (message) {
        if (message.type === 'utf8') {
            console.log('Received Message: ' + message.utf8Data);
            connection.sendUTF(`The ${new Date().toLocaleString()}:${message.utf8Data}`); }}); connection.on('close'.function (reasonCode, description) {
        console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
    });
});
Copy the code

The results

location.hash

The hash value of location changes, the page does not refresh, and the browser provides a Hashchange event

It is mainly used for iframe cross-domain communication

The sample

The parent page

<body>
    <h1>The parent page</h1>
    <button id="send">send</button>
    <iframe id="iframe1" src="http://localhost:3001/2.html"></iframe>
    <script>
        const $send = document.getElementById('send')
        const $iframe = document.getElementById('iframe1')
        const oldSrc = $iframe.src
        $send.onclick = function () {
            $iframe.src = oldSrc + The '#' + Math.random() * 100
        }
    </script>
</body>
Copy the code

Child pages

<body>
    <h1>Child pages</h1>
    <script>
        window.addEventListener('hashchange'.function(e){
            console.log(e);
            console.log(location.hash);
        })
    </script>
</body>
Copy the code

The results

window.name

As long as the current browser TAB is not closed, the name value can be maintained regardless of how the page in the TAB changes, and all pages in the TAB have access to this value. Right

The page in iframe uses the above features to read window.name of any page

Use the sample

The parent page 1. HTML

<body>
    <h1>The parent page</h1>
    <button id="send">send</button>

    <script>
        document.getElementById('send').addEventListener('click'.function () {
            getCrossIframeName('http://localhost:3000/2.html'.console.log)
        })
        function getCrossIframeName(url, callback) {
            let ok = false
            const iframe = document.createElement('iframe')
            iframe.src = url
            iframe.style.width = '0px'
            iframe.style.height = '0px'
            iframe.onload = function () {
                if (ok) {
                    // On the second trigger, the page in the same domain is loaded
                    callback(iframe.contentWindow.name)
                    / / remove
                    document.body.removeChild(iframe)
                } else {
                    // The onload event is fired for the first time and directed to the middle page of the same domain
                    // If the middle page does not exist after testing, it can also be used, if the page content is empty
                    iframe.contentWindow.location.href = '/proxy.html'ok = ! ok } }document.body.appendChild(iframe)
        }
    </script>
</body>
Copy the code

The middle page proxy.html

<! -- Empty file -->
Copy the code

Target page 2.html

<body>
    <script>
        const data = { name: 'Transmitted data'.status: 'success'.num: Math.random() * 100 }
        window.name = JSON.stringify(data)
    </script>
</body>
Copy the code

The results

window.postMessage

The window.postMessage method can safely implement cross-source communication in the following scenarios:

  • Messaging to and from other pages
  • Communicates with the embedded IFrame

usage

otherWindow.postMessage(message, targetOrigin);
Copy the code

Examples of targetOrigin values:

  • Protocol + host + port: A message is sent only when all three match
  • * : passes to any window
  • / : Window of the same origin as the current window

Use the sample

The parent page

<body>
    <h1>The parent page</h1>
    <button id="send">send</button>
    <iframe id="iframe1" src="http://localhost:3001/2.html"></iframe>
    <script>
        const $send = document.getElementById('send')
        const $iframe = document.getElementById('iframe1')
        const oldSrc = $iframe.src
        $send.onclick = function () {
            $iframe.contentWindow.postMessage(JSON.stringify({ num: Math.random() }),The '*')}</script>
</body>
Copy the code

Child pages

<body>
    <h1>Child pages</h1>
    <script>
        window.addEventListener('message'.function (e) {
            console.log('receive', e.data);
        })
    </script>
</body>
Copy the code

The results

document.domain

The same secondary domain names, such as A. sugarat.top and B. sugarat.top, apply to this method.

You can cross domains by simply adding document.domain = ‘sugarat.top’ to the page to indicate that the secondary domain names are the same

A simple example

First modify the host file and add two custom domain names to simulate the cross-domain environment

The parent page

<body>
    <h1>The parent page</h1>
    <iframe id="iframe1" src="http://b.sugarat.top:3000/2.html"></iframe>
    <script>
        document.domain = 'sugarat.top'
        var a = Awesome!
    </script>
</body>
Copy the code

Child pages

<body>
    <h1>Child pages</h1>
    <script>
        document.domain = 'sugarat.top'
        console.log('get parent data a:'.window.parent.a);
    </script>
</body>
Copy the code

The results

conclusion

The above is just an introduction to some common cross-domain solutions, with examples that can be directly copied and pasted to facilitate readers’ understanding and hands-on experience

In the actual production environment, you need to carry out the pick for specific scenarios

This is also a classic question in the interview, hope to help readers deepen their understanding

reference

  • Wangningbo – Talk about several cross-domain approaches
  • MDN – The same origin policy of the browser
  • Cross-domain resource sharing (CORS
  • Browser same origin policy and its circumvention method
  • Common cross-domain solutions at the front end
  • WebSocket-Node

The original text is first published in personal blog, the special series will sort out many articles, and we learn together, common progress, if there is a mistake, please correct

Browser series

  • 1. Browser Thematic series – Caching Mechanisms
  • 2. Browser Thematic series – Principles of rendering
  • 3. Browser Thematic series – Block rendering
  • 4. Browser Thematic Series – Local Storage
  • 5. Browser Special Series – Web Security
  • 6. Browser Series – Browser kernel
  • 7. Browser Thematic series – Cross domain and cross site
  • 8. Browser Thematic Series – Event loops