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 で動作を確認しています。