具体例で学ぶ、kintoneカスタマイズにおけるXSS対策

目次

はじめに

kintoneはJavaScriptを使って自由にカスタマイズできます。

しかし、カスタマイズの自由度が高い反面、セキュリティ対策を十分に行わないと脆弱性を抱える可能性があります。
特にクロスサイトスクリプティング(XSS)では、ユーザー入力を不正に処理し、悪意あるスクリプトが実行されるリスクにつながります。
そのため、適切な対策を講じる必要があります。

この記事では、 kintoneセキュアコーディングガイドライン を補完し、XSSの脅威を具体的なコード例を交えながら解説します。

クロスサイトスクリプティング(XSS)とは

クロスサイトスクリプティング(XSS)とは、ユーザからの⼊⼒内容をWeb ページに表⽰するようなWebアプリケーションの特徴を⽤いて、不正なスクリプトを挿⼊することで引き起こす攻撃のことです。

このような攻撃が成功すると、次のようなリスクが生じます。

  • ユーザーのセッション情報やCookieが盗まれる。
  • kintone内のデータが盗まれる。
  • ユーザーの意図に反して、kintone内で不正な操作が実行される。

XSSを体験する

ここからは、サンプルとなるkintoneアプリおよびカスタマイズを通じて、XSSを体験します。
また、 次の章 では実際にセキュアなコーディングを行うことで問題が解消されることも体験します。

kintone アプリの作成

以下のフィールドを持ったkintoneアプリを作成します。

フィールドタイプ フィールドコード/要素ID
文字列(複数行) input
スペース displayTextSpace

カスタマイズ(JavaScript)の作成

作成したアプリには1つの文字列(複数行)フィールドと、1つのスペースフィールドが含まれています。
以下のように「文字列(複数行)フィールドに入力した内容をスペースフィールドにも表示する」カスタマイズを実装します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
 * kintone XSS sample program
 * Copyright (c) 2024 Cybozu
 *
 * Licensed under the MIT License
 * https://opensource.org/license/mit/
 */
(() => {
  'use strict';

  // レコード詳細画面で実行
  kintone.events.on('app.record.detail.show', (event) => {
    // スペースフィールドの要素取得
    const spaceEl = kintone.app.record.getSpaceElement('displayTextSpace');

    // 文字列(複数行)フィールドのユーザー入力値から、要素を生成
    const textFromKintone = event.record.input.value;
    const divInput = document.createElement('div');
    divInput.innerHTML = '<h1>' + textFromKintone + '</h1>';
    spaceEl.appendChild(divInput);
  });
})();

ここで、レコード作成画面を開き、文字列(複数行)フィールドに以下の文字列を入力します。

1
</h1><span onmouseover="alert('XSS')">マウスを乗せると</span><h1>

この状態でレコードを保存し、詳細画面を開きます。
スペース部分の「マウスを乗せると」というテキストにマウスオーバーしてみましょう。

入力された文字列に含まれるスクリプト(alert('XSS')の部分)が実行されます。その結果、アラートが表示されました。

このように、XSS対策が不十分だと、悪意あるユーザーの入力によってスクリプトを埋め込める状態を作り出してしまいます。

コードの問題点

現在のカスタマイズには、どのような問題があるのでしょうか。

16
17
18
19
20
// 文字列(複数行)フィールドのユーザー入力値から、要素を生成
const textFromKintone = event.record.input.value;
const divInput = document.createElement('div');
divInput.innerHTML = '<h1>' + textFromKintone + '</h1>';
spaceEl.appendChild(divInput);

17行目で、ユーザーがフィールドに入力した値を取得しています。
19行目で、取得した値をinnerHTMLに渡しています。
innerHTMLに渡した値はHTMLとして解釈されるため、入力した内容がそのままHTMLとしてページに組み込まれます。

たとえば、ユーザーが<span onmouseover="alert('XSS')">マウスを乗せると</span>のようなHTMLタグを含む値を入力すると、その内容がHTMLとしてページに表示されます。

具体的には、以下のような流れでXSSが発生します。

  1. <span onmouseover="alert('XSS')">マウスを乗せると</span>innerHTMLに渡されると、ブラウザーはHTMLとして解釈します。
    つまり、<span>タグが生成され、その中に「マウスを乗せると」というテキストを表示します。
  2. onmouseover="alert('XSS')"という部分は、JavaScriptのイベントハンドラーとして設定されます。
    このイベントハンドラーは、<span>タグにマウスを乗せたタイミングでalert('XSS')というスクリプトを実行します。

このように、ユーザーからの入力を元に、動的にDOM要素を生成している場合、不正なスクリプトを挿入することで、XSSを発生させる可能性があります。

セキュアなコードに修正する

それでは、このカスタマイズをセキュアなコードに修正します。
先ほどのinnerHTMLを使用していた箇所を、次のようにinnerTextを使った実装に置き換えます。

1
2
3
4
5
6
7
// 修正前
// divInput.innerHTML = '<h1>' + textFromKintone + '</h1>';

// 修正後
const header = document.createElement('h1');
header.innerText = textFromKintone;
divInput.appendChild(header);

置き換え後の結果は以下のようになります。

ユーザーの入力値がそのままテキストとして表示されるのみで、マウスオーバーしてもスクリプトは実行されません。
このように、innerTextは入力値をそのままテキストとして扱います。
そのため、HTMLとして解釈される心配がありません。

また別な方法として、ユーザーからの入力に対して特殊な意味をもつ文字(< > "など)をエスケープすることも有効な対策です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function escapeHtml(str) {
  str = str.replace(/&/g, '&amp;');
  str = str.replace(/</g, '&lt;');
  str = str.replace(/>/g, '&gt;');
  str = str.replace(/"/g, '&quot;');
  str = str.replace(/'/g, '&#39;');
  return str;
}

// ...

divInput.innerHTML = '<h1>' + escapeHtml(textFromKintone) + '</h1>';

おわりに

本記事では、kintoneカスタマイズにおけるXSSの脅威とその対策について、具体的なコード例を交えながら解説しました。
セキュリティ対策を怠ると、ユーザーに深刻な被害を与えるリスクがあります。
特に、ユーザー入力をそのままWebページに反映する場合は、XSS攻撃を防ぐためにエスケープ処理やサニタイズ処理が不可欠です。

また、innerTextやエスケープ関数を使うことで、ユーザー入力を安全に扱えることを学びましたが、これらはあくまで基本的な対策です。
必要に応じて、 DOMPurify (External link) などのライブラリの利用も検討しましょう。

DOMPurifyを使う方法は、次のページを参考にしてください。
DOMPurifyを使って、kintoneで安全にDOMをエスケープしよう!

information

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