primers
Some time ago, the department has been recruiting front-end development in order to make our website
-
faster
-
A more stable
-
A more secure
I also briefly asked candidates about front-end security issues, including knowledge of XSS attacks. Here is our interview process.
Me: XXX do you know anything about XSS attacks?
Candidate: XSS knows, just… But I feel like we don’t need to do XSS protection anymore with modern frameworks like React. The framework already does this for us.
Me: Does using a framework like React guarantee that XSS attacks won’t happen?
At this point the candidates began to falter. Readers, silently ask yourself: Does react really prevent XSS attacks? In order to make you better grasp the knowledge related to XSS attack, the author will mainly focus on the following points to you.
- What is an XSS attack
- Example of XSS attack
- XSS attacks in the React framework
- XSS attack defense
- conclusion
What is an XSS attack
Cascading Style Sheets (CSS). XSS is a computer security vulnerability in Web applications. It allows malicious Web users to embed code into pages that are intended for use by other users.
XSS attack classification
-
reflective
Reflective XSS simply “reflects” user input data back to the browser. In other words, hackers often need to trick users into “clicking” on a malicious link in order to succeed. Reflective XSS is also called “non-persistent XSS”.
-
Storage type
Storage XSS “stores” user-entered data on the server side (the database). Storage XSS is often called Persistent XSS.
-
Dom Based XSS
Dom Based XSS is not divided according to “whether data exists on the server side”. Dom Based XSS is also reflective XSS in effect. It is separate because DOM Based XSS is formed for a special reason — XSS formed by modifying the DOM node of the page.
Example of XSS attack
Now that we are familiar with what an XSS attack is and the classification of an XSS attack, let’s be a hacker! Next, we will simulate each of the three types of attacks. I recommend you clone my sample code first.
The code used in this article is in the repository, and the directory is Security/XSS
Running instance
git clone https://github.com/chenshengshui/Web-Frontend-Study-Map.git
Copy the code
cd security
Copy the code
npm install
Copy the code
npm run xss
Copy the code
Example of reflective XSS
Example demonstrates
Open a browser and enter the threat website: http://localhost:3000/non-persistent-xss.html
The source code parsing
The target site prints the parameters entered by the user directly to the page:
/* * Target website */
const express = require('express');
const app = express();
app.use(express.static('./xss'));
app.get('/'.function(req, res) {
res.setHeader('X-XSS-Protection'.0); // This is done to turn off XSS protection in modern browsers
res.send('Good morning' + req.query['name']);
});
app.listen(3000);
Copy the code
Threatening websites:
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<a href="http://localhost:3000/? name=''"
>Click me to go to the target website</a
>
</body>
</html>
Copy the code
The threat site induces the user to click on the link, but the link contains the attack script, and if we go from the threat site to our target site, we will find that the alert(‘ XSS ‘) is executed on the target site. A look at the source code shows that the script tag in the parameter has been written to the page, which is clearly not what the developers want to see.
Storage XSS example
A classic scenario for a stored XSS attack is when a hacker writes a blog post that contains malicious Javascript code. After the post is published, all users accessing the blog post will execute the malicious Javascript code in their browsers. Malicious scripts are saved to the server, so this TYPE of XSS attack is called “stored XSS”.
Example demonstrates
Open a browser, enter the web site: http://localhost:3000/persistent-xss.html, in the text box, enter the following content:
<div style="color: red">Welcome to my blog, I'm WaterMan, follow me if you like!</div>
<script>
alert('Haha, I stole your token, be careful next time.' + document.cookie);
</script>
Copy the code
After clicking Save, our cookie will be stolen.
Source code analysis first look at our front-end code, So easy, there is only a Textarea and a submit button, click the submit button will be the user input content submitted to the background, no problem, we usually is So developed.
document.getElementById('submit').onclick = function() {
var content = document.getElementById('textarea').value;
var url = 'http://localhost:3000/postArticle';
easyRequest()
.post(url, {
content: content
})
.then(res= > {
alert(res.message);
window.open('http://localhost:3000/persistent-xss');
});
};
function easyRequest() {
return {
post: function(url, data) {
return fetch(url, {
body: JSON.stringify(data),
cache: 'no-cache'.credentials: 'same-origin'.headers: {
'content-type': 'application/json'
},
method: 'POST'.mode: 'cors'.redirect: 'follow'.referrer: 'no-referrer'
}).then(response= >response.json()); }}; }Copy the code
Looking at our back-end code, the back-end code is also extremely simple, saving our commit to a file, which is used to simulate a regular database.
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const fs = require('fs');
app.use(express.static('./xss'));
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
// parse application/json
app.use(bodyParser.json());
app.use(function(req, res, next) {
// Simulate setting user tookie
res.setHeader('Content-Type'.'text/html; charset=utf-8');
res.cookie('token'.'zkskskdngqkkkgn245tkdkgj');
next();
});
app.get('/'.function(req, res) {
res.setHeader('X-XSS-Protection'.0);
res.send('Good morning' + req.query['name']);
});
app.get('/persistent-xss'.function(req, res) {
fs.readFile('./xss/data.txt'.function(err, data) {
if (err) {
return res.send(err);
}
res.send(data);
});
});
app.post('/postArticle'.function(req, res) {
const content = req.body.content + '\n';
fs.writeFile('./xss/data.txt', content, { flag: 'w' }, function(err) {
if (err) {
return res.send({ status: 500.message: err.message });
}
res.send({ status: 200.message: 'Saved successfully' });
});
});
app.listen(3000);
Copy the code
In this way, as long as any one user access article details http://localhost:3000/persistent-xss, his tooken will be obtained.
DOM Based XSS
XSS formed by modifying the DOM node of the page is called DOM Based XSS. Look directly at the following code
<body>
<div id="content"></div>
<input type="text" id="text" value="" style="width: 500px" />
<input type="button" id="submit" value="write" />
</body>
<script>
document.getElementById('submit').onclick = function() {
var str = document.getElementById('text').value;
document.getElementById('content').innerHTML =
"<a href='" + str + "'>testLink</a>";
};
</script>
Copy the code
When you click the write button, a hyperlink is inserted into the current page with the address of the contents of the text box. In this case, the “write” button’s onclick event modifies the PAGE’s DOM node, using innerHTML to write a piece of user data into the page as HTML. This creates DOM Based XSS. Construct the following data
‘onclick=alert(/ XSS /) //
<a href=' ' onclick=alert(/xss/)//'> testLink</a>
Copy the code
Close the first href single quote with a single quote, insert an onclik event, and comment out the second single quote. Click the newly generated link and the script will be executed. So we launched a DOM Based XSS attack. You can also start the project, open the web site http://localhost:3000/dom-based-xss.html experience.
XSS attacks in the React framework
The React framework is actually a lot safer than writing code using Jquery, because React already does a lot of work for us. Here’s a quick overview of what React does.
React:
It is safe to embed user input in JSX:
const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;
Copy the code
By default, React DOM escapes any values embedded in JSX before rendering them. Thus it ensures that you can never inject anything That’s not explicitly written in your application. Everything is in line to a string before being rendered. This helps prevent XSS (cross-site-scripting) attacks.
Embedding user input directly in React is safe because the React Dom encodes values in JSX before rendering. This ensures that you don’t inject any code that threatens your application. Everything is converted to a string before rendering, which helps defend against XSS attacks.
So how does this HTML entity code work?
& becomes &
< becomes <
> becomes >
" becomes "
' becomes '
Copy the code
React doesn’t convert everything all at once, otherwise we wouldn’t be able to render properlyThe defense mechanism of XSS-Fitler is described in more detail here.
Let’s go back to the React XSS attack. React API dangerouslySetInnerHTML is used to render HTML.
DangerouslySetInnerHTML is React’s replacement for using innerHTML in the browser dom. in general, Setting HTML from code is risky because it’s easy to expose your users to a cross-site scripting (XSS) attack. So, you can set HTML directly from React, but you have to type out dangerouslySetInnerHTML and pass an object with a __html key, To remind yourself that it’s dangerous. For example:
function createMarkup() {
return { __html: 'First · Second' };
}
function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />;
}
Copy the code
That said, dangerouslySetInnerHTML is a replacement for innerHtml, but it still has security issues.
Run the example
/security/react-xss
cd security/react-xss
Copy the code
npm install
Copy the code
npm start
Copy the code
Source code analysis
class Chat extends Component {
state = {
input: ' '.result: ' '
};
onChange = e= > {
this.setState({
input: e.target.value
});
};
onSubmit = (a)= > {
this.setState({
result: this.state.input
});
};
render() {
return(<div styleName="container"> <div styleName=" MSG "> <textarea onChange={this.onChange} placeholder=" Input here "/> <button OnClick ={this.onsubmit}> Submit </button> <div dangerouslySetInnerHTML={{__html: this.state.result}} /> </div> </div>); }}Copy the code
When dangerouslySetInnerHTML is used in React, XSS attacks may occur.
React is not foolproof. How can we improve the security of our website? That brings me to the focus of this article, where I’ll cover some of the tools you can use to protect against XSS attacks and make your site more secure.
XSS attack defense
XSS defense is complex. Here are a few of the XSS defenses I’ve learned, and if there are others I haven’t mentioned, please feel free to add in the comments section. Our goal is to make our site more secure and enhance your capabilities.
Next, XSS attack protection is mainly discussed around the following points:
- Four two money – HttpOnly
- Input inspection
- Output check
- Handle rich text correctly
Four two money – HttpOnly
What is HttpOnly?
HttpOnly is a property that sets whether cookies can be read by javasript scripts. The browser will prevent the page’s Javascript from accessing cookies with the HttpOnly property.
Returning to the example code, look at the following syntax for setting cookies:
const express = require('express');
const app = express();
app.use(function(req, res, next) {
res.setHeader('Content-Type'.'text/html; charset=utf-8');
// Simulate setting user tookie
res.cookie('token'.'zkskskdngqkkkgn245tkdkgj');
next();
});
Copy the code
The cookie is not set to true, so we can use document.cookie directly to obtain the token value. The following figure
Cookie details can be viewed in the Application TAB of the console
To prevent hackers from accessing cookie information through Javascript scripts, HttpOnly can be enabled.
const express = require('express');
const app = express();
app.use(function(req, res, next) {
res.setHeader('Content-Type'.'text/html; charset=utf-8');
// Simulate setting user tookie
res.cookie('token'.'zkskskdngqkkkgn245tkdkgj', { httpOnly: true });
next();
});
Copy the code
Native NodeJS can be set up like this without using frameworks such as Express
response.setHeader('Set-Cookie'.'token=zkskskdngqkkkgn245tkdkgj; HttpOnly');
Copy the code
Strictly speaking, HttpOnly is not designed to counter XSS; HttpOnly addresses post-XSS cookiehijacking attacks.
Cookie set HttpOnly, Secure property can effectively prevent XSS attacks, x-frame-options response header can avoid click hijacking.
Properties:
-
Secure When this parameter is set to true, the created Cookie is transmitted to the server in a Secure manner (SSL). That is, the Cookie can only be transmitted to the server in an HTTPS connection for session authentication, but not in an HTTP connection. So you can’t steal the specific contents of the Cookie.
-
HttpOnly Attribute If the HttpOnly attribute is set in cookies, programs (JS scripts, applets, etc.) cannot read Cookie information. In this way, XSS attacks are effectively prevented.
-
X-frame-options Response header X-frame-options Http response header indicates whether a page is allowed to display tags in < Frame >,
/ / set the cookie
response.setHeader(
'Set-Cookie'.'JSESSIONID=' + sessionid + '; Secure; HttpOnly'
); / / set the Secure; HttpOnly
response.setHeader('x-frame-options'.'SAMEORIGIN'); / / set x - frame - the options
Copy the code
Input inspection
Common Web vulnerabilities, such as XSS and SQL Injection, require the attacker to construct some special characters. These special characters may not be used by normal users, so the existence of input check is necessary.
Input check, often used for format check, simply means adding a whitelist to the information entered by the user. For example, the user name required to fill in the website registration will be required as a combination of letters and numbers. For example, “waterMan1” is a valid username, while “waterMan$^” is not allowed. Input checking must be implemented in server-side code because it can be easily circumvented by attackers using javascript on the client side. That as a front end of us, is it ok to put both hands into the pocket? In the current era of NodeJS, this is often simplistic. We can perform input checks on the Node side to make sure our site is secure. To say the least, we add input check in the client code, can also block most of the normal users who misoperate, so as to save server resources, but also can improve the threshold of hacker attack.
So how do we do an input check?
Rough practice
The most effective way to check input is to filter or encode special characters such as
& becomes &
< becomes <
> becomes >
" becomes "
' becomes '
Copy the code
However, due to the lack of context, such violent processing, which is indeed safe, can also make some normal input difficult to understand, and make the user crazy.
Such as:
1 + 2 < 4 becomes 1 + 2 < 4
Copy the code
This is obviously not what users want.
More intelligent XSS-filter
XSS Filter obtains variables when users submit data, performs XSS check, and performs intelligent input check to match XSS features. It is very difficult to design such an intelligent XSS Filter library, and we will describe the implementation of an XSS-filter library in detail in the next article. Welcome to witness the process of an XSS-filter library from scratch with me.
Output check
Output checking and input checking do the same thing, but output checking is simpler. Output checking can be context-specific, but output checking requires a lot of work. The React framework already does a lot of output checking. In fact, the output check can also be uniformly intercepted at the interface, so as to be consistent with the input check.
Processing rich text
Processing rich text is the same as input checking. We add a whitelist to the input information, such as tags in rich text, but
conclusion
It’s the end again, and I’m glad you guys made it to the end. XSS defense is worthy of further study. In a word, I hope we can learn from my article. Finally, have a great weekend!
@Author: WaterMan