preface
In fact, front-end security is not a lot, but know, at least the back end brothers will not be so tired. This article mainly discusses the following attack modes:
- XSS way
- CSRF way
- Click on the hijacked
I hope you can answer the following questions well after reading the text:
- What are the front-end attacks?
- What is XSS mode? How many types of XSS attacks are there? How do I defend against XSS attacks?
- What is a CSRF attack? How do I defend against CSRF attacks?
- How do I check if a website is secure?
1. XSS way
XSS(Cross-site Scripting) is a code injection attack. Attackers inject malicious codes into the target website, and execute these malicious codes when the target logs in. These scripts can read cookies,session tokens, or other sensitive website messages, carry out phishing scams on users, and even launch worm attacks.
The essence of XSS: Malicious code is unfiltered and mixed in with the site’s normal code; Browsers cannot tell which scripts are trusted, causing malicious scripts to be executed. Because it is executed directly on the user’s terminal, malicious code can directly obtain the user’s information and use this information to attack the website as the user
XSS classification:
- Storage type
- reflective
- The DOM model
1.1 Reflective XSS
When a user clicks on a malicious link, submits a form, or enters a malicious site, the script is injected into the site of the attacker. The Web server will inject scripts, such as an error message, search results, and so on, back to the user’s browser unfiltered.
Attack steps of reflective XSS:
- 1. The attacker constructs a special URL that contains malicious code
- 2. When a user opens a URL with malicious code, the web server takes the malicious code out of the URL and splices it into HTML to return it to the browser
- 3. The user’s browser parses and executes the response, and the malicious code mixed in the response is also executed.
- 4. Malicious code steals user data and sends it to the attacker’s website, or impersonates the user and calls the target website interface to perform the operations specified by the attacker.
Reflective XSS vulnerability is common in the function of URL passing parameters, such as website search, jump and so on. Because users need to take the initiative to open malicious URL to take effect, attackers often combine a variety of means to induce users to click
Reflective XSS can also be triggered by the contents of a POST, but the trigger condition is more stringent (the form submission page needs to be constructed and the user is directed to click), so it is very rare.
Without further ado, let’s take an example
<button id=" BTN "onclick="test()" SRC = "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1136880418, 15 & gp = 0. 2628684495 & FM = JPG" Alt = "" > < / button > Var test = function() {let url = '/welcome? Type =<script>alert(' malicious content ')<\/script>'; location.href = url; } </script> // backend code // server.js const express = require('express'); const app = express(); App.use (express.static(path.join(__dirname))) app.get('/welcome',function(req, res) { Send (' ${req.query.type} '); res.end(); }) app.listen(3000, ()=> { console.log('server is running at port 4000') });Copy the code
If you don’t want cookies from the front end, you can set httpOnly on the back end (although this is not an XSS solution and can only reduce the damage range).
How do I defend against reflective XSS attacks?
The string is encoded and the query parameters of the URL are escaped before being output to the page
App.get ('/welcome',function(req,res) {res.send(' ${encodeURIComponent(req.query.type)} '); })Copy the code
Summary: To put it simply, the front end sends the GET request data to the back end, and the back end returns the results, the URL query parameters must be encoded and output to the page
1.2 the DOM XSS
Dom-type XSS attacks are essentially the result of sloppy front-end javaScript code that inserts untrusted content into the page. Be careful when using apis like.innerhtml,.outerhtml, appendChild, document.write() to insert untrusted data into the page as HTML. Use.innertext instead. .textContent, setAttribute(), etc.
DOM XSS attack steps:
- 1. The attacker constructs special data, which contains malicious code
- 2. The user browser executes malicious codes
- Malicious code steals user data and sends it to the attacker’s website, or impersonates the user’s behavior and calls the target website interface to perform the operations specified by the attacker.
How do I defend against DOM XSS attacks
The key to defending against DOM-type XSS attacks is to escape the input (inline event listeners and link jumps in the DOM can both run strings as code and check their contents)
-
For URL links (such as image SRC attributes), escape them directly using encodeURIComponent
-
Non-url, we can encode it this way
function encodeHtml(str) { return str.replace(/"/g, '" ') .replace(/'/g, '' ') .replace(/</g, '< ') .replace(/>/g, '> '); }Copy the code
DOM TYPE XSS attack, taking out and executing malicious code is completed by the browser side, which belongs to the security vulnerability of front-end javaScript itself
Here’s another example
<div class="form-group"> <label for="comments"> </label> <input class="form-control" type="text" id="comments" /> </div> <div class="form-group"> <input class="btn btn-danger" <input class=" BTN btn-primary" type="button" id="security" value=" I am safe "/> </div> </form> <form> <ul class="list-group"></ul> </form> <script src="/node_modules/jquery/dist/jquery.js"></script> <script> // DOM XSS attack $('#comment ').click(function () {let commend = $('#comment ').val(); console.log(commend); if ($.trim(commend)) { $('.list-group').append(`<li class="list-group-item">${commend}</li>`); $('#comments').val(''); Function encodeHtml(STR) {return str.replace(/"/g, '"); ') .replace(/'/g, '' ') .replace(/</g, '< ') replace(/>/g, '> ') } $('#security').click(function () { let commend = $('#comments').val(); // they can be used to escape information in commend mode. // They can be used to escape information in commend mode. If ($.trim(commend)) {$('.list-group').append(' <li ') class="list-group-item">${encodeHtml(commend)}</li>`); $('#comments').val(''); } }) </script>Copy the code
Summary: Do not trust user input. If you want to insert HTML on the page, you must escape it before inserting it. You can use encodeURIComponent for urls and the above method for non-urls
1.3 Storage XSS
Malicious scripts are permanently stored on the target server. When the browser requests data, the script is sent back from the server and executed, with greater impact than reflective and DOM XSS. Storage XSS attacks are still caused by poor data filtering;
- Data is not filtered when the front-end submits data to the server.
- The server does not filter the received data before storing it
- The front end requests data from the server without filtering output.
Storage XSS attack steps
- 1. The attacker submits malicious code to the database of the target website
- 2. When the user opens the target website, the website server takes out the malicious code from the database, splices it into HTML and returns it to the browser
- 3. The user’s browser parses and executes the response, and the malicious code mixed in the response is also executed.
- 4. Malicious code steals user data and sends it to the attacker’s website, or impersonates the user and calls the target website interface to perform the operations specified by the attacker
This attack is common on sites with user-saved data, such as forum posts, product reviews, and private messages.
How do I defend against stored XSS attacks
- 1. The front-end data should be escaped/filtered before being transmitted to the server (the situation of capturing and modifying data cannot be prevented).
- 2. The server escapes or filters the received data before storing it in the database
- 3. The front end receives the data from the server and escapes/filters it before displaying it to the page
Here’s an example, maybe a little longer, of using a cookie to log in
HTML <div class="panel-heading"> <h3> </h3> </div> <ul class="list-group"></ul> <form> <div </label> <input class="form-control" type="text" id="comments" /> </div> <div class="form-group"> <input class=" BTN btn-primary" type="button" id="publish" value=" post "/> </div> </form> <script SRC = "/ node_modules/jquery/dist/jquery js" > < / script > < script > / type/stored XSS attacks $(document). Ready (function () {/ / for posts list function getList() { $.get('/getComments2').then(res => { if (res.code === 0) { let lists = ''; $.each(res.comments, (index, comment) => {// The front-end receives the data from the server and escapes it before displaying it to the page. console.log(comment.content , content) lists += `<li class="list-group-item"><span>${comment.username}:</span> ${content}</li>`; }); $('.list-group').html(lists); }}); }; getList(); Function encodeHtml(STR) {return str.replace(/"/g, '"); ') .replace(/'/g, '' ') .replace(/</g, '< ') .replace(/>/g, '> '); } function decodeHtml(STR) {return str.replace(/ "; /g, '\"') .replace(/' /g, '\'') .replace(/< /g, '<') .replace(/> /g, '>'); } $('#publish').click(function () { let comment = $('#comments').val(); $. Post ('/addComment2', {comment: EncodeHtml (comment)}). Then (res => {if (res.code === 1) {// Go to location.href = '/login.html'; } else {// Get a new post list getList(); $('#comments').val(''); }}); }); }); </script> // front-end login. HTML <div class=" paneling-heading "> <h3> Login </h3> </div> <form onsubmit="return false"> <div Class ="form-group"> <label for="username"> </label> <input class="form-control" type="text" id="username" /> </div> <div class="form-group"> <label for="password"> </label> <input class="form-control" type="text" id="password" /> </div> <div class="form-group"> <input class=" BTN bTN-primary "type="submit" id="login" value=" login" /> </div> </form> <script src="/node_modules/jquery/dist/jquery.js"></script> <script> $(document).ready(function () { $('#login').click(function () { let username = $('#username').val(); let password = $('#password').val(); $.post('/ API /login', {username, password}). Then (res => {if (res.code === 0) {// login success page location.href = '/ welcome? Type =<script>alert(' malicious content ')<\/script> '; } else {// Failed to log in. Href = '/error? Type =<script>alert(' malicious content ')<\/script> '; }})}) </script> // backend server.js const express = require('express'); const app = express(); const path = require('path'); const bodyParser = require('body-parser'); const cookieParser = require('cookie-parser'); App.use (express.static(path.join(__dirname, 'SRC '))); app.use(express.static(path.join(__dirname, '.. / '))); Use (bodyParser.urlencoded({extended: true})); //req.cookie[XXX] Get cookie app.use(cookieParser()); Let userList = [{username: 'zs', password: '123456'}, {username: 'star', password: 'star'}]; let SESSION_ID = 'connect.sid'; let session = {}; App.post ('/ API /login', (req, res) => {let {username, password} = req.body; let user = userList.find(item => item.username === username && item.password === password); Const cardId = math.random () + date.now (); const cardId = math.random () + date.now (); session[cardId] = { user }; res.cookie(SESSION_ID, cardId); res.json({ code: 0 }); } else { res.json({ code: 1, error: `${username} does not exist or password mismatch` }); }}); / / 1. Reflective XSS attacks: http://localhost:3000/error? Type =<script>alert(' malicious content ')</script> app.get('/error', function (req, res) {res.send(' ${req.query.type} '); }); App.get ('/welcome', function (req, res) {res.send(' ${encodeURIComponent(req.query.type)} '); Res.send (' ${encodeURIComponent(req.query.type)} '); }); Let comments2 = [{username: 'ZS ', content:' I am ZS '}, {username: 'hw', content: 'I am hw'}, {username:' Hw ', content: 'I am hw'}, {username: Get ('/getComments2', function (req, res) {res.json({code: 0, comments: comments2 }); }); function encodeHtml(str) { return str.replace(/"/g, '" ') .replace(/'/g, '' ') .replace(/</g, '< ') .replace(/>/g, '> '); } app.post('/addComment2', function (req, Res) {//cardId (req.cookies[SESSION_ID]) let info = session[req.cookies[SESSION_ID]]; If (info) {// let username = info.user.username; // The server escapes/filters comments2.push({username, content: encodeHtml(req.body.comment)}) after receiving data before storing it in the database; res.json({ code: 0, comments: comments2 }); } else { res.json({ code: 1, error: 'user not logged in.' }); }}); app.listen(3000);Copy the code
Conclusion:
- 1. If malicious scripts are not converted, they are stored in the background. Any user visiting this page will execute a malicious script
- 2. Add string filtering:
- Front-end input filtering
- Filter when the server is added
- Filter when front-end output
1.4 XSS security problems in JSONP
Function jsonp({url, params, callback}) {return new Promise((resolve, resolve) reject) => { let script = document.createElement('script'); params = JSON.parse(JSON.stringify(params)); let arrs = []; for (let key in params) { arrs.push(`${key}=${params[key]}`); } arrs.push(`callback=${callback}`); script.src = `${url}? ${arrs.join('&')}`; document.body.appendChild(script); console.log(callback) window[callback] = function (data) { resolve(data); document.body.removeChild(script); }})} / / if I so call, security issues will XSS the json ({url: 'http://localhost:3000/say', params: {wd: 'I Love you'}, the callback: 'alert(1)' }).then(data => { alert(data.username) console.log(data) })Copy the code
The simple answer is to set the parameters of the jSONP callback function to malicious code
Common preventive actions
-
Mark important cookies as HTTP only so that the document.cookie statement in Javascript cannot fetch cookies.
-
Allow the user to enter only the callback parameters we expect
let { callback} = req.query; if(callback === 'show') { callback = 'show'; } else { res.end('error') } Copy the code
-
Filter or remove special Html tags
< script >, < iframe >, etcCopy the code
2. CSRF way
Cross-site request-forgery (CSRF) : An attacker induces the victim to access a third-party website and then sends cross-domain requests to the attacked website. Using the victim in the attacked website has obtained the registration certificate, bypassing the background user authentication, to impersonate the user to perform a certain operation on the attacked website.
Typical CSRF attack flow
- 1. The victim logs in to site A and retains the login credentials (cookies).
- 2. The attacker induces the victim to visit site B
- 3. Site B sends A request to site A. By default, the browser carries site A’s Cookie.
- 4. After receiving the request, site A verifies the request and confirms that it is the victim’s credential. Site A mistakenly believes that the request is sent by an innocent victim.
- 5. Site A executes site B’s request on behalf of the victim
- The attack is complete. The attacker impersonates the victim to complete the attack without the victim’s knowledge
The characteristics of CSRF
- 1. Attacks are usually launched on third-party websites, such as site B in the figure. Site A cannot prevent attacks.
- 2. The attack uses the login credentials of the victim on the attacked website to submit operations as the victim; It doesn’t get cookies (cookies have same-origin policy)
- 3. Cross-site requests can be made in various ways: image urls, hyperlinks,CORS, Form submissions, etc. (do not click on links from unknown sources)
CSRF Attack defense
1. Add a verification code (Poor experience)
The verification code can defend against CSRF attacks, but it is impossible for us to need the verification code for every interaction, otherwise the user experience will be very bad. However, we can add the verification code in the operation of transfer and transaction to ensure the security of our account.
2. Determine the source of the request: check the Referer(not secure,Referer can be changed)
Referer can be used as an auxiliary means to determine whether the source of the request is secure, but since Referer itself can be modified, it cannot be relied on alone
3. Use tokens (mainstream)
The CSRF attack succeeds because the server mistook the request sent by the attacker for the user’s own request. Then we can require all user requests to carry a Token that a CSRF attacker cannot obtain. The server verifies that the request carries the correct Token to distinguish the normal request from the attack request. It’s like a captcha, but the user doesn’t know it
Steps:
- The server generates a token for the user, encrypts it, and sends it to the user.
- Users need to carry this token when submitting requests
- The server verifies that the token is correct
Let me give you an example
//login.html 登录页
<div class="panel-heading">
<h3>登录</h3>
</div>
<div class="panel-body">
<form onsubmit="return false">
<div class="form-group">
<label for="username">用户名</label>
<input class="form-control" type="text" id="username" />
</div>
<div class="form-group">
<label for="password">密码</label>
<input class="form-control" type="text" id="password" />
</div>
<div class="form-group">
<input class="btn btn-primary" type="submit" id="login" value="登录" />
</div>
</form>
</div>
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script>
$('#login').click(function () {
let username = $('#username').val();
let password = $('#password').val();
console.log(username,password)
$.post('/api/login', {
username,
password
}).then(res => {
if(res.code === 0) {
// 登录成功页
location.href = '/safe3.html'
} else {
}
})
})
</script>
//safe.html 显示页面
<div class="panel-heading">
<h3>转账</h3>
<h5>
<p>用户:<span id='username'></span></p>
<p>余额:<span id='account'></span></p>
</h5>
</div>
<div class="panel-body">
<form onsubmit="return false">
<div class="form-group">
<label for="payee">收款人</label>
<input class="form-control" type="text" id="payee" />
</div>
<div class="form-group">
<label for="amount">金额</label>
<input class="form-control" type="text" id="amount" />
</div>
<div class="form-group">
<input class="btn btn-primary" type="submit" id="transfer" value="转账" />
</div>
</form>
</div>
<button>
<a href="/fish3.html">钓鱼连接</a>
</button>
</body>
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script>
function getUserInfo () {
$.get('/api/userinfo').then(res => {
if(res.code === 1) {
location.href = '/login.html';
} else {
let { username , account } = res.info;
$('#username').text(username);
$('#account').text(account);
}
})
}
getUserInfo();
$('#transfer').click(function() {
let payee = $('#payee').val(); // 收款人
let amount = $('#amount').val(); // 转账金额
// 生成 token
let token = document.cookie.match(/connect.sid=([^;]*)/) || [];
$.post('/api/transfer3', {
payee,
amount,
token : 'my_token_' + token[1]
}).then(res => {
if(res.code === 0) {
// 重新获取用户信息
getUserInfo();
} else {
}
})
})
</script>
//钓鱼页面
<img width="600"
src="http://b4-q.mafengwo.net/s12/M00/FE/AE/wKgED1v772uAa_VkAA3UNOJhbkE35.jpeg?imageMogr2%2Fthumbnail%2F1360x%2Fstrip%2Fquality%2F90" />
<p>你的钱不安全了~</p>
<a href="http://localhost:3001/safe.html">返回查看余额</a>
<iframe src="http://localhost:3002/fake1.html" style="display: none"></iframe>
//恶意站点
// fake1.html
<body>
<form name="sneak1" action="http://localhost:3001/api/transfer3" method="post">
<input type="text" name="payee" value="loki" />
<input type="text" name="amount" value="2000" />
</form>
</body>
<script>
window.onload = function () {
document.sneak1.submit(); //有验证码的情况
}
</script>
//后端 server1.js
const express = require('express');
const app = express();
const path = require('path');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
//将参数转换成对象
app.use(bodyParser.urlencoded({ extended: true }));
//req.cookie[xxx] 获取cookie
app.use(cookieParser());
//设置路径
app.use(express.static(path.join(__dirname, 'src')));
//用户列表
let userList = [{ username: 'hw', password: '123456', account: 1000 }, { username: 'loki', password: 'loki', account: 100000 }];
let SESSION_ID = 'connect.sid';
let session = {};
//登录接口
app.post('/api/login', (req, res) => {
let { username, password } = req.body;
let user = userList.find(item => item.username === username && item.password === password);
if (user) {
//用户登录后,给一个标识(cookie登录)
const cardId = Math.random() + Date.now();
session[cardId] = { user };
res.cookie(SESSION_ID, cardId);
res.json({ code: 0 });
} else {
res.json({ code: 1, error: `${username} does not exist or password mismatch` });
}
});
//获取信息
app.get('/api/userinfo', (req, res) => {
let info = session[req.cookies[SESSION_ID]];
if (info) {
//用户已经登录
let username = info.user.username;
res.json({ code: 0, info: { username : username, account: info.user.account} });
} else {
res.json({ code: 1, error: 'user not logged in.' });
}
})
//转账前, 先验证 token
app.post('/api/transfer3', (req, res) => {
let info = session[req.cookies[SESSION_ID]];
if(info) {
// 用户已经登录
let { payee , amount , token } = req.body;
console.log(token);
console.log('my_token_' + req.cookies[SESSION_ID]);
// 校验 token
if(token === 'my_token_' + req.cookies[SESSION_ID] && Number(amount)) {
// token 正确
let username = info.user.username;
userList.forEach(user => {
if(user.username === username) {
user.account -= amount;
}
if(user.username === payee) {
user.account += amount;
}
});
res.json({ code : 0})
}
} else {
res.json({code : 1, error : 'user not logged in'})
}
})
app.listen(3001, () => {
console.log('Server is running at port 3001')
})
//后端 server2.js
const express = require('express');
const app = express();
const path = require('path');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
//将参数转换成对象
app.use(bodyParser.urlencoded({ extended: true }));
//req.cookie[xxx] 获取cookie
app.use(cookieParser());
//设置路径
app.use(express.static(path.join(__dirname, 'dest')));
app.listen(3002, () => {
console.log('Server is running at port 3002')
})
Copy the code
Conclusion:
- After logging in, go to the safe.html transfer page
- If you click the phishing link on the transfer page, you will be attacked. The third-party website will carry the user’s cookie, so it can bypass the authentication and use the identity of the attacked to do some operations specified by the attacker.
4. Samesite Cookie properties
To address this problem at its source, Google drafted a draft to improve the HTTP protocol by adding a Samesite attribute to the set-cookie response header, which indicates that the Cookie is a “same-site Cookie.” A same-site Cookie can only be used as a first-party Cookie, not a third-party Cookie. Samesite has two property values: Strict and Lax. It is simple to deploy and can effectively defend against CSRF attacks, but compatibility problems exist
Samesite=Strict
Samesite=Strict is known as Strict mode, indicating that the Cookie cannot be used as a third-party Cookie under any circumstances and is capable of preventing all CSRF attacks. At this point, we launch any request to site A under site B, and the Cookie of site A will not be included in the Cookie request header.
Security scanning tool
In the absence of a security department, or our front end can do a rough security scan first
-
w3af
w3af -
Arachni
Arachni
The last
Refer to the article
W3af installation and Arachni installation and introduction