takeaway
This article, based on [RFC 6265], briefly explains the use and features of cookies. It probably includes the following four contents: 1) Introduce the use of Cookie; 2) Explain the format of Cookie; 3) Test Cookie’s response in various situations; 4) CSRF attack description
Environment, tools, and prior knowledge
- System:
macOS Mojava
- IDE:
IDEA
SwitchHosts
OpenSSL
Chrome
js
basisNodeJS
basisHTTP
basis
0x001 Use of Cookie
Using cookies is very simple and can be summarized in 4 steps:
- The front-end sent
HTTP
Request to the back end - Back-end generation is to be placed in
cookie
And set to the responseSet-Cookie
The head - The front-end pulls out of the response
Set-Cookie
Save the contentcookie
Information to local - The front-end continues to access the back-end page, which will be saved locally
cookie
Put in the requestedCookie
The head
Illustrate with a picture:
With code:
const http = require('http');
http.createServer((req, res) => {
res.writeHead(200, {'Set-Cookie': 'name=123'})
res.write(`
<script>document.write(document.cookie)</script>
`)
res.end()
}).listen(3000)
Copy the code
The function of this code is simple:
- To create a
HTTP
The server - Add one for each response
Set-Cookie
The head, its value is zeroname=123
- The body of the response is one
html
Only one of themscript
Tag, the script inside the tag willcookie
Output to the current page
Start the script, then open your browser and visit:
$ node index.js
$ open http://localhost:3000
Copy the code
You can see:
- Display on the page
name=123
It is usSet-Cookie
The content of the HTTP
的response
The head isSet-Cookie: name=123
Open Application > cookies > localhost:3000 and you can see the cookie we set:
Open another TAB and then visit the page (or refresh it, but open another TAB for comparison) :
As you can see, there is an extra Cookie in the request compared to the first access, and the contents of the Cookie are the contents of our set-cookie (this does not mean that Cookie === set-cookie, The content of the Cookie comes from set-cookie. The conversion from set-cookie to Cookie will be explained later.
This is the simplest way to use cookies.
Format description of 0x002 cookies
As you can see from Chrome’s cookies management tool, a cookie has many attributes:
Name
Value
Domain
Path
Expire / Max-Age
Size
(Ignore, should only be front-end statistics Cookie key value pair length, such as “name” and “123” length is 7, if you know, please inform)HttpOnly
Secure
SameSite
These properties will be explained slowly in the following sections.
1. Expire
Expires is used to set an expiration date for a cookie. It’s a UTC time after which the cookie is no longer valid and the browser will not include the expired cookie in the cookie request header. And the expired cookie is deleted.
Code:
const http = require('http');
http.createServer((req, res) => {
const cookie =req.headers['cookie']
const date = new Date();
date.setMinutes(date.getMinutes()+1)
if(! cookie || ! cookie.length){ res.writeHead(200, {'Set-Cookie': `name=123; expires=${date.toUTCString()}`})
}
res.write(`
<script>document.write(document.cookie)</script>
`)
res.end()
}).listen(3000)
Copy the code
The above code adds an extra expires attribute to the set-cookie in response, which is used to Set the expiration time. Here it is Set to one minute later:
const date = new Date();
date.setMinutes(date.getMinutes()+1)
Copy the code
We also make a judgment that if there is a cookie in the request, we do not add the set-cookie header, otherwise it will never expire.
Open your browser (delete previous cookies first) and go to localhost:3000:
Date: Sun, 01 Dec 2019 15:24:47 GMT
Set-Cookie: name=123; expires=Sun, 01 Dec 2019 15:25:47 GMT
Copy the code
The set-cookie format was changed to include an expires attribute, and the time is 1 minute after the Date attribute. Within this minute, if we refresh the page, we will find that there is a Cookie in request and no set-cookie in response:
After 1 minute, a new set-cookie is generated, and the Cookie in the request is missing:
The expires attribute is in the format:
expires-av = "Expires="Sanal-cookie-date sanal-cookie-date = < RFC113-date, defined in [RFC2616], Section 3.3.1>Copy the code
Sana-cookie-date is a time format that looks something like this:
Sun, 06 Nov 1994 08:49:37 GMT
Copy the code
Js can be obtained using:
$ new Date().toUTCString()
"Sun, 01 Dec 2019 15:18:12 GMT"
Copy the code
The default expires value is session, which is deleted when the current browser closes.
2. Max-Age
Max-age is also used to set the cookie expiration time, but it sets the number of seconds relative to the time the resource was fetched. For example, if the response Date is Sun, 01 Dec 2019 15:44:43 GMT for the first time, the cookie will expire at Sun, 01 Dec 2019 15:45:43 GMT
On the first request, you can see that the cookie format is: set-cookie: name=123; Max-age =60, one more max-age=60.
If accessed within 60 seconds, you will see that the request contains a Cookie: name=123 and the response does not have a set-cookie
After 60 s, a new cookie is created.
Max-age defaults to session and is deleted when the current browser is closed.
The format of max-age is:
max-age-av = "Max-Age=" non-zero-digit *DIGIT
Copy the code
3. Domain
Domain limits the Domain name under which this cookie is included in the cookie header of the request.
For example, if we set a cookie with the Domain attribute example.com, the user agent will send the cookie to example.com, www.example.com, This Cookie is carried in the request Cookie at www.corp.example.com.
The code:
const http = require('http');
http.createServer((req, res) => {
const cookie =req.headers['cookie']
const date = new Date();
date.setMinutes(date.getMinutes()+1)
if(! cookie || ! cookie.length){ res.writeHead(200, {'Set-Cookie': 'name=123; domain=example.com'})
}
res.write(`
<script>document.write(document.cookie)</script>
`)
res.end()
}).listen(3000)
Copy the code
Here we add a domain=example.com. Then use SwitchHost to configure several hosts:
127.0.0.1 example.com
127.0.0.1 www.example.com
127.0.0.1 www.corp.example.com
Copy the code
Access example.com:3000, which is returned when accessed for the first time
Set-Cookie: name=123; domain=example.comCopy the code
Then we refresh the page and see that the cookie is carried to the request:
Visit www.example.com:3000 and this cookie also exists
Visit www.corp.example.com:3000, there are the cookies
But if you access exampel2.com:3000, it doesn’t exist, and because the domain in the cookie is different from the current domain, the user agent refuses to store the cookie, so we don’t see the output of the cookie:
cookie
cookie
The default value of domain is the current domain name.
The format for domain is:
domain-av = "Domain=" domain-value
Copy the code
4. Path
While Domain is limited to the cookie’s Domain name, Path is limited to the cookie’s Path. For example, if we set the cookie’s Path to /a, it will not be available at/or /b
The code:
const http = require('http');
http.createServer((req, res) => {
const cookie =req.headers['cookie']
const date = new Date();
date.setMinutes(date.getMinutes()+1)
if(! cookie || ! cookie.length){ res.writeHead(200, {'Set-Cookie': 'name=123; path=/a'})
}
res.write(`
<script>document.write(document.cookie)</script>
`)
res.end()
}).listen(3000)
Copy the code
Add a path=/a, nothing else changes, call localhost:3000/a, get a cookie:
Refresh the page and send the cookie you just got:
Access /a/b, but can:
Access /b, no cookie is sent, and a new cookie is generated:
This path was rejected because it did not match the current path:
If/is accessed, the same result is obtained as if /b is accessed:
Therefore, Path can restrict which Path and its children a cookie can use. Ancestor and sibling paths are not allowed. If this value is not available, it is /
The default value for Path is /, which is valid for the entire site.
The format of path is:
path-av = "Path=" path-value
Copy the code
5. Secure
Secure is used to indicate that a cookie is sent only over a “Secure” channel, which generally means HTTPS. This means that it is sent only when HTTPS is used, not HTTP.
The code:
let https = require("https");
let http = require("http");
let fs = require("fs");
const options = {
key: fs.readFileSync('./server.key'),
cert: fs.readFileSync('./server.pem')}; const app = (req, res) => { const cookie =req.headers['cookie']
const date = new Date();
date.setMinutes(date.getMinutes()+1)
if(! cookie || ! cookie.length){ res.writeHead(200, {'Set-Cookie': 'name=123; secure'})
}
res.write(`
<script>document.write(document.cookie)</script>
`)
res.end()
}
https.createServer(options, app).listen(443);
http.createServer(app).listen(80);
Copy the code
Port 80 and port 443 are used to provide HTTP and HTTPS services. The certificates required for HTTPS services are generated by OpenSSL, which is not mentioned here. Visit https://example.com to get cookies:
Refresh the page and send cookies:
Visit http://example.com to get cookies:
But rejected:
The default value of Secure is false, that is, HTTP/HTTPS access.
The secure format is:
secure-av = "Secure"
Copy the code
6. HttpOnly
Secure Indicates that cookies can only be used over Secure channels. HttpOnly indicates that cookies can only be used over Http. We’ve been accessing cookies on the front end through document.cookie, but if we specify this, we can’t manipulate cookies through Document. cookie.
The code:
const http = require('http');
http.createServer((req, res) => {
const cookie =req.headers['cookie']
const date = new Date();
date.setMinutes(date.getMinutes()+1)
if(! cookie || ! cookie.length){ res.writeHead(200, {'Set-Cookie': 'name=123; httpOnly'})
}
res.write(`
<script>document.write(document.cookie)</script>
`)
res.end()
}).listen(3000)
Copy the code
HttpOnly: localhost:3000
Refresh, send cookies:
Use document.cookie to manipulate cookies:
> document.cookie = 'name=bar'
< "name=bar"
> document.cookie
< ""
Copy the code
If you look at the cookie, you can see that value is still 123, and HttpOnly is checked.
The default value for httpOnly is fasle, which means cookies can be manipulated using document.cookie
The format of httpOnly is
httponly-av = "HttpOnly"
Copy the code
7. SameSite
Notice, before WE get to this property, it’s important to note that the cookie in the request header is not sent only when the url is entered in the address bar, but when the HTTP request is sent across all pages of the browser, such as the SRC of IMG, Script SRC, link SRC, ifram SRC, and even ajax configuration, if the conditions mentioned above are met, the HTTP request will contain the cookie of the request address, even if you are visiting from another website. This is why CSRF is available.
The code:
const http = require('http');
http.createServer((req, res) => {
if (req.headers.host.startsWith('example')) {
res.writeHead(200, {'Set-Cookie': `host=${req.headers.host}`})}if (req.headers.host.startsWith('localhost')) {
res.write(`
<script src="http://example.com:3000/script.js"></script>
<img src="http://example.com:3000/img.jpg"/>
<iframe src="http://example.com:3000/"/>
`)
}
res.end()
}).listen(3000)
Copy the code
If the accessed address begins with example, a cookie is set with the name host and the value is the current accessed address. If the accessed address begins with localhost, return three elements whose SRC points to example.com:3000, three different resources (which don’t exist, but are sufficient). Visit example.com:3000 and get cookie:host=example.com :3000
Refresh the page and send cookies normally:
Localhost :3000. Because of the previous code, script, img, iframe will be returned, and the user agent will load these three resources:
When you open these three resources, you can see that each resource carries a cookie.
-
script.js
-
img.jpg
-
index.html
SameSite is designed to limit this:
Strict
When SameSite is Strict, the user agent will only send cookies that match the exact URL of the site, not even the subdomain.
The code:
const http = require('http');
http.createServer((req, res) => {
if (req.headers.host.startsWith('example')) {
res.writeHead(200, {'Set-Cookie': `host=${req.headers.host}; SameSite=Strict`}) }if (req.headers.host.startsWith('localhost')) {
res.write(`
<a href="http://example.com:3000/index.html">http://example.com:3000/index.html</a>
<script src="http://example.com:3000/script.js"></script>
<img src="http://example.com:3000/img.jpg"/>
<iframe src="http://example.com:3000/index.html"/>
`)
}
res.end()
}).listen(3000)
Copy the code
-
Access example.com:3000 to get cookies:
-
Refresh normal sending cookies:
-
Accessing localhost:3000 any resource that points to example.com:3000 does not send cookies:
-
Includes jumping past from this page:
-
subdomain
Lax
The default SameSite for newer browsers is Lax. If SameSite is Lax, it will be reserved for cross-site sub-requests, such as image loads or calls to frames, but will only be sent when the user navigates to a URL from an external site. Such as link link. (I have not been able to test it out, specifically look at ruan Yifeng -Cookie’s SameSite attribute and MDN-HTTP cookies)
The code:
const http = require('http');
http.createServer((req, res) => {
if (req.headers.host.startsWith('example')) {
res.writeHead(200, {'Set-Cookie': `host=${req.headers.host}; SameSite=Lax`}) }if (req.headers.host.startsWith('localhost')) {
res.write(`
<a href="http://example.com:3000/index.html">http://example.com:3000/index.html</a>
<form action="http://example.com:3000/form" method="post">
<button>post</button>
</form>
<form action="http://example.com:3000/form" method="get">
<button>get</button>
</form>
<script src="http://example.com:3000/script.js"></script>
<img src="http://example.com:3000/img.jpg"/>
<iframe src="http://example.com:3000/index.html"/>
`)
}
res.end()
}).listen(3000)
Copy the code
Here are my results
- A Link forward: with cookies
- The form [action = get] : take cookies
- Form [action=post] : no cookie
- Iframe: does not contain cookies
- Script: no cookie
- Img: No cookies
That is, the simple navigation jump band, and the resource load does not take.
None
On newer browsers, if you want cookies to support both same-site and cross-site Settings, you need to explicitly specify SameSite=None (my test results are consistent with not adding this attribute).
The code:
const http = require('http');
http.createServer((req, res) => {
if (req.headers.host.startsWith('example')) {
res.writeHead(200, {'Set-Cookie': `host=${req.headers.host}; SameSite=None`}) }if (req.headers.host.startsWith('localhost')) {
res.write(`
<a href="http://example.com:3000/index.html">http://example.com:3000/index.html</a>
<form action="http://example.com:3000/form" method="post">
<button>post</button>
</form>
<form action="http://example.com:3000/form" method="get">
<button>get</button>
</form>
<script src="http://example.com:3000/script.js"></script>
<img src="http://example.com:3000/img.jpg"/>
<iframe src="http://example.com:3000/index.html"/>
`)
}
res.end()
}).listen(3000)
Copy the code
8. Set-cookie format
In fact, the format of set-cookie is composed of Cookie key-value pair and attribute list, so it can be expressed as follows (without ABNF) :
Set-Cookie: cookie-pair ";" cookie-av ";" cookie-av....
Copy the code
Cookie-pair is a format similar to name=123 written above:
cookie-pair = cookie-name "=" cookie-value
Copy the code
This can be followed by a set of properties, cookie-pair and cookie-av; And cookie-AV can be expressed as:
cookie-av = expires-av / max-age-av / domain-av /
path-av / secure-av / httponly-av /
extension-av
Copy the code
The expires-av, max-age-av, domain-av, path-av, secure-av, and httponly-av correspond to each of the above attributes, but there are some differences. Chrome implements the same Origin attribute. However, RFC 6265 does not have this property, which can be viewed as extension-AV,
Do the correspondence again:
- Name: cookie – Name
- Value: the cookie – Value
- Domain: Domain – the av
- Path: the Path – the av
- Expire/max-age: Expires -av/max-age-av
- Size (ignore, should only be front-end statistics Cookie key value pair length, such as “name” and “123” length is 7, if any big god knows, please inform)
- HttpOnly: HttpOnly – av
- Secure: Secure – av
- SameSite: the extension – the av
You can see that the format of cookies is not the same as that of cookies in Chrome, it tiled cookie-pair and cookie-av.
0x003 Cookie
In a variety of situations
1. In each status codeSet-Cookie
Will it all be dealt with? including301
,404
,500
?
According to RFC 6265, the user agent may ignore status codes contained at level 100, but must process set-cookies (both level 400 and level 500) in other responses.
According to the tests, in addition to 100,101, a 407 (for unknown reasons) does not process set-cookies, and even 999 does
2. How to send multiple cookies to set-cookie, and how to deal with cookies?
The code:
const http = require('http');
http.createServer((req, res) => {
res.setHeader('Set-Cookie'['cookie1=1'.'cookie1=2'.'cookie2=2'.'cookie3=3'])
res.end()
}).listen(3000)
Copy the code
Call localhost:3000 and get cookie:
As you can see, cookies are placed in multiple set-cookie headers. In FACT, RFC 7230 states that there should not be duplicate headers in a packet. If a header has multiple values, it should be separated by, Like the accept-encoding and Accept-language below. The problem is, it can exist as a valid value for cookie-value, but dropping it as a delimiter will break cookie parsing 😢, so there you go.
Refresh the sent Cookie, and you can see that the Cookie is folded, and the repeated cookie-name Cookie is overwritten in order:
3. How does Ajax send cookies? Will set-cookies returned by Ajax be accepted?
The answer is yes, using axios as an example:
const http = require('http');
http.createServer((req, res) => {
res.writeHead(200, {'Set-Cookie': 'name=ajax'})
res.write(`
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script >
axios.get('http://example.com:3000/', {
withCredentials: true
})
</script>
`)
res.end()
}).listen(3000)
Copy the code
Call localhost:3000 and get cookie:
Check the cookie store, it has been saved:
View the Ajax request that has been sent:
The same origin policy is followed. In the case of the same origin, the packet is sent by default. In the case of cross-domains, you need to add withCredentials: True. The XMLHttpRequest and Fetch configurations may differ.
Off the top of my head
0x004 CSRF
example
- Open the login page
localhost:3000/login
After logging in, the back end returns oneSet-Cookie
:name=bob
, back-end readCookie
Determine if the user is logged in. If logged in, the amount of the account will be displayed, along with a transfer form
- Transfers are made through
http://localhost:3000/transfer?name=lucy&money=1000
To make the transfer,name
Is the target user of the transfer,money
Is the amount. Here for convenience, use directlyGET
.
- Start a new service, pretend to be another site, this site is only one
img
theimg
的src
It points to the transfer address above:
const http = require('http');
http.createServer((req, res) => {
res.writeHead(200, {'Content-Type':'text/html'})
res.write(`
<img src="http://localhost:3000/transfer? name=bad&money=10000000">
`)
res.end()
}).listen(3001)
Copy the code
- Has been in
localhost:3000
Log inbob
accessexample.com:3001
, you will find that although only a picture points tolocalhost:3000
But because it’s availablelocalhost:3000
的cookie
That’s why it’s taken there, and this address is based oncookie
Determine the user and perform the transfer operation:
- If Bob returns to his account page, he is bankrupt and restricted to high spending:
defense
(This article mainly talks about cookie, CSRF principle and defense is not the focus)
- detection
Referrer
Header: But some browsers can disable this header (I can’t find one that does either) SameSite
: Not yet universalcsrftoken
: generalcsrf
Launch attacks are similar to the above, set up a phishing site, in fact, the phishing site is unable to access the source stationcookie
, sendcookie
Is browser native behavior, so just generate a randomtoken
Just bring it with you when each form is sent, because phishing sites can’t predict thistoken
The value of the. You can even take thistoken
Directly stored incookie
Is retrieved and sent with the form when it is sent. It can also be injected into a form when the backend generates itinputp[typehidden]
The domain.- Secondary authentication, such as verification code, payment password, etc
0 x005 resources
- Example source code
- RFC 6265-HTTP State Management Mechanism
- MDN HTTP Cookie
- Ruan Yifeng -Cookie SameSite
0 x006 take goods
React is a phenomenal micro scene editor.