LoginSignup
6
7

More than 5 years have passed since last update.

angular-marked を使う + XSS対策など

Last updated at Posted at 2015-09-04

AngularJS の Markdown表示用 directive を使ってみる。

bower

bower で依存入手

bower install --save angular
bower install --save angular-marked 
bower.json
{
  ..省略..
  "dependencies": {
    "angular": "~1.4.5",
    "angular-marked": "~0.0.21"
  }
}

適当なフォームなどを作り動作確認

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Sample1</title>
</head>
<body ng-app="app">
  <h2>入力文字列</h2>
  <textarea ng-model="text" cols="80" rows="5"></textarea>
  <h2>変換前</h2>
  <div ng-bind="text"></div>
  <h2>変換済みMarkdown</h2>
  <div marked="text"></div>
  <script src="bower_components/angular/angular.js"></script>
  <script src="bower_components/marked/marked.min.js"></script>
  <script src="bower_components/angular-marked/angular-marked.js"></script>
  <script>
app = angular.module('app', [
  'hc.marked'
]);
  </script>
</body>
</html>

スクリーンショット 2015-09-04 18.08.43.png

XSS対策

しかし、これでは XSS 対策が行われていない。
以下では試しにbタグを使っているが、scriptタグなんかも問答無用で動作しちゃう。

スクリーンショット 2015-09-04 18.07.44.png

Makredはデフォルトでは、HTMLタグのエスケープを行わない。これを対処するために以下の行をスクリプトに追加する。

  markedProvider.setOptions({
    sanitize: true
  });

これを設定後は以下のようになる。

スクリーンショット 2015-09-04 18.08.06.png

デフォルトでは以下のことが行われる

  • escape処理
  • javascript:から始まるリンクの無効化
  • vbscript: から始まるリンクの無効化

デフォルトのエスケープ処理はこんな感じ

function escape(html, encode) {
  return html 
    .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

このエスケープ処理は markedProvider.setOptions() で設定することができる sanitizer オプションにより自前のものに変更することができる。
何はともあれ、ユーザに何か入力させて表示させる場合は sanitize: true にしないとダメですね。

勝手に外の画像埋め込まれたくない時

Markdown では ![alt](URL) による画像埋め込みに対応しているが、それを禁止したり、特定のドメインの画像に限定したいときは、画像タグの出力方法を変更することにより対処できる。

app.config(['markedProvider', function(markedProvider) {
  renderer = new marked.Renderer();
  renderer.image = function(href, title, text) {
    return '';
  };
  markedProvider.setOptions({
    renderer: renderer,
    sanitize: true
  });
}]);

上の例では、image を出力しようとした時に、問答無用で空文字を返すように変更したもの。

もし、画像ファイルのソースURLのバリデーションを行いたい場合は元々のRenderの挙動を変更するとよいだろう。以下がオリジナルの挙動になる。

Renderer.prototype.image = function(href, title, text) {
  var out = '<img src="' + href + '" alt="' + text + '"'; 
  if (title) {
    out += ' title="' + title + '"'; 
  }
  out += this.options.xhtml ? '/>' : '>'; 
  return out; 
};

というわけで、href をバリデーションばあいのコードが以下になる。

app.config(['markedProvider', function(markedProvider) {
  renderer = new marked.Renderer();
  renderer.image = function(href, title, text) {
    if (href.indexOf('https://example.com/images/') === -1) {
      return ''; // https://example.com/images/ 下にない画像埋め込みはしない
    }
    var out = '<img src="' + href + '" alt="' + text + '"'; 
    if (title) {
      out += ' title="' + title + '"'; 
    }
    out += this.options.xhtml ? '/>' : '>'; 
    return out; 
  };
  markedProvider.setOptions({
    renderer: renderer,
    sanitize: true
  });
}]);

同じように、link などもよくわからんリンクを防いだり、クッションページ入れ込んだりなどなどいろいろカスタマイズが利いたりする。

6
7
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
6
7