Thinking about Same Origin Strategy and Cross-domain Method

Overview of Same Origin Policy

Meaning

In 1995, the same origin policy was introduced into browsers by Netscape. Currently, all browsers implement this policy.
Originally, it meant that the cookie set by A webpage and B webpage cannot be opened unless the two webpages are “of the same origin”. The so-called “same origin” means “three are the same”. The protocol is the same, the domain name is the same, and the port is the same.

Purpose

The purpose of the same origin policy is to ensure the security of user information and prevent malicious websites from stealing data.

Scenario examples

Steal cookies

Imagine a situation like this: Website A is a bank, and after the user logs in, he goes to another website. What would happen if other websites could read the cookies of Website A?
Obviously, if the cookie contains privacy (such as the total amount of deposits), this information will be leaked. Even more frightening is that cookies are often used to save the user’s login status. If the user does not log out, other websites can impersonate the user and do whatever they want. Because the browser also stipulates that submitting forms is not subject to the same origin policy.

Stealing pages

  1. One day you just woke up and received an email saying that your bank account was at risk, so you quickly clicked on the www.yinghang.com to change the password. You are scared to pee, so you quickly click on it, or the familiar bank login interface, you decisively enter your account password and log in to see if the money is missing.
  2. You didn’t see it clearly, the bank website you usually visit is www.yinhang.com, and now you are visiting www.yinghang.com, what does this phishing website do?
1
2
3
4
5
6
7
// HTML
<iframe name="yinhang" src="www.yinhang.com"></iframe>
// JS
//Since there is no restriction of the same origin strategy, phishing websites can directly get the Dom of other websites.
const iframe = window.frames['yinhang']
Const node = iframe.document.getElementById ('Input for your account password')
Console.log ('I got this ${node}, can't I get the account password you just entered')

It can be seen that the “same origin policy” is necessary, otherwise cookies can be shared and the Internet is not safe at all.

Restricted range

  1. Cookie,LocalStorage和IndexDB。
  2. DOM cannot be obtained.
  3. AJAX request cannot be sent

Two common ways of AJAX cross-domain

CORS

CORS is a W3C standard, the full name is “Cross-Origin Resource Sharing” (Cross-origin resource sharing).
It allows browsers to make XMLHttpRequest requests to cross-origin servers, thus overcoming the limitation that AJAX can only be used from the same origin.

Introduction

CORS requires both browser and server support. Currently, all browsers support this feature, and IE browsers cannot be lower than IE10.

The entire CORS communication process is automatically completed by the browser and does not require customer engagement. For developers, CORS communication is no different from AJAX communication of the same origin, and the code is exactly the same. Once the browser finds that the AJAX request is cross-origin, it will automatically add some additional header information, and sometimes there will be an additional request, but the user will not feel it.

Therefore, the key to achieving CORS communication is the server. As long as the server implements the CORS interface, it can communicate across origins.

Simple requests and non-simple requests

Browsers divide CORS requests into two categories: simple requests and not-so-simple requests.

Simple request

As long as the following two conditions are met at the same time, it is a simple request.

(1)
HEAD
GET
POST
(2) HTTP header information does not exceed the following fields:

Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

For simple requests, the browser issues a CORS request directly. Specifically, an Origin field is added to the header information.

The following is an example. The browser finds that this cross-origin AJAX request is a simple request, and automatically adds an Origin field to the header information

1
2
3
4
5
6
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

In the header information above, the’Origin 'field is used to indicate which source this request comes from (protocol + domain name + port). Based on this value, the server decides whether to agree to the request.

If the source specified by’Origin ‘is not within the permission scope, the server will return a normal HTTP response. The browser detects that the header information of this response does not contain the’Access-Control-Allow-Origin’ field (see below for details), knows that an error has occurred, and throws an error, which is caught by the’onerror ‘callback function of’XMLHttpRequest’. Note that this error cannot be identified by a status code, as the status code of an HTTP response may be 200.

If the domain name specified by Origin is within the permission scope, the response returned by the server will have several more header information fields.

1
2
3
4
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

(1)Access-Control-Allow-Origin

This field is required. Its value is either the value of the’Origin ‘field at the time of the request, or a’ * 'indicating acceptance of the request for any domain name.

(2)Access-Control-Allow-Credentials

This field is optional. Its value is a Boolean value indicating whether to allow sending cookies. By default, cookies are not included in CORS requests. Set’true ‘to indicate that the server explicitly allows cookies to be included in the request and sent to the server. This value can only be set to’true’. If the server does not want the browser to send cookies, delete this field.

(3)Access-Control-Expose-Headers

This field is optional. When requesting CORS, the getResponseHeader () method of the XMLHttpRequest object can only get 6 basic fields: Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma. If you want to get other fields, you must specify them in Access-Control-Exposure-Headers. The above example specifies that getResponseHeader (FooBar) can return the value of the FooBar field.

As mentioned above, CORS requests do not send cookies and HTTP authentication information by default. If you want to send cookies to the server, on the one hand, the server agrees, specify the’Access-Control-Allow-Credentials’ field.

1
Access-Control-Allow-Credentials:

On the other hand, the developer must turn on the’withCredentials’ property in the AJAX request.

1
2
var
xhr.withCredentials

Otherwise, even if the server agrees to send cookies, the browser will not send them. Alternatively, if the server requests to set cookies, the browser will not process them.

However, if the’withCredentials’ setting is omitted, some browsers will still send cookies together. At this time, you can explicitly turn off’withCredentials’.

1
xhr.withCredentials

It should be noted that if you want to send cookies, ‘Access-Control-Allow-Origin’ cannot be set to an asterisk, and a clear domain name that is consistent with the requested web page must be specified. At the same time, cookies still follow the same origin policy, only cookies set with the server domain name will be uploaded, cookies with other domain names will not be uploaded, and the’document.cookie 'in the (cross-origin) original web page code cannot read the server domain name Cookie.

Non-simple request
  1. Preflight requests

Non-simple requests are those that have special requirements for the server, such as the request method being’PUT ‘or’DELETE’, or the type of the’Content-Type ‘field being’application/json’.

CORS requests that are not simple requests will add an HTTP query request before formal communication, called a “preflight” request.

The browser first asks the server whether the domain name where the current webpage is located is in the server’s permission list, and which HTTP verbs and header information fields can be used. Only when a positive answer is received, the browser will issue an official’XMLHttpRequest 'request, otherwise an error will be reported.

Below is a JavaScript script for the browser.

1
2
3
4
5
var
var
xhr.open('PUT',
xhr.setRequestHeader('X-Custom-Header',
xhr.send();

In the above code, the HTTP request method is’PUT ‘and sends a custom header information’X-Custom-Header’.

The browser finds that this is a non-simple request and automatically issues a “preflight” request, asking the server to confirm that such a request can be made. Below is the HTTP header information of this “preflight” request.

1
2
3
4
5
6
7
8
OPTIONS
Origin:
Access-Control-Request-Method:
Access-Control-Request-Headers:
Host:
Accept-Language:
Connection:
User-Agent:

The request method used for the “preflight” request is’OPTIONS ‘, indicating that the request is used for inquiry. In the header information, the keyword field is’Origin’, indicating which source the request comes from.

In addition to the’Origin 'field, the header information of the “preflight” request includes two special fields.

(1)Access-Control-Request-Method

This field is required to list which HTTP methods will be used in the browser’s CORS request. The above example is’PUT '.

(2)Access-Control-Request-Headers

This field is a comma-separated string that specifies the additional header information field that the browser CORS request will send. The above example is’X-Custom-Header '.

  1. Return of preflight request

After the server receives the “preflight” request, checks the’Origin ‘,’ Access-Control-Request-Method ‘, and’Access-Control-Request-Headers’ fields, and confirms that cross-origin requests are allowed, it can respond.

1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1
Date:
Server:
Access-Control-Allow-Origin:
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Content-Type:
Content-Encoding:
Content-Length:
Keep-Alive:
Connection:
Content-Type:

In the HTTP response above, the key is the Access-Control-Allow-Origin field, indicating that http://api.bob.com can request data. This field can also be set to an asterisk to indicate consent to any cross-origin request.

1
Access-Control-Allow-Origin:

If the browser rejects the “preflight” request, it will return a normal HTTP response, but without any CORS-related header information fields. At this time, the browser will determine that the server does not agree with the preflight request, so an error is triggered, which is caught by the “onerror” callback function of the “XMLHttpRequest” object. The Console will print the following error message.

1
2
XMLHttpRequest
Origin

Other CORS related fields of the server response are as follows.

1
2
3
4
Access-Control-Allow-Methods:
Access-Control-Allow-Headers:
Access-Control-Allow-Credentials:
Access-Control-Max-Age:

(1)Access-Control-Allow-Methods

This field is required. Its value is a comma-separated string indicating all methods supported by the server for cross-origin requests. Note that all supported methods are returned, not just the one requested by the browser. This is to avoid multiple “preflight” requests.

(2)Access-Control-Allow-Headers

The Access-Control-Request-Headers field is required if the browser request includes the Access-Control-Request-Headers field. It is also a comma-separated string indicating all header information fields supported by the server, not limited to the fields requested by the browser in the “preflight”.

(3)Access-Control-Allow-Credentials

This field has the same meaning as a simple request.

(4)Access-Control-Max-Age

This field is optional and specifies the valid period of this preflight request in seconds. In the above result, the valid period is 20 days (1728000 seconds), which allows the response to be cached for 1728000 seconds (ie 20 days). During this period, another preflight request does not need to be issued.

  1. Normal requests after the preflight request is successful.

Once the server passes the “preflight” request, every normal CORS request from the browser in the future will have an’Origin ‘header information field like a simple request. The server’s response will also have an’Access-Control-Allow-Origin’ header information field.

The following is the normal CORS request of the browser after the “preflight” request.

1
2
3
4
5
6
7
PUT
Origin:
Host:
X-Custom-Header:
Accept-Language:
Connection:
User-Agent:

The’Origin 'field of the above header information is automatically added by the browser.

Below is the normal response from the server.

1
2
Access-Control-Allow-Origin:
Content-Type:

In the above information, the’Access-Control-Allow-Origin 'field must be included in each response.

Nginx reverse proxy

Think about it, if we still use the front-end domain name when we request, and then something helps us forward the request to the real back-end domain name, won’t cross-domain be avoided? At this time, Nginx appeared.

1
2
3
4
5
6
7
8
9
10
server{
# listening on port 9099
listen 9099;
# domain name is localhost
server_name localhost;
Everything that localhost:9099/api like this is forwarded to the real server level address http://localhost:9871
location ^~ /api {
proxy_pass http://localhost:9871;
}
}

Same-Origin Policy Thinking Triggered by Retargeting Request

Encountered such a situation today, the front end is written in vue, the home page of the routing guard will modify the current url to call the background of the express interface.

In the middle of the processing key in this interface, the authentication interface of passport-azure will be called, and this process can be executed smoothly.

However, if I find that the session expires in the middle key of ajax request processing, I will automatically help the user go to Azure again, but an error will be thrown at this time.

1
2
3
4
5
6
7
8
9
10
//middle key
function loginMiddleware(req, res, next) {
passport.authenticate('azuread-openidconnect',
{
response: res, // required
customState: 'my_state', // optional. Provide a value if you want to provide custom state value.
failureRedirect: '/'
}
)(req, res, next);
}
1
2
3
4
//call by url
app.route('/api/auth/user/login').get(aadController.loginMiddleware);
//ajax request
app.all('/api/*', aadController.ensureAuthenticatedWithSkipPath);

The above code can run normally in the first route, which is the way the url is called, but if it takes the second route, an error will be thrown.

Assuming my request is A, if the identity verification is passed, it will be executed normally. If it is not, I will automatically call the authentication request of passport-azure for the user, assuming it is B. At this time, the Console will report an error, saying that I Retargeting the request B from A cross-domain.

There are two issues involved here

  • Why is request B sent by backend code restricted by the browser’s same origin policy.

  • Why is it said that my request B is from A Retargeting.

  • Why does B request cross-domain at this time, but I call this B through the url but not cross-domain.

First of all, let’s talk about the answers to the first two questions. The first two questions can actually be explained together, because the req used in the whole passport is actually passed from the front end, so it has always been a request, but the passport is Retargeting this request. That’s it.

The second question is why the url will not be cross-domain, while ajax will. This problem can be understood in two ways. You can think that the browser’s same-origin policy has no restrictions on the url method, or you can think that calling a request directly through the url is in a domain from beginning to end, that is, the domain where your request is located, so there is no cross-domain.

Summary

First of all, the main purpose of the same origin policy is to limit the random call of resources between different websites and ensure the security of the network. His identification relies on the origin field in the request header, and the origin field is determined when requesting a page. For example, if you are requesting a www.baidu.com, then the origin field of any request sent from this page is www.baidu.com. Baidu’s server detects that the origin field of these requests is the same as itself. If it is the same, it will not trigger cross-domain issues. Reject the request.

However, due to the same origin policy is too strict, when my backend is composed of many servers together, it is inevitable to encounter cross-domain problems, so you need some cross-domain way to make my backend servers can access each other.

Security Issues of Same Origin Policy

Assuming that there is mutual trust between A and B websites, if a hacker controls A through XSS, this time to request some resources of B as A, it will not be limited by the same-origin policy.

Reference article:

https://segmentfault.com/a/1190000015597029

http://www.ruanyifeng.com/blog/2016/04/cors.html