Abstract
Hello everyone it’s me candle. In this time, we will write a program which displays only the permitted html tags by React and delete other tags.
Notice, displaying originally html contents, it may has a security risk such as XSS.
First of all, I am not a security expert, so there is a possibility of a bug in the code. Of course, I check it and test it as long as I do. If you find any vulnerabilities in the code, it would be helpful if you point out it in the post comment form.
Condition
Nothing
Preparation of Sample
If you already have React project, please use it. On the other hand, if you don’t have any project, please make sample project with this command.
create-react-app htmltag_sample cd htmltag_sample
Ready to start.
Implementation
Open the appropriate component file. If you are trying the sample code, open src / App.js
.
First of all, write the code to get and display the post of the user from the text area.
import React, { Component } from 'react' class App extends Component { constructor(props) { super(props) this.state = { content: '', } this.handleFormInputChanged = this.handleFormInputChanged.bind(this) } handleFormInputChanged(event) { this.setState({ [event.target.name]: event.target.value, }) } render() { return ( <div style={{ textAlign: 'center' }}> <div>サンプル</div> <textarea name="content" value={this.state.content} onChange={this.handleFormInputChanged} style={{ width: '400px', height: '300px', fontSize: '15px', }} /> <div style={{ marginTop: '10px' }}>{this.state.content}</div> </div> ) } } export default App
The contents written in the text area are displayed.
However as I saw it, the p tag is displayed as it is.
Now we can not display tags so that we use dangerouslySetInnerHTML
.
Be careful It has XSS vulnerability.
Change the code. from
<div style={{ marginTop: '10px' }}>{this.state.content}</div>
to
<div dangerouslySetInnerHTML={{ __html: this.state.content }} />
According to this article, it seems that it can disable to XSS of <script>
tag.
https://github.com/facebook/react/issues/8838
XSS of <a>
or <iframe>
is not supported.
Let’s write these code to the text area.
<script>alert("1");</script>
Script tag is no problem.
XSS of a
tag will work.
<a onmouseover="alert(1)">XSS</a>
And also iframe
tag.
<iframe src="javascript:alert('1');"></iframe>
Remove except specified tag
Create a function that removes the tags other than you specified.
Let’s write this somewhere in the component.
strip_tags(input, allowed) { allowed = (((allowed || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('') const tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi return input.replace(tags, ($0, $1) => (allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '')) }
Usage is that it requires in the render
function and throws to the dangerouslySetInnerHTML
.
<div style={{ marginTop: '10px' }}> <div dangerouslySetInnerHTML={{ __html: this.strip_tags(this.state.content, '<a>') }} /> </div>
In the above example, a
tag is permitted and others are deleted.
Let’s write such contents.
<iframe src="javascript:alert('XSS');">hello</iframe> <h1>H1 tag</h1> <a href="https://www.google.com" target="_blank">google</a>
You can see that only permitted tags are displayed. But, this also has some problems. If it is a permitted tag, onEvent
and sytle
can be executed.
So we will create a function to remove the properties of these tags.
<a onmouseover="alert(document.cookie)">XSS</a>
Remove and allow tag properties
As you saw above, if you only allow tags, malicious users can attack with onmouseover="alert(document.cookie)"
or style="font-size: 800px"
.
Add this function.
strip_properties(input, allowed) { allowed = (((allowed || '') + '').toLowerCase().match(/[a-z][a-z0-9]*/g) || []).join('') const properties = /\s([a-z][a-z0-9]*)="[^"]*"/gi return input.replace(properties, ($0, $1) => (allowed.indexOf($1.toLowerCase()) > -1 ? $0 : '')) }
When using it you do like this.
Since we only allow a
tag, we allow href
andtarget
properties.
<div style={{ marginTop: '10px' }}> <div dangerouslySetInnerHTML={{ __html: this.strip_properties(this.strip_tags(this.state.content, '<a>'), ['href', 'target']), }} /> </div>
It seems no problem to try the XSS test code.
<script>alert("1");</script> <a onmouseover="alert(1)">XSS</a> <iframe src="javascript:alert('1');"></iframe>
Completed sample code
At last, I wrote the sample code.
import React, { Component } from 'react' class App extends Component { constructor(props) { super(props) this.state = { content: '', } this.handleFormInputChanged = this.handleFormInputChanged.bind(this) } handleFormInputChanged(event) { this.setState({ [event.target.name]: event.target.value, }) } strip_properties(input, allowed) { allowed = (((allowed || '') + '').toLowerCase().match(/[a-z][a-z0-9]*/g) || []).join('') const properties = /\s([a-z][a-z0-9]*)="[^"]*"/gi return input.replace(properties, ($0, $1) => (allowed.indexOf($1.toLowerCase()) > -1 ? $0 : '')) } strip_tags(input, allowed) { allowed = (((allowed || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('') const tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi return input.replace(tags, ($0, $1) => (allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '')) } render() { return ( <div style={{ textAlign: 'center' }}> <div>サンプル</div> <textarea name="content" value={this.state.content} onChange={this.handleFormInputChanged} style={{ width: '400px', height: '300px', fontSize: '15px', }} /> <div style={{ marginTop: '10px' }}> <div dangerouslySetInnerHTML={{ __html: this.strip_properties(this.strip_tags(this.state.content, '<a>'), ['href', 'target']), }} /> </div> </div> ) } } export default App
Conclusion
How is it ?
If you have any problems, please report in the comments section.