If you have any questions during reading, please let me know in the comments section. All comments will be replied to
Since the release of Chrome 80 earlier this year, Google chrome has changed the default Cookie policy: The default value of the SameSite property has been changed to Lax.
Gold nine silver ten, no problem of the harvest season. Some people got the offer, some people got flowers, and worse still, they received milk tea in autumn. In front of the thriving scene let me this ten thousand years single dog also began to mind, mobile phone suddenly shivered, pulled my heartstrings. Could it be…
To my surprise, WTM received this…
Dear Employee: After scanning by the security team, the XXX business line you are in charge of has CSRF vulnerability, which is now ordered to be repaired within a time limit. Repair time 2020.09.25. Please repair in time and contact the security group for reinspection.... Jiangnan Tannery safety groupCopy the code
U1S1 although safety related interview questions back very 6 but play real combat…
But on second thought, as a development siege lion with development experience. In the face of difficulties, or to pretend as always calm and calm. Who goes to hell if I don’t?
See the leadership suspicious expression, my heart is actually this style son…
Old rules, encounter a problem affirmation is to learn this problem first, then I opened Baidu
What is thecsrf
Cross-site request forgery Cross-site Request Forgery, also known as one-click attack or session riding, usually abbreviated as CSRF or XSRF, Is a method of hijacking a user to perform unintended actions on a currently logged Web application. In contrast to cross-site scripting (XSS), which exploits the user’s trust in a given site, CSRF exploits the site’s trust in the user’s Web browser.
— from Baidu Baike
According to the summary in Baidu Encyclopedia, cross-site request forgery means three: cross-site request forgery. To implement the cross-site premise, we create a site…
Create a targeted website (fat and full)
- First, we create a directory and add related files. The directory structure is as follows
├ ─ ─ frond - end front-end code directory │ ├ ─ ─ index. The HTML user homepage │ └ ─ ─ the login. The login HTML page ├ ─ ─ package - lock. Json NPM ├ ─ ─ package. The json NPM └ ─ ─ ├── ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ index.js ├─ ├─ index.js ├─ ├─ index.js ├─ ├─ index.js The back-end library entry file from which the methods in the library are exportedCopy the code
- Second, write the project code
- The first step is to implement the simplest login page login.html
<! DOCTYPEhtml> <html> <head> <title>Login - Fat belly full</title> </head> <body> <h1>User login, fat belly ~</h1> <form id="form" method="POST" action="/api/login"> <input required name="id" placeholder="Please enter user ID" /> <button type="submit">The login</button> </form> </body> </html> Copy the code
- The second step is to realize the front-end page index.html of the user’s home page
<! DOCTYPEhtml> <html lang="en"> <head> <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script> <title>Welcome to use - fat and full</title> </head> <body> <h1>Fat fills the belly, and fat supports the belly</h1> <h3>welcome<span id="user"></span>. Wallet balance:<span id="money"></span>yuan</h3> <form> <label>Transfer to:</label> <input name="toUser" placeholder="Who's the baby?"> <input name="money" placeholder="Transfer Amount"> <button type="submit">transfer</button> </form> <script> const refresMyMoney = () = > { $.get('/api/appinfo').done(({ errno, data }) = > { if (errno === 0) { const { userName, money } = data $('#money').html(money) $('#user').html(userName) } else { location.href = '/login.html'}})} $('form').on('submit'.e= > { e.preventDefault() const formData = $(e.target).serializeArray() .map(({ name, value }) = > ({ [name]: value })) .reduce((memo, curItem) = >({... memo, ... curItem }), {}) $.get('/api/transfer', formData).done(resp= > { if (resp.errno === 0) { refresMyMoney() } else { location.href = '/login.html'}})})// When the page is loaded, request the current page data window.onload = refresMyMoney </script> </body> </html> Copy the code
- Step 3, implement the back-end logic server.js
const Koa = require('koa') const KoaRouter = require('koa-router') const path = require('path') const koaBody = require('koa-body') const koaStatic = require('koa-static') const { db, getReqData } = require('./utils') const PORT = 2333 const app = new Koa() const router = new KoaRouter() router.all('/api/login'.ctx= > { const { body: { id } } = getReqData(ctx) if (db[id]) { ctx.cookies.set('userid', id) ctx.redirect('/')}else { ctx.redirect('/login.html') } }) router.all('/api/appinfo'.ctx= > { const { id } = getReqData(ctx) if (db[id]) { ctx.body = { errno: 0.data: { money: db[id], userName: id } } } else { ctx.body = { errno: Awesome! } } }) router.all('/api/transfer'.ctx= > { const { query: { toUser, money }, id } = getReqData(ctx) if(! id) { ctx.body = {errno: Awesome!.errmsg: 'You are not logged in please log in' } return } db[toUser] += (+money) db[id] -= (+money) ctx.body = { errno: 0 } }) app.use(koaBody()) app.use(router.routes(), router.allowedMethods()) app.use(koaStatic(path.resolve(__dirname, '.. /frond-end'))) app.listen(PORT, () = > { console.log(`the server is running in ${PORT}`)})Copy the code
- Finally, implement the backend tool library
// db.js module.exports = { quanquan: 100.zhangsan: 100.huangguan: 100 } // getReqData.js module.exports = (ctx) = > { const { query, request: { body } } = ctx const id = ctx.cookies.get('userid') const token = ctx.headers.token return { query, body, id, token } } // index.js exports.getReqData = require('./getReqData') exports.db = require('./db') Copy the code
- The first step is to implement the simplest login page login.html
- Then, install the dependencies and modify them
package.json
The specific steps are as follows:- perform
npm init -y
Quickly initialize the Node project - perform
npm i koa koa-body koa-router koa-static
Installation project dependencies - perform
npm i nodemon -D
Installation development dependency - in
package.json
Of the filescript
Add under field"start": "nodemon server/server.js"
- Well done, buddy…
- perform
- The last
npm start
, browser accesslocalhost:2333, see the following page, it is successful ~
At this point, we have the core functionality of (so far) the website of the world’s largest IPO company. Soon to ring the bell, and a little bit excited ~
To make our site look bigger, we added a NB domain name to it, modified the hosts file, and added the following two lines
127.0.0.1 zhifubao.com
127.0.0.1 huangguan.com
Copy the code
After the addition, we can access the website through Zhifubao.com. You can experience the two browsers login zhangsan and Quanquan respectively, and experience the mutual transfer function between them, as shown below
Current step code address
Launch the first wave of attacks
After a meal operation, we have been attacked website — fat belly full, the following began to attack operation. As summarized in Baidu Baike. As a cross-site scripting attack, the first thing to do is cross-site. Next we create an attack site, create a directory hacker and create an index.html file that says:
<! DOCTYPEhtml>
<html>
<head>
<meta charset="UTF-8">
<title>----</title>
<style>
html.body.iframe { margin: 0; padding: 0; width: 100%; height: 100%; }</style>
</head>
<body>
<iframe src="/" frameborder="0"></iframe>
<img src="http://zhifubao.com:2333/api/transfer?toUser=quanquan&money=10">
</body>
</html>
Copy the code
Then, install NPM i-g live-server globally and execute live-server in the hacker directory. 127.0.0.1:8080 is the host of our attack
Now, zhang SAN, a diligent student, stays up all night to learn the basic principles and architecture ideas of the Linux kernel. All of a sudden I got an E-mail.
This is daytime, encounter this kind of mail that must be a swipe close popover. But zhang SAN, who had been studying hard all night, was wondering.
So the layout of the browser looks like this.
Shy student Zhang SAN felt ashamed when he saw the page and closed it quickly. Refresh the browser. 10 bucks short, 10 bucks extra in the ring account next door.
Eat mute kui of very uncomfortable, dare not stretch again.
Open the console and review what you just did.
There is a request from Zhibubao.com in the console of hacker website, and it also carries cookies. Poor Zhang SAN was attacked by the simplest CSRF even though he closed the page quickly. Let’s summarize the attack process:
- First, guide
Fat belly users
accessHacker web site
- Second, through the page inside
img
The label toFat belly full
sendGET
Request and carryFat belly full
Everything under the siteCookie
- Finally, the request is approved and the transfer is completed
The code address of the step so far
Defend against IMG tag GET attacks
When you think about the img tag on your hacker website, you’ll only send a GET request, so why not change the transfer interface to POST?
Talk is cheap…
The front-end code
// Change the request mode to POST
$.post('/api/transfer', formData).done(resp= > {
Copy the code
The back-end code
router.post('/api/transfer'.ctx= > {
const { body: { toUser, money }, id } = getReqData(ctx)
if(! id) { ctx.body = {errno: Awesome!.errmsg: 'You are not logged in please log in' }
return
}
db[toUser] += (+money)
db[id] -= (+money)
ctx.body = { errno: 0}})Copy the code
Refresh the page to test the transfer function again, and it still works at 😂
Then open up Hacker and try…
CSRF cross-site request forgery is so easy to solve.
No worries, the long night zhang SAN decided to…
Defend against form attacks
Bug easily fixed, eating hot pot singing, happy happy…
By modifying the request method this is blocking many entry-level script kids. But when you meet hacker’s boss, you’ll get nothing but a shrug…
Find interface 404 hacker and update the code briefly
<body>
<iframe src="/" frameborder="0"></iframe>
<form action="http://zhifubao.com:2333/api/transfer" method="post">
<input type="hidden" name="toUser" value="quanquan">
<input type="hidden" name="money" value="10">
</form>
<script>
document.addEventListener('DOMContentLoaded'.() = > {
document.forms[0].submit()
})
</script>
</body>
Copy the code
Then, zhang SAN, who visited the page again, found that the page had jumped directly and the circle on the left had added another 10 yuan to the account
Zhang SAN feel not good, the website of a famous far – known unexpectedly so random jump. Backhanded to the relevant department to report the hacker website. So the hackers upgraded the site again.
<body>
<iframe src="/" frameborder="0"></iframe>
<iframe src="/" frameborder="0" style="display: none;" name="hacker"></iframe>
<form action="http://zhifubao.com:2333/api/transfer" method="post" target="hacker">
<input type="hidden" name="toUser" value="quanquan">
<input type="hidden" name="money" value="10">
</form>
<script>
document.addEventListener('DOMContentLoaded'.() = > {
document.forms[0].submit()
})
</script>
</body>
Copy the code
The code diff can view this submission
POST is supposed to be more secure than GET.
Ultimate solution CSRF
After the previous steps, we can summarize the essence of CSRF attack in two aspects:
- The user is accessing the need
Cookie
A web site that records login status - Use the same browser to access links to sites that have constructed malicious requests
In view of these two points, it is not difficult for us to conclude that if we want to prevent being attacked, we can work from two levels.
As the user
As users, we have the option of using an infrequently used browser when visiting an indescribable site. It is best to uninstall the browser after using it…
As a developer
As a developer, we know that CSRF vulnerability only exists when a website requires cookies to record login status. Is there a way to bypass cookies?
Suppose, when we log in successfully, the server does not write cookies but sends a token to the browser. When the front end determines the successful login, it stores the returned token in localStorage (remember it is not a cookie). When the front-end sends a subsequent request to the back-end, the token is fetched from localStorage and written to the request header. The backend verifies the request header to determine the user login status. This seems to bypass the problem of cookies storing login state.
The whole process is as follows:
talk is cheap… , first front-end modification
index.html
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<style>
html.body { padding: 0; margin: 0; display: flex; align-items: center; flex-direction: column; padding-top: 100px; }
</style>
<title>Welcome to use - fat and full</title>
</head>
<body>
<h1>Fat fills the belly, and fat supports the belly</h1>
<h3>welcome<span id="user"></span>. Wallet balance:<span id="money"></span>yuan</h3>
<form>
<label>Transfer to:</label>
<input type="text" name="toUser">
<input type="text" name="money">
<button type="submit">transfer</button>
</form>
<script>
const refresMyMoney = () = > {
$.ajax({
url: '/api/appinfo'.headers: {
token: localStorage.getItem('token')
}
}).done(({errno, data}) = > {
if (errno === 0) {
const {userName, money} = data
$('#money').html(money)
$('#user').html(userName)
} else {
location.href = '/login.html'}})} $('form').on('submit'.e= > {
e.preventDefault()
const formData =
$(e.target).serializeArray()
.map(({name, value}) = > ({[name]: value}))
.reduce((memo, curItem) = >({... memo, ... curItem}) , {}) $.ajax({url: '/api/transfer'.method: 'post'.data: formData,
headers: {
token: localStorage.getItem('token')
}
}).done(resp= > {
if (resp.errno === 0) {
refresMyMoney()
} else {
location.href = '/login.html'}})})window.onload = refresMyMoney
</script>
</body>
</html>
Copy the code
login.html
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
html.body { padding: 0; margin: 0; display: flex; align-items: center; flex-direction: column; padding-top: 100px; }
</style>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<title>Login - Fat belly full</title>
</head>
<body>
<h1>User login, fat belly ~</h1>
<form>
<input
required
name="id"
placeholder="Please enter user ID"
list="users"
/>
<button type="submit">The login</button>
</form>
<script>
$('form').on('submit'.e= > {
e.preventDefault()
const formData =
$(e.target).serializeArray()
.map(({ name, value }) = > ({ [name]: value }))
.reduce((memo, curItem) = >({... memo, ... curItem }), {}) $.post('/api/login', formData).done(resp= > {
if (resp.errno === 0) {
const {data} = resp
localStorage.setItem('token', data);
location.href = '/'
} else {
alert('Login failed ~')}})})</script>
</body>
</html>
Copy the code
And then put in our back-end changes
const Koa = require('koa')
const KoaRouter = require('koa-router')
const path = require('path')
const koaBody = require('koa-body')
const koaStatic = require('koa-static')
const { db, getReqData } = require('./utils')
const PORT = 2333
const app = new Koa()
const router = new KoaRouter()
router.all('/api/login'.ctx= > {
const { body: { id } } = getReqData(ctx)
if (db[id]) {
ctx.body = {
errno: 0.data: id
}
} else {
ctx.body = { errno: Awesome! }
}
})
router.all('/api/appinfo'.ctx= > {
const { token } = getReqData(ctx)
if (db[token]) {
ctx.body = {
errno: 0.data: {
money: db[token],
userName: token
}
}
} else {
ctx.body = { errno: Awesome! }
}
})
router.post('/api/transfer'.ctx= > {
const { body: { toUser, money }, token } = getReqData(ctx)
if(! token) { ctx.body = {errno: Awesome!.errmsg: '30,000 yuan a month, come to work for dad -- Hang Zhou Ma'
}
return
}
db[toUser] += (+money)
db[token] -= (+money)
ctx.body = { errno: 0 }
})
app.use(koaBody())
app.use(router.routes(), router.allowedMethods())
app.use(koaStatic(path.resolve(__dirname, '.. /frond-end')))
app.listen(PORT, () = > {
console.log(`the server is running in ${PORT}`)})Copy the code
Finally, verify a, is still the left and right browsers login quanquan and Zhangsan two users. And successfully transferred 20 yuan to quanquan account.
However, would it be a problem to “accidentally” open an indescribable website again?
We see hacker try to fake a money transfer request, fail, and receive an invitation from his dad to be a lucky child
We have rejected all CSRF attacks so far, and now we can eat hot pot and sing a song.
Afterword.
Thank you for your patience. We have received a series of safety work orders (XSS/CSRF, etc.) this year. As the team is more looking forward to CSRF content, I first wrote this article as an internal sharing. If you like I may also summarize other security work order resolution processes and vulnerability principles. Thank you.
Reference documentation
- CSRF attacks
- About CSRF, what is CSRF and how can it be protected?
- Network Security -CSRF
- Front-end Security Series 2: How do I Prevent CSRF Attacks?
- The SameSite property of the Cookie
- Historical version browser download
- jwt.io