Abstract

Nếu sink là JSON.stringify và context là HTML thì có thể chèn trực tiếp HTML tags vào để thực hiện XSS.

Redux có đoạn code sau trong phiên bản cũ của documentation:

function renderFullPage(html, preloadedState) {
  return `
    <!doctype html>
    <html>
      <head>
        <title>Redux Universal Example</title>
      </head>
      <body>
        <div id="root">${html}</div>
        <script>
          window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)}
        </script>
        <script src="/static/bundle.js"></script>
      </body>
    </html>
    `
}

Đoạn code minh họa cách mà output của JSON.stringify sẽ được sử dụng:

function renderFullPage(html, preloadedState) {
  return `
    <!doctype html>
    <html>
      <head>
        <title>Redux Universal Example</title>
      </head>
      <body>
        <div id="root">${html}</div>
        <script>
          window.__PRELOADED_STATE__ = 
            {"title":"The best meal ever!","content":"This place is amazing! ...","restaurantId":1,"id":1}
        </script>
        <script src="/static/bundle.js"></script>
      </body>
    </html>
    `
}

Vấn đề của đoạn code này là việc nó sử dụng output của JSON.stringify để xây dựng HTML. Do JSON.stringify không escape HTML tags nên các đối số có HTML tags vẫn sẽ được giữ nguyên1. Ví dụ, nếu attacker sử dụng content có chứa HTML tags thì kết quả sẽ như sau:

function renderFullPage(html, preloadedState) {
  return `
    <!doctype html>
    <html>
      <head>
        <title>Redux Universal Example</title>
      </head>
      <body>
        <div id="root">${html}</div>
        <script>
          window.__PRELOADED_STATE__ = 
            {"title":"oh shit!","content":"</script><script>alert('gotcha!')</script>","restaurantId":1,"id":1}
        </script>
        <script src="/static/bundle.js"></script>
      </body>
    </html>
    `
}

Khi browser parse HTML này, nó sẽ thấy 2 khối <script> thay vì 1:

<!doctype html>
<html>
    <head>
    <title>Redux Universal Example</title>
    </head>
    <body>
    <div id="root">${html}</div>
    <script>
        window.__PRELOADED_STATE__ =
        {"title":"oh shit!","content":"</script><script>alert('gotcha!')</script>","restaurantId":1,"id":1}
    </script>
    <script src="/static/bundle.js"></script>
    </body>
</html>

Khối đầu tiên là:

<script>
  window.__PRELOADED_STATE__ =
  {"title":"oh shit!","content":"
</script>

Khối thứ hai là:

<script>
  alert("gotcha!")
</script>

Resources

Footnotes

  1. Hành vi này được mô tả ở đặc tả của JSON.stringify