See GitHub project kuayu-1 for the full code
Same-origin policy – Cross-domain is prohibited
The source
- Source of a URL = protocol + domain name + port number
- Use in developer tools, console
window.origin
orlocation.origin
You can view the source of the current page
homologous
The source of two urls must be exactly the same, so they are homologous
The same-origin policy
- Browsers deliberately specify that if A JS file is running in source A, only source A’s data can be retrieved. Data from other sources cannot be obtained, that is, cross-domain data cannot be obtained
- Pages from different sources are not allowed to access each other’s data
Example (omit HTTP protocol)
- Assuming that
frank.com/index.html
This page referencescdn.com/1.js
This JS file then says”1.js
Run in the sourcefrank.com
In the” - Note that this with
cdn.com
It doesn’t matter, though1.js
fromcdn.com
The download, so1.js
You can only get the sourcefrank.com
The data of
The purpose of the browser is to protect user privacy!
If there is no same-origin policy
- Take QQ Space as an example
- Source for user.qzone.qq.com
- Assuming that the current user is already logged in (using cookies, more on that later)
- Suppose an AJAX request
/friends.json
Get a list of the user’s friends - So far so normal
- Hackers come
- Suppose your goddess shares qzone-qq.com with you
- This is actually a phishing site
- You click on this page, which also requests your friends list
- Using Ajax: The requested URL is
https://user.qzone.qq.com/friends.json
- Did your friends list steal the hacker?
Root of the problem
- Unable to distinguish the sender
- The JS in QQ space page and the JS in hacker web page
- (AJAX request of almost no difference between the requested url is qq friends. Json) (only ask head referfer there’s a difference, one is user.qzone.qq.com/index.html,…).
- If the backend developer doesn’t check the referer, it makes no difference
- So, without the same origin policy, any page can steal qzone data, even alipay balance!
- Why not check the referer?
- Safety rule: a safety chain is only as strong as its weakest link
- What if the backend developer of this site is a fool
- So browsers should be proactive in preventing such data theft
- In short, browsers enforce strict same-origin policies for user privacy
Some of the problems
- Why does a.qq.com visit qq.com also count as cross-domain?
- A: Because historically, different companies have shared domain names, and a.qq.com and QQ.com are not necessarily the same site, the browser is cautious and thinks that they are different sources
- Why do different ports count as cross-domain?
- Answer: The reason is the same as above, one port one company. Remember that a security chain is only as strong as its weakest link, and any security-related issues should be treated with caution
- Why are the IP addresses of the two websites the same and also considered cross-domain?
- A: For the same reason, IP addresses can be shared.
- Why cross-domain use of CSS, JS, images, etc?
- A: The same-origin policy restricts data access. When we refer to CSS, JS, and images, we don’t really know what they are. We just refer to execution. Can’t read content (data) (in Ajax) because it’s not cognate. Can you tell what the first character of CSS is?
All requests in Ajax cannot cross domains, but in Html script tags and link tags, browsers can reference js files and CSS files in other domains. Html script tag, link tag is sent JS, CSS request, that is, in Html JS, CSS request can cross domain. But I can also see the content of JS and CSS
demo
Step 1: Initialization
1. Create directories QQ.com (simulated QQ website) and YY.com (simulated hacker website)
2. It’s in the folder QQ.com
-
Pull the folder qq.com into VSCode
-
Create file server.js as the QQ server to simulate the QQ website.
-
Create a new public/index.html and make its contents the body of the response to the request /index.html (the home page)
-
Create a new public/ q.js and make its content the response body of the request/q.js (js script file)
-
Create a new public/friends.json and make its contents the body of the response to the request /friends.json
-
Open terminal, node-dev server.js 8888 start server
-
In other words, all requests within a server are homologous, and /index.html and/qi.js and /friends.json are homologous
3. In the folder yy.com
-
Files → Add folders to workspace → select YY.com
-
Create file server.js as the server of hacker website, simulate hacker website.
-
Create a new public/index.html and make its contents the body of the response to the request /index.html (the home page)
-
Create a new public/ q.js and make its contents the body of the response to the request/q.js (js script file)
-
Open terminal, node-dev server.js 9990 start server
- See the Github project for the server.js code
Step 2: Obviously, qzone and hacker site are different sources, because the port number is not consistent
Step 3: Start emulating the same origin policy with Ajax
1. Ask the JS of Qzone to request friends.json, the request is successful and the response is received
- Request friends.json in qq.js
const request = new XMLHttpRequest(); request.open("GET", "/friends.json"); request.onreadystatechange = () => { if (request.readyState === 4) { if (request.status >= 200 && request.status < 300) { console.log(request.response); // If the request is successful, the response body will be printed. request.send();Copy the code
- Request successful, response received
- Since qzone references/q.js, q.js runs in the source
"http://localhost:8888"
In, so Q.js can request also in the source"http://localhost:8888"
/ friends in json
2. Let the hacker website JS request friend.json (steal data), the request failed
- Request friends.json in yy.js, please note that the requested URL should be complete, if not complete, how will you know which source friends.json data you want to steal
const request = new XMLHttpRequest(); request.open("GET", "http://localhost:8888/friends.json"); request.onreadystatechange = () => { if (request.readyState === 4) { if (request.status >= 200 && request.status < 300) { console.log(request.response); }}}; request.send();Copy the code
- The request failed
Since the hacker website references /yy.js, /yy.js runs in the source “http://localhost:9990”, so Yy.js cannot request /friends.json in the source “http://localhost:8888”
CORS– I’m going to cross domains
Introduction to the
Cross-origin Resource Sharing (CORS) is a mechanism that allows the resources of the current domain (such as HTML/JS/Web Service) to be accessed by scripts of other domains. Typically, browsers prohibit such cross-domain requests due to the same-Origin security policy.
practice
-
I am the owner of the source “http://localhost:8888” and I agree that my /friends.json can make JS requests running on the source “http://localhost:9990” and get responses!
-
I’m going to set the response header in the /friends.json route on my server, Response. setHeader(‘ access-Control-allow-origin ‘,”http://localhost:9990″)
-
Done!!
Detailed practice
See the MDN documentation when you need it for your work
JSONP–IE is cross-domain
- Although cross-domain is not allowed, this is limited to data content, and we can still reference the JS of any website
- Therefore, I am the source
"http://localhost:8888"
I agree that the data content in my /friends.json can be sourced"http://localhost:9990"
! - So I asked my little staff to write the data contents of /friends.json into /friends.js
- This source
"http://localhost:9990"
/friends.js is referenced in the HTML (and any source), and the content he wants is in it
steps
Qq.com will write the data to /friends.js
- Create a new public/friends.js and say
Window. XXX = {{data}} //{{data}} is a placeholder // another method: Cso.xxx ({{data}}); cso.xxx ({{data}}); cso.xxx ({{data}}); cso.xxx ({{data}});Copy the code
- Add a new route /friends.js to the server
Replace the public/ Friends. js placeholder with the public/ Friends. json data content, and the final response body content is the replaced public/ Friends. js content
else if (path === "/friends.js") { response.statusCode = 200; response.setHeader("Content-Type", "text/javascript; charset=utf-8"); const string = fs.readFileSync("./public/friends.json").toString(); const data = fs.readFileSync("./public/friends.json").toString(); const content = string.replace("{{data}}", data); response.write(content); response.end();Copy the code
2. Hacker websites can quote JS of QQ website
- You need to add script to the HTML of hacker website to reference QQ website /friends.js, then use DOM operation, in hacker website JS script file with DOM operation
- In yy, js
const script = document.createElement("script");
script.src = "http://localhost:8888/friends.js";
document.body.appendChild(script);
Copy the code
- /friends.js = /friends.js = /friends.js = /friends.js = /friends.js = /friends.js = /friends.js = /friends.js = /friends.js
window.xxx = [{ "name": "Linda" }, { "name": "Jack" }, { "name": "Pike" }]; / / another way window. XXX ([{" name ":" Linda "}, {" name ":" Jack "}, {" name ":" Pike "}));Copy the code
- Then, we can print out the list of friends, that is, to operate in the hacker website’s own JS script file /yy.js.
script.onload = () => { console.log(window.xxx); }; // Listen for the loading completion event of the script tag, and when the script tag is generated, execute the function to print the window. XXX // Another method: XXX = x=>{console.log(x)} // So that when I reference friends.js, the content is window. XXX ([{"name": "Linda"}, {"name": "Jack"}, {"name": "Pike"}]), I also happen to have this function, so that the friends list is printed outCopy the code
Note: window. XXX is a callback. Cross-domain callback! Yy.js defines this function when friends.js calls it
Note: The data doesn’t have to come from JSON. It can be XML, so the name JSONP really has nothing to do with JSON
Optimization 1: Let data written as JS be shared in a targeted manner, rather than referenced from any source
Check the referer in the request header and rewrite the server route /firends.js
else if (path === "/friends.js") { if (request.headers["referer"].indexOf("http://localhost:9990") === 0) { response.statusCode = 200; response.setHeader("Content-Type", "text/javascript; charset=utf-8"); const string = fs.readFileSync("./public/friends.js").toString(); const data = fs.readFileSync("./public/friends.json").toString(); const content = string.replace("{{data}}", data); response.write(content); response.end(); } else { response.statusCode = 404; response.end(); }Copy the code
Optimization 2: If window. XXX is already used, use window instead of window. XXX. Random number, this will not be repeated with me
- JS script file yy.com in hacker site
- Name functions with random numbers
- Random number functionName (Callback, functionName)
const random = `yyJSONPCallbackName` + Math.random(); window[random] = x => { console.log(x); }; const script = document.createElement("script"); script.src = `http://localhost:8888/friends.js? functionName=${random}`; document.body.appendChild(script);Copy the code
- On the qq.com/friend. Js
- Put the function name in a placeholder first
window[`{{xxx}}`] ({{data}});
Copy the code
- Share data on qq.com server.js js route /friends.js
- Gets the functionName of the query parameter to the request header of the request, which will be the functionName
- So just replace the placeholders in Friends.js with the function name.
else if (path === "/friends.js") { if (request.headers["referer"].indexOf("http://localhost:9990") === 0) { response.statusCode = 200; response.setHeader("Content-Type", "text/javascript; charset=utf-8"); const string = fs.readFileSync("./public/friends.js").toString(); const data = fs.readFileSync("./public/friends.json").toString(); const content = string .replace("{{data}}", data) .replace("{{xxx}}", query.functionName); // Replace the placeholder of the function name with the random number response.write(content) we stored in the query parameter; response.end(); } else { response.statusCode = 404; response.end(); }}Copy the code
Optimization three: hacker website every qq.com reference JS (every time send JSONP request), will add a script label in their HTML, good bloated!
- JS script file yy.com in hacker site
- When the script tag is loaded, it is deleted
const random = `yyJSONPCallbackName` + Math.random(); window[random] = x => { console.log(x); }; const script = document.createElement("script"); script.src = `http://localhost:8888/friends.js? functionName=${random}`; script.onload = () => { script.remove(); }; document.body.appendChild(script);Copy the code
Optimization4: the background of QQ.com is specially used to save the JS file friends. JS for others to share data. It is only tm line, and you also reference this file in the route
- Delete friend.js from qq.com
- Put his content directly into the server /friends.js route
else if (path === "/friends.js") { if (request.headers["referer"].indexOf("http://localhost:9990") === 0) { response.statusCode = 200; response.setHeader("Content-Type", "text/javascript; charset=utf-8"); const string = "window[`{{xxx}}`] ({{data}})"; // Optimized here! const data = fs.readFileSync("./public/friends.json").toString(); const content = string .replace("{{data}}", data) .replace("{{xxx}}", query.functionName); response.write(content); response.end(); } else { response.statusCode = 404; response.end(); }}Copy the code
Encapsulate JSONP with promises
- All you need to do is give a URL to make a JSONP request
- In the hacker website JS script file yy.js
JSONP function JSONP (url) {return new Promise((resolve, resolve) reject) => { const random = `yyJSONPCallbackName` + Math.random(); window[random] = x => { resolve(x); // Execute resolve on success; const script = document.createElement("script"); script.src = `${url}? functionName=${random}`; script.onload = () => { script.remove(); }; script.onerror = () => { reject(); }; / / failure is executed reject the document () function. The body. The appendChild (script). }); } / / json function called json (" http://localhost:8888/friends.js "). Then (x = > {the console. The log (x); });Copy the code
Finally: What is JSONP
1. First answer what JSONP is
When we cross domains, because the current browser (damn Internet Explorer) doesn’t support CROS, or for some reason doesn’t, we have to use JSONP to cross domains. So we ask for a JS file, and that JS file will execute a callback, and that callback will have our data in it. Our current site to create a script tag to request another site js, JS will entrap the data I want, and this JS will call a global function I wrote to run, you can give me the data. The name of the callback is a random number that we generate at random. We pass the name of the callback to the background as a callback argument, and the background returns the function to us in the execution
2, strengths,
- Compatible with IE
- Cross-domain (it was meant to be cross-domain, I laughed)
3 and disadvantages
- Because the script tag references, you don’t get the details: status codes, headers
- Because of script tag references, only GET requests can be sent, but POST is not supported