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
- 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
- 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
- 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
- A fake website
https://a.com
, internally nested with a full-screen IFrame tag pointing to a bank websitehttps://b.com
- Except for the domain name, users visited the fake site no different from the bank’s site
- Developers can inject input event listening scripts into fake websites for cross-domain access
https://b.com
Contents of nodes - 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
- The user is on the bank’s website
https://b.com
After login, the website uses cookie authentication - Attackers directly from the site
https://a.com
Makes an attack request to the bank’s web site. This cross-domain request carries a cookie from the target site - The bank server verifies the user’s cookie is correct and returns the corresponding response data
- 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></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
- The client registers a global method
function callbackFunName(res){}
- After receiving the request, the server retrieves the parameters of the URL
- The server returns a string
callbackFunName({"name":"sugar","age":18})
- The client is directly parsed and executed as a JS script
- The method is called
callbackFunName
And 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¶m2=2'
document.body.appendChild($srcipt)
</script>
<! -- The final tag constructed -->
<! -- <script src="localhost:3000/path1/path2? param1=1¶m2=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
- By default, only seven simple Response headers can be exposed to the outside world:
- 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