上QQ阅读APP看书,第一时间看更新
How to do it...
We will now see how to create our XSS component:
- Create an XSS component:
import React, { Component } from 'react';
// Let's suppose this response is coming from a service and have
// some XSS attacks in the content...
const response = [
{
id: 1,
title: 'My blog post 1...',
content: '<p>This is <strong>HTML</strong> code</p>'
},
{
id: 2,
title: 'My blog post 2...',
content: `<p>Alert: <script>alert(1);</script></p>`
},
{
id: 3,
title: 'My blog post 3...',
content: `
<p>
<img onmouseover="alert('This site is not secure');"
src="attack.jpg" />
</p>
`
}
];
// Let's suppose this is our initialState of Redux
// which is injected to the DOM...
const initialState = JSON.stringify(response);
class Xss extends Component {
render() {
// Parsing the JSON string to an actual object...
const posts = JSON.parse(initialState);
// Rendering our posts...
return (
<div className="Xss">
{posts.map((post, key) => (
<div key={key}>
<h2>{post.title}</h2>
<p>{post.content}</p>
</div>
))}
</div>
);
}
}
export default Xss;
File: src/components/Xss/Xss.js
- If you render this component, you will see something like this:
- As you can see, by default, React prevents us from injecting HTML code directly into our components. It is rendering the HTML as a string. This is good, but sometimes we need to insert HTML code in our components.
- Implementing dangerouslySetInnerHTML: This prop probably scares you a little bit (maybe because it explicitly says the word danger!). I'm going to show you that this prop is not too bad if we know how to use it securely. Let's modify our previous code, and we are going to add this prop to see how the HTML is rendering it now:
import React, { Component } from 'react';
// Let's suppose this response is coming from a service and have
// some XSS attacks in the content...
const response = [
{
id: 1,
title: 'My blog post 1...',
content: '<p>This is <strong>HTML</strong> code</p>'
},
{
id: 2,
title: 'My blog post 2...',
content: `<p>Alert: <script>alert(1);</script></p>`
},
{
id: 3,
title: 'My blog post 3...',
content: `
<p>
<img onmouseover="alert('This site is not secure');"
src="attack.jpg" />
</p>
`
}
];
// Let's suppose this is our initialState of Redux
// which is injected to the DOM...
const initialState = JSON.stringify(response);
class Xss extends Component {
render() {
// Parsing the JSON string to an actual object...
const posts = JSON.parse(initialState);
// Rendering our posts...
return (
<div className="Xss">
{posts.map((post, key) => (
<div key={key}>
<h2>{post.title}</h2>
<p><strong>Secure Code:</strong></p>
<p>{post.content}</p>
<p><strong>Insecure Code:</strong></p>
<p
dangerouslySetInnerHTML={{ __html: post.content }}
/>
</div>
))}
</div>
);
}
}
export default Xss;
File: src/components/Xss/Xss.js
- Our site should now look like this:
- It is interesting, probably you thought that the content of "My blog post 2" will fire an alert in the browser but does not. If we inspect the code the alert script is there.
- Even if we use dangerouslySetInnerHTML, React protects us from malicious scripts injections, but it is not secure enough for us to relax on the security aspect of our site. Now let's see the issue with My blog post 3 content. The code <img onmouseover="alert('This site is not secure');" src="attack.jpg" /> is not directly using a <script> tag to inject a malicious code, but is using an img tag with an event (onmouseover). So, if you were happy about React's protection, we can see that this XSS attack will be executed if we move the mouse over the image:
- Removing XSS attacks: This is kind of scary, right? But as I said at the beginning of this recipe, there is a secure way to use dangerouslySetInnerHTML and, yes, as you may be thinking right now, we need to clean our code of malicious scripts before we render it with dangerouslySetInnerHTML. The next script will take care of removing <script> tags and events from tags, but of course, you can modify this depending on the security level you want to have:
import React, { Component } from 'react';
// Let's suppose this response is coming from a service and have
// some XSS attacks in the content...
const response = [
{
id: 1,
title: 'My blog post 1...',
content: '<p>This is <strong>HTML</strong> code</p>'
},
{
id: 2,
title: 'My blog post 2...',
content: `<p>Alert: <script>alert(1);</script></p>`
},
{
id: 3,
title: 'My blog post 3...',
content: `
<p>
<img onmouseover="alert('This site is not secure');"
src="attack.jpg" />
</p>
`
}
];
// Let's suppose this is our initialState of Redux
// which is injected to the DOM...
const initialState = JSON.stringify(response);
const removeXSSAttacks = html => {
const SCRIPT_REGEX = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
// Removing the <script> tags
while (SCRIPT_REGEX.test(html)) {
html = html.replace(SCRIPT_REGEX, '');
}
// Removing all events from tags...
html = html.replace(/ on\w+="[^"]*"/g, '');
return {
__html: html
}
};
class Xss extends Component {
render() {
// Parsing the JSON string to an actual object...
const posts = JSON.parse(initialState);
// Rendering our posts...
return (
<div className="Xss">
{posts.map((post, key) => (
<div key={key}>
<h2>{post.title}</h2>
<p><strong>Secure Code:</strong></p>
<p>{post.content}</p>
<p><strong>Insecure Code:</strong></p>
<p
dangerouslySetInnerHTML=
{removeXSSAttacks(post.content)}
/>
</div>
))}
</div>
);
}
}
export default Xss;
File: src/components/Xss/Xss.js
- If we look at the code now, we will see that now our render is more secure:
- The problem with JSON.stringify: So far, we have learned how to inject HTML code into a React component with dangerouslySetInnerHTML, but there is another potential security issue using JSON.stringify. If we have an XSS attack (<script> tag inside the content) in our response and then we use JSON.stringify to convert the object to a string, the HTML tags are not encoded. That means that if we inject the string into our HTML (like Redux does with the initial state), we will have a potential security issue. The output of JSON.stringify(response) is this:
[
{"id":1,"title":"My blog post 1...","content":"<p>This is <strong>HTML</strong> code</p>"},
{"id":2,"title":"My blog post 2...","content":"<p>Alert: <script>alert(1);</script></p>"},
{"id":3,"title":"My blog post 3...","content":"<p><img onmouseover=\"alert('This site is not secure');\" src=\"attack.jpg\" /></p>"}
]
- As you can see, all the HTML is exposed without any encoding characters, and that is a problem. But how we can fix this? We need to install a package called serialize-javascript:
npm install serialize-javascript
- Instead of using JSON.stringify, we need to serialize the code like this:
import serialize from 'serialize-javascript';
// Let's suppose this response is coming from a service and have
// some XSS attacks in the content...
const response = [
{
id: 1,
title: 'My blog post 1...',
content: '<p>This is <strong>HTML</strong> code</p>'
},
{
id: 2,
title: 'My blog post 2...',
content: `<p>Alert: <script>alert(1);</script></p>`
},
{
id: 3,
title: 'My blog post 3...',
content: `<p><img onmouseover="alert('This site is not
secure');" src="attack.jpg" /></p>`
}
];
// Let's suppose this is our initialState of Redux which is
// injected to the DOM...
const initialState = serialize(response);
console.log(initialState);
- The output of the console is as follows:
[
{"id":1,"title":"My blog post 1...","content":"\u003Cp\u003EThis is \u003Cstrong\u003EHTML\u003C\u002Fstrong\u003E code\u003C\u002Fp\u003E"},
{"id":2,"title":"My blog post 2...","content":"\u003Cp\u003EAlert: \u003Cscript\u003Ealert(1);\u003C\u002Fscript\u003E\u003C\u002Fp\u003E"},
{"id":3,"title":"My blog post 3...","content":"\u003Cp\u003E\u003Cimg onmouseover=\"alert('This site is not secure');\" src=\"attack.jpg\" \u002F\u003E\u003C\u002Fp\u003E"}
]
- Now that we have our code with HTML entities (encoded) instead of directly having HTML tags, and the good news is that we can use JSON.parse to convert this string again into our original object. Our component should look like this:
import React, { Component } from 'react';
import serialize from 'serialize-javascript';
// Let's suppose this response is coming from a service and have
// some XSS attacks in the content...
const response = [
{
id: 1,
title: 'My blog post 1...',
content: '<p>This is <strong>HTML</strong> code</p>'
},
{
id: 2,
title: 'My blog post 2...',
content: `<p>Alert: <script>alert(1);</script></p>`
},
{
id: 3,
title: 'My blog post 3...',
content: `<p><img onmouseover="alert('This site is not secure');"
src="attack.jpg" /></p>`
}
];
// Let's suppose this is our initialState of Redux which is
// injected to the DOM...
const secureInitialState = serialize(response);
// const insecureInitialState = JSON.stringify(response);
console.log(secureInitialState);
const removeXSSAttacks = html => {
const SCRIPT_REGEX = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
// Removing the <script> tags
while (SCRIPT_REGEX.test(html)) {
html = html.replace(SCRIPT_REGEX, '');
}
// Removing all events from tags...
html = html.replace(/ on\w+="[^"]*"/g, '');
return {
__html: html
}
};
class Xss extends Component {
render() {
// Parsing the JSON string to an actual object...
const posts = JSON.parse(secureInitialState);
// Rendering our posts...
return (
<div className="Xss">
{posts.map((post, key) => (
<div key={key}>
<h2>{post.title}</h2>
<p><strong>Secure Code:</strong></p>
<p>{post.content}</p>
<p><strong>Insecure Code:</strong></p>
<p
dangerouslySetInnerHTML={removeXSSAttacks(post.content)}
/>
</div>
))}
</div>
);
}
}
export default Xss;
File: src/components/Xss/Xss.js