DOMPurifyを使って、kintoneで安全にDOMをエスケープしよう!

著者名:江田 篤史

目次

はじめに

kintoneは、カスタマイズによってレコードの表示方法を変更できるため非常に便利です。
レコードに入力されたマークアップテキストやマークダウンテキストを解析して表示できます。

しかし、セキュリティ面を意識しないと、悪意を持ったユーザーからサイバー攻撃を受ける可能性があります。
今回はkintoneアプリで想定されるサイバー攻撃の例と、 DOMPurify (External link) を使った対策を紹介します。

DOMPurifyは Cybozu CDNにてサポートされているので利用してください。

DOMPurifyのメリット

単純に正規表現で置き換えたりしようとすると回避されてXSSを埋め込まれる可能性があります。
innerHTMLやjQueryのhtml()などで出力する前にDOMPurify.sanitize()しておくことで、より安全にHTMLタグを許容できます。

kintoneセキュアコーディングガイドラインでも、出力するすべての要素に対してエスケープ処理を施すことを推奨しています。
やむを得ずinnerHTMLを使う場合は、DOMPurifyのような対策が有効です。

サイバー攻撃の例

ここでは、セキュリティ脆弱性を含んだアプリとそのアプリへのサイバー攻撃の例を紹介します。

セキュリティ脆弱性を含んだアプリ

サンプルとして、マークダウンフィールドの内容をコンバートして表示するアプリを用意しました。

フォーム設定
フィールド名 フィールドタイプ フィールドコード
マークダウン
文字列(複数行)
マークダウン
スペース
preview
フォーム画面

JavaScriptカスタマイズ

sample.jsとして次のプログラムをアプリに適用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/*
* DOMPurify-kintone
* Copyright (c) 2018 Cybozu
*
* Licensed under the MIT License
*/
(function() {
  'use strict';
  kintone.events.on(['app.record.detail.show'], (event) => {
    const space = kintone.app.record.getSpaceElement('preview');
    $(space).html(marked(event.record.マークダウン.value));
  });
})();

JavaScriptライブラリの jQuery (External link) および Marked.js (External link) を利用しています。
お試しの場合は、 Cybozu CDNから利用してください。

動作

詳細画面を開くと、コンバートされたマークダウンフィールドの内容がpreviewスペースに表示されます。

サイバー攻撃

上記アプリで、悪意のあるユーザーにより文字列(複数行)フィールドに次の内容が登録されたとします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<script>
  kintone.api('/k/v1/records', 'GET', {app: kintone.app.getId()}).then((response) => {
    const ids = response.records.map((record) => {
      return record.$id.value;
    });
    kintone.api(kintone.api.url('/k/v1/records', true), 'DELETE', {
      app: kintone.app.getId(),
      ids: ids
    });
  });
</script>

レコードの削除権限を持った別のユーザーが、このレコードの詳細画面を開くと、アプリ内のレコードが全件削除されてしまいました。

このようにセキュリティ脆弱性を含むアプリでは、悪意のあるユーザー自身がレコードの削除権限を持っていないとしても、間接的にレコードを全件削除できます。

同様に、閲覧権限のないレコードを盗み出すことなどもできてしまいます。

注意事項
  • 当アプリはサイバー攻撃の一例として意図的にカスタマイズしたものです。
    セキュリティリスクがあるため、DOMPurifyのようなセキュリティ対策なしに使うことはお控えください。
  • 検証環境でのみお試しください。 万一データが損失した場合、サイボウズは責任を負いません。

対策

上記の対策としてDOMPurifyを使います。

DOMPurify.sanitize()を用いると、コード内の危険性のある箇所(<script>タグ等)を除去してくれます。

DOMPurifyの利用例

HTMLを埋め込んでいる箇所に対してDOMPurify.sanitize()します。

1
2
3
const dirty = `<div><p>テキスト</p><script>alert('アラート');</script></div>`;
const clean = DOMPurify.sanitize(dirty);
console.log(clean); // => '<div><p>テキスト</p></div>'

alert("アラート");が除去されていることを確認できます。

さきほどのsample.jsにDOMPurify.sanitize()を実装した場合はこちらになります。

1
2
3
4
5
6
7
8
(function() {
  'use strict';
  kintone.events.on(['app.record.detail.show'], (event) => {
    const space = kintone.app.record.getSpaceElement('preview');
    const content = DOMPurify.sanitize(marked(event.record.マークダウン.value));
    $(space).html(content);
  });
})();

動作確認

先ほどはすべてのレコードが削除されてしまいしたが、DOMPurify.sanitize()することで、 マークダウンの表示をしつつ全件削除の攻撃を防ぐようになりました。

最後に

セキュリティを意識せずに、ユーザーが入力したコードを表示するのは非常に危険です。

今回は「サイバー攻撃」という想定で書きましたが、意図しない「うっかり」でも同様の危険を含みます。

このような場合に備えてあらかじめDOMPurifyでエスケープすることで、より安全にDOMを扱うことができます。

主要ブラウザーでは、innerHTMLを用いてDOMを追加した場合には<script>タグの中身を実行しません。
しかし念のため、出力前にDOMPurify.sanitize()しておくことをおすすめします。

information

このTipsは、2018年6月版kintoneで動作を確認しています。