Debouncing in React + XSS attack.md
-
In order to debounce but also avoid 2 request to the API, we need 2 pieces of state (term and denounced term)
-
Using dangerouslySetInnerHTML could lead to a xss attack
Debouncing in React + XSS attack.md
In order to debounce but also avoid 2 request to the API, we need 2 pieces of state (term and denounced term)
Using dangerouslySetInnerHTML could lead to a xss attack
| // libs | |
| import React, { useState, useEffect } from 'react'; | |
| import axios from 'axios'; | |
| const Search = () => { | |
| const [term, setTerm] = useState('programming'); | |
| const [debouncedTerm, setDebouncedTerm] = useState(term); | |
| const [results, setResults] = useState([]); | |
| useEffect(() => { | |
| const timerId = setTimeout(() => { | |
| setDebouncedTerm(term); | |
| }, 1000); | |
| return () => { | |
| clearTimeout(timerId); | |
| }; | |
| }, [term]); | |
| useEffect(() => { | |
| const search = async () => { | |
| const { data } = await axios.get('https://en.wikipedia.org/w/api.php', { | |
| params: { | |
| action: 'query', | |
| list: 'search', | |
| origin: '*', | |
| format: 'json', | |
| srsearch: debouncedTerm, | |
| }, | |
| }); | |
| setResults(data.query.search); | |
| }; | |
| search(); | |
| }, [debouncedTerm]); | |
| const renderedResults = results.map((result) => { | |
| return ( | |
| <div key={result.pageid} className="item"> | |
| <div className="right floated content"> | |
| <a | |
| className="ui button" | |
| href={`https://en.wikipedia.org?curid=${result.pageid}`} | |
| > | |
| Go | |
| </a> | |
| </div> | |
| <div className="content"> | |
| <div className="header">{result.title}</div> | |
| <span dangerouslySetInnerHTML={{ __html: result.snippet }}></span> | |
| </div> | |
| </div> | |
| ); | |
| }); | |
| return ( | |
| <div> | |
| <div className="ui form"> | |
| <div className="field"> | |
| <label>Enter Search Term</label> | |
| <input | |
| value={term} | |
| onChange={(e) => setTerm(e.target.value)} | |
| className="input" | |
| /> | |
| </div> | |
| </div> | |
| <div className="ui celled list">{renderedResults}</div> | |
| </div> | |
| ); | |
| }; | |
| export default Search; |
| const express = require('express'); | |
| const cors = require('cors'); | |
| const app = express(); | |
| app.use(express.static('public')); | |
| app.use(cors()); | |
| app.get('/', (req, res) => { | |
| if (req.query.srsearch === 't') { | |
| res.send({ | |
| query: { | |
| search: [ | |
| { | |
| snippet: ` | |
| <img src="asdf" onerror="document.body.innerHTML = '<h1>HAHAHA, I control this app now!!!</h1>';"></img> | |
| `, | |
| }, | |
| ], | |
| }, | |
| }); | |
| } else { | |
| res.send({ | |
| query: { | |
| search: [], | |
| }, | |
| }); | |
| } | |
| }); | |
| app.listen(3001); |
Note:
In order to debounce but also avoid 2 request to the API, we need 2 pieces of state (term and denounced term)
Using
dangerouslySetInnerHTMLcould lead to a xss attack