10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

marked.jsのサニタイズ(エスケープ)2STEP

Last updated at Posted at 2019-08-21

marked.js便利ですよね

Markdownをプレビューしたり、HTMLで表示させる時にクライアント側で変換してもらうためにmarked.jsを使う事があると思います。
ですが、うっかりしてると結構致命的なミスを引き起こしてしまいます。
##前提
marked|githubがすでに使える状態とします。

STEP1 sanitize-html等のライブラリを入れる

Marked自身にもサニタイズオプションはありますが、現在それの使用は非推奨です。

Warning: 🚨 Marked does not sanitize the output HTML. Please use a sanitize library, like DOMPurify (recommended), sanitize-html or insane on the output HTML! 🚨

環境にあったライブラリを選定して入れましょう。
私はsanitize-htmlを選びました。
#STEP2 サニタイズする
markedはmarked(text)という形でtextをHTMLに変換した値を返してくれます。
sanitize-htmlはsanitizeHtml(text)とするとtextから<script></script>等を削除して返します。
簡単ですね。

では、あなたはこれら2つを使ってプレビュー付きのMarkdownエディタを作る時、この2つの関数をどのようにつなげますか?
(プレビューするだけならサニタイズの必要性は薄いのですが、一般に公開するときも同様の手順でサニタイズするとします)

フォームはこんな感じとします

editor.vue
<template>
...
<textarea
 id="editor-content"
 :value="textareaValue"
 name="editor-textarea-content"
 @input="convertMarkdown"
 ></textarea>
<div id="editor-preview" v-html="convertHtml"></div>
...
</template>
<script>
const marked = require('marked')
const sanitizeHtml = require('sanitize-html')
const _ = require('lodash')
export default {
  head() {
    return {
      title: 'テストサイト'
    }
  },
  components: {
    headerComponent
  },
  data() {
    return {
      textareaValue: ''
    }
  },
  computed: {
    convertHtml() {
      //ここでmarkdownの変換と、エスケープ処理を行う
    }
  },
  mounted() {
    marked.setOptions({
      breaks: true
    })
  },
  methods: {
    convertMarkdown: _.throttle(function(e) {
      console.log(e.target.value)
      this.textareaValue = e.target.value
    }, 500)
  }
}
</script>

例がNuxtなので念の為解説すると、textareaに何か入力されたらconvertMarkdownが値を保存し、convertHtmlがそれを変換するという形です。、下のdivに内容がHTML化されたものが表示されるという感じで、他の部分は値の受け渡し等の設定と思ってください。
追記: 実用的にするため、const _ = require('lodash')を用いて、inputで関数が呼ばれすぎない様にしました。

ここでの書き方は大まかに2つあります。

  methods: {
    convertMarkdown() {
      const conbertContent = marked(this.textareaValue)
      this.convertHtml = sanitizeHtml(conbertContent)
    }
  }

  methods: {
    convertMarkdown() {
      const conbertContent = sanitizeHtml(this.textareaValue)
      this.convertHtml = marked(conbertContent)
    }
  }

です。どちらもサニタイズ関数を用いており一見問題ないのですが、これ1つは簡単にJavaScriptを実行できてしまうんです

欠陥があるのは、先にサニタイズしている2番目の例です。
どちらもtextareaに<script>alert(1);</script>なんかを入れてもきっちり弾くので「よし、問題ないな」と思ってしまう事もあると思うんですが、
後者だとbugbounty-cheatsheetのMarkdwodn xss例の
[a](javascript:window.onerror=confirm;throw%201)
の様なmarkedで展開されるリンクまでは完璧にサニタイズしてくれないので、ポップアップが出てきてしまいます。

まとめ

Markdownのサニタイズは一番最後!
(ちなみにh1やh2はサニタイズで弾かれるので、適時許可しましょう)

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?