Просмотр исходного кода

Merge branch 'master' into feat/post-comment-notification

sou 7 лет назад
Родитель
Сommit
775df44e83

+ 11 - 0
lib/form/admin/markdownXss.js

@@ -0,0 +1,11 @@
+'use strict';
+
+var form = require('express-form')
+  , field = form.field;
+
+module.exports = form(
+  field('markdownSetting[markdown:xss:isEnabledPrevention]').trim().toBooleanStrict(),
+  field('markdownSetting[markdown:xss:option]').trim().toInt(),
+  field('markdownSetting[markdown:xss:tagWhiteList]').trim(),
+  field('markdownSetting[markdown:xss:attrWhiteList]').trim()
+);

+ 1 - 0
lib/form/index.js

@@ -22,6 +22,7 @@ module.exports = {
     securityPassportGoogle: require('./admin/securityPassportGoogle'),
     securityPassportGitHub: require('./admin/securityPassportGitHub'),
     markdown: require('./admin/markdown'),
+    markdownXss: require('./admin/markdownXss'),
     customcss: require('./admin/customcss'),
     customscript: require('./admin/customscript'),
     customheader: require('./admin/customheader'),

+ 15 - 2
lib/locales/en-US/translation.json

@@ -327,6 +327,8 @@
     "change_redirect_url": "Enter <code>https://${crowi.host}/google/callback</code> <br>(where <code>${crowi.host}</code> is your host name) for \"Authorized redirect URIs\".",
     "clientID": "Client ID",
     "client_secret": "Client Secret",
+    "xss_prevent_setting":"Prevent XSS(Cross Site Scripting)",
+    "xss_prevent_setting_link":"Go to Markdown settings",
     "callback_URL": "Callback URL",
     "guest_mode": {
       "deny": "Deny Unregistered Users",
@@ -398,11 +400,22 @@
 	},
 
   "markdown_setting": {
-    "markdown_rendering": "You can change Markdown rendering settings.",
+    "line_break_setting": "Line Break Setting",
+    "line_break_setting_desc": "You can change line break settings.",
     "Enable Line Break": "Enable Line Break",
     "Enable Line Break desc": "Treat line break in the text page as <code>&lt;br&gt;</code> in HTML",
     "Enable Line Break for comment": "Enable Line Break in comment",
-    "Enable Line Break for comment desc": "Treat line break in comment as <code>&lt;br&gt;</code> in HTML"
+    "Enable Line Break for comment desc": "Treat line break in comment as <code>&lt;br&gt;</code> in HTML",
+    "XSS_setting": "Prevent XSS(Cross Site Scripting) Setting",
+    "XSS_setting_desc": "You can change the handling of HTML tags in markdown text.",
+    "Enable XSS prevention": "Enable XSS Prevention",
+    "Ignore all tags": "Ignore All Tags",
+    "Ignore all tags desc": "Stripe all HTML tags and attributes",
+    "Recommended setting": "Recommended Setting",
+    "Custom Whitelist": "Custom Whitelist",
+    "Tag names":"Tag names",
+    "Tag attributes":"Tag attributes",
+    "import_recommended": "Import recommended %s"
   },
 
   "customize_page": {

+ 15 - 3
lib/locales/ja/translation.json

@@ -344,6 +344,8 @@
     "change_redirect_url": "承認済みのリダイレクトURLに、 <code>https://${crowi.host}/google/callback</code> を入力<br>(<code>${crowi.host}</code>は環境に合わせて変更してください)",
     "clientID": "クライアントID",
     "client_secret": "クライアントシークレット",
+    "xss_prevent_setting":"XSS(Cross Site Scripting)対策設定",
+    "xss_prevent_setting_link":"マークダウン設定ページに移動",
     "callback_URL": "コールバックURL",
     "guest_mode": {
       "deny": "アカウントを持たないユーザーはアクセス不可",
@@ -414,12 +416,22 @@
     }
   },
   "markdown_setting": {
-    "markdown_rendering": "Markdownレンダリングの設定を変更できます。",
+    "line_break_setting": "Line Break設定",
+    "line_break_setting_desc": "Line Breakの設定を変更できます。",
     "Enable Line Break": "Line Break を有効にする",
     "Enable Line Break desc": "ページテキスト中の改行を、HTML内で<code>&lt;br&gt;</code>として扱います",
     "Enable Line Break for comment": "コメント欄で Line Break を有効にする",
-    "Enable Line Break for comment desc": "コメント中の改行を、HTML内で<code>&lt;br&gt;</code>として扱います"
-
+    "Enable Line Break for comment desc": "コメント中の改行を、HTML内で<code>&lt;br&gt;</code>として扱います",
+    "XSS_setting": "XSS(Cross Site Scripting)対策設定",
+    "XSS_setting_desc": "マークダウンテキスト内の HTML タグの扱いを設定し、悪意のあるプログラムからの攻撃を防ぎます",
+    "Enable XSS prevention": "XSSを抑制する",
+    "Ignore all tags": "すべてのタグを抑制する",
+    "Ignore all tags desc": "すべてのHTMLタグと属性を使用不可にします",
+    "Recommended setting": "おすすめ設定",
+    "Custom Whitelist": "カスタムホワイトリスト",
+    "Tag names": "タグ名のホワイトリスト",
+    "Tag attributes": "タグ属性のホワイトリスト",
+    "import_recommended": "おすすめをインポート"
   },
 
   "customize_page": {

+ 88 - 0
lib/models/config.js

@@ -2,6 +2,7 @@ module.exports = function(crowi) {
   var mongoose = require('mongoose')
     , debug = require('debug')('growi:models:config')
     , uglifycss = require('uglifycss')
+    , recommendedXssWhiteList = require('../util/recommendedXssWhiteList')
     , configSchema
     , Config
 
@@ -101,6 +102,10 @@ module.exports = function(crowi) {
 
   function getDefaultMarkdownConfigs() {
     return {
+      'markdown:xss:isEnabledPrevention': true,
+      'markdown:xss:option': 2,
+      'markdown:xss:tagWhiteList': [],
+      'markdown:xss:attrWhiteList': [],
       'markdown:isEnabledLinebreaks': false,
       'markdown:isEnabledLinebreaksInComments': true,
     };
@@ -334,6 +339,85 @@ module.exports = function(crowi) {
     return config.markdown[key];
   };
 
+  configSchema.statics.isEnabledXssPrevention = function(config) {
+    const key = 'markdown:xss:isEnabledPrevention';
+
+    // return default value if undefined
+    if (undefined === config.markdown || undefined === config.markdown[key]) {
+      return getDefaultMarkdownConfigs[key];
+    }
+
+    return config.markdown[key];
+  };
+
+  configSchema.statics.xssOption = function(config) {
+    const key = 'markdown:xss:option';
+
+    // return default value if undefined
+    if (undefined === config.markdown || undefined === config.markdown[key]) {
+      return getDefaultMarkdownConfigs[key];
+    }
+
+    return config.markdown[key];
+  };
+
+  configSchema.statics.tagWhiteList = function(config) {
+    const key = 'markdown:xss:tagWhiteList';
+
+    // return default value if undefined
+    if (undefined === config.markdown || undefined === config.markdown[key]) {
+      return getDefaultMarkdownConfigs[key];
+    }
+
+    if (this.isEnabledXssPrevention(config)) {
+      switch (this.xssOption(config)) {
+        case 1: // ignore all: use default option
+          return [];
+
+        case 2: // recommended
+          return recommendedXssWhiteList.tags;
+
+        case 3: // custom white list
+          return config.markdown[key];
+
+        default:
+          return [];
+      }
+    }
+    else {
+      return [];
+    }
+
+  };
+
+  configSchema.statics.attrWhiteList = function(config) {
+    const key = 'markdown:xss:attrWhiteList';
+
+    // return default value if undefined
+    if (undefined === config.markdown || undefined === config.markdown[key]) {
+      return getDefaultMarkdownConfigs[key];
+    }
+
+    if (this.isEnabledXssPrevention(config)) {
+      switch (this.xssOption(config)) {
+        case 1: // ignore all: use default option
+          return [];
+
+        case 2: // recommended
+          return recommendedXssWhiteList.attrs;
+
+        case 3: // custom white list
+          return config.markdown[key];
+
+        default:
+          return [];
+      }
+    }
+    else {
+      return [];
+    }
+  };
+
   /**
    * initialize custom css strings
    */
@@ -474,6 +558,10 @@ module.exports = function(crowi) {
       layoutType: Config.layoutType(config),
       isEnabledLinebreaks: Config.isEnabledLinebreaks(config),
       isEnabledLinebreaksInComments: Config.isEnabledLinebreaksInComments(config),
+      isEnabledXssPrevention: Config.isEnabledXssPrevention(config),
+      xssOption: Config.xssOption(config),
+      tagWhiteList: Config.tagWhiteList(config),
+      attrWhiteList: Config.attrWhiteList(config),
       highlightJsStyleBorder: Config.highlightJsStyleBorder(config),
       isSavedStatesOfTabChanges: Config.isSavedStatesOfTabChanges(config),
       env: {

+ 32 - 2
lib/routes/admin.js

@@ -14,6 +14,7 @@ module.exports = function(crowi, app) {
     , PluginUtils = require('../plugins/plugin-utils')
     , pluginUtils = new PluginUtils()
     , ApiResponse = require('../util/apiResponse')
+    , recommendedXssWhiteList = require('../util/recommendedXssWhiteList')
 
     , MAX_PAGE_LIST = 50
     , actions = {};
@@ -104,10 +105,12 @@ module.exports = function(crowi, app) {
   // app.get('/admin/markdown'                  , admin.markdown.index);
   actions.markdown = {};
   actions.markdown.index = function(req, res) {
-    var config = crowi.getConfig();
-    var markdownSetting = Config.setupCofigFormData('markdown', config);
+    const config = crowi.getConfig();
+    const markdownSetting = Config.setupCofigFormData('markdown', config);
+
     return res.render('admin/markdown', {
       markdownSetting: markdownSetting,
+      recommendedXssWhiteList: recommendedXssWhiteList,
     });
   };
 
@@ -130,6 +133,33 @@ module.exports = function(crowi, app) {
     }
   };
 
+  // app.post('/admin/markdown/xss-setting' , admin.markdown.xssSetting);
+  actions.markdown.xssSetting = function(req, res) {
+    let xssSetting = req.form.markdownSetting;
+
+    xssSetting['markdown:xss:tagWhiteList'] = stringToArray(xssSetting['markdown:xss:tagWhiteList']);
+    xssSetting['markdown:xss:attrWhiteList'] = stringToArray(xssSetting['markdown:xss:attrWhiteList']);
+
+    req.session.markdownSetting = xssSetting;
+    if (req.form.isValid) {
+      Config.updateNamespaceByArray('markdown', xssSetting, function(err, config) {
+        Config.updateConfigCache('markdown', config);
+        req.session.xssSetting = null;
+        req.flash('successMessage', ['Successfully updated!']);
+        return res.redirect('/admin/markdown');
+      });
+    }
+    else {
+      req.flash('errorMessage', req.form.errors);
+      return res.redirect('/admin/markdown');
+    }
+  };
+
+  const stringToArray = (string) => {
+    const array = string.split(',');
+    return array.map(item => item.trim());
+  };
+
   // app.get('/admin/customize' , admin.customize.index);
   actions.customize = {};
   actions.customize.index = function(req, res) {

+ 2 - 1
lib/routes/index.js

@@ -77,7 +77,8 @@ module.exports = function(crowi, app) {
 
   // markdown admin
   app.get('/admin/markdown'                   , loginRequired(crowi, app) , middleware.adminRequired() , admin.markdown.index);
-  app.post('/admin/markdown/lineBreaksSetting', loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.markdown, admin.markdown.lineBreaksSetting);
+  app.post('/admin/markdown/lineBreaksSetting', loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.markdown, admin.markdown.lineBreaksSetting); //change form name
+  app.post('/admin/markdown/xss-setting'       , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.markdownXss, admin.markdown.xssSetting);
 
   // markdown admin
   app.get('/admin/customize'                , loginRequired(crowi, app) , middleware.adminRequired() , admin.customize.index);

+ 19 - 0
lib/util/recommendedXssWhiteList.js

@@ -0,0 +1,19 @@
+/**
+ * reference: https://meta.stackexchange.com/questions/1777/what-html-tags-are-allowed-on-stack-exchange-sites
+ * added tags: h4, h5, h6, span, div, iframe, table, thead, tbody, tfoot, th, td, tr, colgroup, col
+ * added attributes: class, style
+ */
+
+const tags = [
+  'a', 'b', 'blockquote', 'blockquote', 'code', 'del', 'dd', 'dl', 'dt', 'em',
+  'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'i', 'img', 'kbd', 'li', 'ol', 'p', 'pre',
+  's', 'sup', 'sub', 'strong', 'strike', 'ul', 'br', 'hr', 'span', 'div', 'iframe',
+  'table', 'thead', 'tbody', 'tfoot', 'th', 'td', 'tr', 'colgroup', 'col',
+];
+
+const attrs = ['src', 'href', 'class', 'id', 'width', 'height', 'alt', 'title', 'style'];
+
+module.exports = {
+  'tags': tags,
+  'attrs': attrs,
+};

+ 19 - 7
lib/util/xss.js

@@ -1,20 +1,32 @@
 class Xss {
 
-  constructor(isAllowAllAttrs) {
+  constructor(xssOption) {
     const xss = require('xss');
 
-    // create the option object
+    const isEnabledXssPrevention = xssOption.isEnabledXssPrevention;
+    const tagWhiteList = xssOption.tagWhiteList;
+    const attrWhiteList = xssOption.attrWhiteList;
+
+    let whiteListContent = {};
+
+    // default
     let option = {
       stripIgnoreTag: true,
+      stripIgnoreTagBody: false,
       css: false,
+      whiteList: whiteListContent,
       escapeHtml: (html) => html,   // resolve https://github.com/weseek/growi/issues/221
     };
-    if (isAllowAllAttrs) {
-      // allow all attributes
-      option.onTagAttr = function(tag, name, value, isWhiteAttr) {
-        return `${name}="${value}"`;
-      };
+
+    if (isEnabledXssPrevention) {
+      tagWhiteList.forEach(tag => {
+        whiteListContent[tag] = attrWhiteList;
+      });
     }
+    else {
+      option['stripIgnoreTag'] = false;
+    }
+
     // create the XSS Filter instance
     this.myxss = new xss.FilterXSS(option);
   }

+ 13 - 0
lib/util/xssOption.js

@@ -0,0 +1,13 @@
+class XssOption {
+
+  constructor(config) {
+    const recommendedXssWhiteList = require('../util/recommendedXssWhiteList');
+    const initializedConfig = (config != null) ? config : {};
+
+    this.isEnabledXssPrevention = initializedConfig.isEnabledXssPrevention || true;
+    this.tagWhiteList = initializedConfig.tagWhiteList || recommendedXssWhiteList.tags;
+    this.attrWhiteList = initializedConfig.attrWhiteList || recommendedXssWhiteList.attrs;
+  }
+
+}
+module.exports = XssOption;

+ 162 - 40
lib/views/admin/markdown.html

@@ -38,61 +38,179 @@
       {% endif %}
 
       <form action="/admin/markdown/lineBreaksSetting" method="post" class="form-horizontal" id="markdownSettingForm" role="form">
-      <fieldset>
-        <legend>{{ t('Markdown settings') }}</legend>
-        <p class="well">{{ t("markdown_setting.markdown_rendering") }}</p>
-
-        <div class="form-group">
-          <label for="markdownSetting[markdown:isEnabledLinebreaks]" class="col-xs-4 control-label">
-            {{ t('markdown_setting.Enable Line Break') }}
-          </label>
-          <div class="col-xs-5">
-            <div class="btn-group btn-toggle" data-toggle="buttons">
-              <label class="btn btn-default btn-rounded btn-outline {% if markdownSetting['markdown:isEnabledLinebreaks'] %}active{% endif %}" data-active-class="primary">
-                <input name="markdownSetting[markdown:isEnabledLinebreaks]" value="true" type="radio"
-                    {% if true === markdownSetting['markdown:isEnabledLinebreaks'] %}checked{% endif %}> ON
-              </label>
-              <label class="btn btn-default btn-rounded btn-outline {% if !markdownSetting['markdown:isEnabledLinebreaks'] %}active{% endif %}" data-active-class="default">
-                <input name="markdownSetting[markdown:isEnabledLinebreaks]" value="false" type="radio"
-                    {% if !markdownSetting['markdown:isEnabledLinebreaks'] %}checked{% endif %}> OFF
-              </label>
+        <fieldset>
+          <legend>{{ t('markdown_setting.line_break_setting') }}</legend>
+          <p class="well">{{ t("markdown_setting.line_break_setting_desc") }}</p>
+
+          <div class="form-group">
+            <label for="markdownSetting[markdown:isEnabledLinebreaks]" class="col-xs-4 control-label">
+              {{ t('markdown_setting.Enable Line Break') }}
+            </label>
+            <div class="col-xs-5">
+              <div class="btn-group btn-toggle" data-toggle="buttons">
+                <label class="btn btn-default btn-rounded btn-outline {% if markdownSetting['markdown:isEnabledLinebreaks'] %}active{% endif %}" data-active-class="primary">
+                  <input name="markdownSetting[markdown:isEnabledLinebreaks]" value="true" type="radio"
+                      {% if true === markdownSetting['markdown:isEnabledLinebreaks'] %}checked{% endif %}> ON
+                </label>
+                <label class="btn btn-default btn-rounded btn-outline {% if !markdownSetting['markdown:isEnabledLinebreaks'] %}active{% endif %}" data-active-class="default">
+                  <input name="markdownSetting[markdown:isEnabledLinebreaks]" value="false" type="radio"
+                      {% if !markdownSetting['markdown:isEnabledLinebreaks'] %}checked{% endif %}> OFF
+                </label>
+              </div>
+              <p class="help-block">{{ t("markdown_setting.Enable Line Break desc") }}</p>
             </div>
-            <p class="help-block">{{ t("markdown_setting.Enable Line Break desc") }}</p>
           </div>
-        </div>
 
-        <div class="form-group">
-          <label for="markdownSetting[markdown:isEnabledLinebreaksInComments]" class="col-xs-4 control-label">
-            {{ t("markdown_setting.Enable Line Break for comment") }}
-          </label>
-          <div class="col-xs-5">
-            <div class="btn-group btn-toggle" data-toggle="buttons">
-              <label class="btn btn-default btn-rounded btn-outline {% if markdownSetting['markdown:isEnabledLinebreaksInComments'] %}active{% endif %}" data-active-class="primary">
-                <input name="markdownSetting[markdown:isEnabledLinebreaksInComments]" value="true" type="radio"
-                    {% if true === markdownSetting['markdown:isEnabledLinebreaksInComments'] %}checked{% endif %}> ON
-              </label>
-              <label class="btn btn-default btn-rounded btn-outline {% if !markdownSetting['markdown:isEnabledLinebreaksInComments'] %}active{% endif %}" data-active-class="default">
-                <input name="markdownSetting[markdown:isEnabledLinebreaksInComments]" value="false" type="radio"
-                    {% if !markdownSetting['markdown:isEnabledLinebreaksInComments'] %}checked{% endif %}> OFF
-              </label>
+          <div class="form-group">
+            <label for="markdownSetting[markdown:isEnabledLinebreaksInComments]" class="col-xs-4 control-label">
+              {{ t("markdown_setting.Enable Line Break for comment") }}
+            </label>
+            <div class="col-xs-5">
+              <div class="btn-group btn-toggle" data-toggle="buttons">
+                <label class="btn btn-default btn-rounded btn-outline {% if markdownSetting['markdown:isEnabledLinebreaksInComments'] %}active{% endif %}" data-active-class="primary">
+                  <input name="markdownSetting[markdown:isEnabledLinebreaksInComments]" value="true" type="radio"
+                      {% if true === markdownSetting['markdown:isEnabledLinebreaksInComments'] %}checked{% endif %}> ON
+                </label>
+                <label class="btn btn-default btn-rounded btn-outline {% if !markdownSetting['markdown:isEnabledLinebreaksInComments'] %}active{% endif %}" data-active-class="default">
+                  <input name="markdownSetting[markdown:isEnabledLinebreaksInComments]" value="false" type="radio"
+                      {% if !markdownSetting['markdown:isEnabledLinebreaksInComments'] %}checked{% endif %}> OFF
+                </label>
+              </div>
+              <p class="help-block">{{ t("markdown_setting.Enable Line Break for comment desc") }}</p>
             </div>
-            <p class="help-block">{{ t("markdown_setting.Enable Line Break for comment desc") }}</p>
           </div>
-        </div>
 
-        <div class="form-group">
-          <div class="col-xs-offset-4 col-xs-5">
+          <div class="form-group my-3">
+            <div class="col-xs-offset-4 col-xs-5">
+              <input type="hidden" name="_csrf" value="{{ csrf() }}">
+              <button type="submit" class="btn btn-primary">{{ t("Update") }}</button>
+            </div>
+          </div>
+        </fieldset>
+      </form>
+
+      <form action="/admin/markdown/xss-setting" method="post" class="form-horizontal" id="markdownSettingForm" role="form">
+        {% set nameForIsXssEnabled = "markdownSetting[markdown:xss:isEnabledPrevention]" %}
+        {% set isXssEnabled = markdownSetting['markdown:xss:isEnabledPrevention'] %}
+
+        <legend>{{ t('markdown_setting.XSS_setting') }}</legend>
+        <p class="well">{{ t("markdown_setting.XSS_setting_desc") }}</p>
+
+        <fieldset class="row">
+          <div class="form-group">
+            <label for="markdownSetting[markdown:isEnabledLinebreaks]" class="col-xs-4 control-label">
+              {{ t('markdown_setting.Enable XSS prevention') }}
+            </label>
+            <div class="col-xs-5">
+              <div class="btn-group btn-toggle" data-toggle="buttons">
+                <label class="btn btn-default btn-rounded btn-outline {% if isXssEnabled %}active{% endif %}" data-active-class="primary">
+                  <input name="{{nameForIsXssEnabled}}" value="true" type="radio"
+                      {% if isXssEnabled %}checked{% endif %}> ON
+                </label>
+                <label class="btn btn-default btn-rounded btn-outline {% if !isXssEnabled %}active{% endif %}" data-active-class="default">
+                  <input name="{{nameForIsXssEnabled}}" value="false" type="radio"
+                      {% if !isXssEnabled %}checked{% endif %}> OFF
+                </label>
+              </div>
+            </div>
+          </div>
+        </fieldset>
+
+        <fieldset class="form-group row my-3" id="xss-hide-when-disabled" {% if !isXssEnabled %}style="display: none;"{% endif %}>
+          {% set nameForXssOption = "markdownSetting[markdown:xss:option]" %}
+          {% set xssOption = markdownSetting['markdown:xss:option'] %}
+
+          <div class="col-xs-4 radio radio-primary">
+            <input type="radio" id="option1" name="{{nameForXssOption}}" value="1" {% if xssOption === 1 %}checked{% endif %}>
+            <label for="option1">
+              <p class="font-weight-bold">{{ t('markdown_setting.Ignore all tags') }}</p>
+              <div class="m-t-15">
+                  {{ t('markdown_setting.Ignore all tags desc') }}
+              </div>
+            </label>
+          </div>
+
+          <div class="col-xs-4 radio radio-primary">
+            <input type="radio" id="option2" name="{{nameForXssOption}}" value="2" {% if xssOption === 2 %}checked{% endif %}>
+            <label for="option2">
+              <p class="font-weight-bold">{{ t('markdown_setting.Recommended setting') }}</p>
+              <div class="m-t-15">
+                {{ t('markdown_setting.Tag names') }}
+                <textarea class="form-control xss-list" name="recommendedTags" rows="6" cols="40" readonly>{{ recommendedXssWhiteList.tags }}</textarea>
+              </div>
+              <div class="m-t-15">
+                {{ t('markdown_setting.Tag attributes') }}
+                <textarea class="form-control xss-list" name="recommendedAttrs" rows="6" cols="40" readonly>{{ recommendedXssWhiteList.attrs }}</textarea>
+              </div>
+            </label>
+          </div>
+
+          <div class="col-xs-4 radio radio-primary">
+            <input type="radio" id="option3" name="{{nameForXssOption}}" value="3" {% if xssOption === 3 %}checked{% endif %}>
+            <label for="option3">
+              <p class="font-weight-bold">{{ t('markdown_setting.Custom Whitelist') }}</p>
+              <div class="m-t-15">
+                <div class="d-flex justify-content-between">
+                  {{ t('markdown_setting.Tag names') }}
+                  <p id="btn-import-tags" class="btn btn-xs btn-primary">
+                    {{ t('markdown_setting.import_recommended', 'tags') }}
+                  </p>
+                </div>
+                <textarea class="form-control xss-list" type="text" name="markdownSetting[markdown:xss:tagWhiteList]" rows="6" cols="40" placeholder="e.g. iframe, script, video...">{{ markdownSetting['markdown:xss:tagWhiteList'] }}</textarea>
+              </div>
+              <div class="m-t-15">
+                <div class="d-flex justify-content-between">
+                  {{ t('markdown_setting.Tag attributes') }}
+                  <p id="btn-import-attrs" class="btn btn-xs btn-primary">
+                    {{ t('markdown_setting.import_recommended', 'attributes') }}
+                  </p>
+                </div>
+                <textarea class="form-control xss-list" name="markdownSetting[markdown:xss:attrWhiteList]" rows="6" cols="40" placeholder="e.g. src, id, name...">{{ markdownSetting['markdown:xss:attrWhiteList'] }}</textarea>
+              </div>
+            </label>
+          </div>
+
+        </fieldset>
+
+        <div class="form-group row">
+          <div class="col-xs-12 d-flex justify-content-center">
             <input type="hidden" name="_csrf" value="{{ csrf() }}">
             <button type="submit" class="btn btn-primary">{{ t("Update") }}</button>
           </div>
         </div>
-      </fieldset>
-      </form>
 
+      </form>
     </div>
   </div>
 
 </div>
+
+<script>
+  // give a space between items in textarea(',' => ', ')
+  for (var i = 0; i < $('textarea.xss-list').length; i++) {
+    $($('textarea.xss-list')[i]).val($($('textarea.xss-list')[i]).val().replace(/,/g, ', '));
+  };
+
+  $('input[name="markdownSetting[markdown:xss:isEnabledPrevention]"]').change(function() {
+    if ($(this).val() === 'true') {
+      $('#xss-hide-when-disabled').slideDown();
+    }
+    else {
+      $('#xss-hide-when-disabled').slideUp();
+    }
+  });
+
+  $('#btn-import-tags').on('click', () => {
+    var $tagWhiteList = $('textarea[name="markdownSetting[markdown:xss:tagWhiteList]"]');
+    var $recommendedTagList = $('textarea[name="recommendedTags"]');
+    $tagWhiteList.val($recommendedTagList.val());
+  });
+  $('#btn-import-attrs').on('click', () => {
+    var $attrWhiteList = $('textarea[name="markdownSetting[markdown:xss:attrWhiteList]"]');
+    var $recommendedAttrList = $('textarea[name="recommendedAttrs"]');
+    $attrWhiteList.val($recommendedAttrList.val());
+  });
+</script>
 {% endblock content_main %}
 
 {% block content_footer %}
@@ -100,3 +218,7 @@
 
 
 
+
+
+
+

+ 11 - 2
lib/views/admin/security.html

@@ -100,7 +100,17 @@
         </fieldset>
       </form>
 
-      <form action="/_api/admin/security/mechanism" method="post" class="form-horizontal m-t-30" id="mechanismSetting" role="form">
+      <!-- prevent XSS link -->
+      <div class="mt-5">
+        <legend>{{ t('security_setting.xss_prevent_setting') }}</legend>
+        <div class="text-center">
+          <a class="flexbox" style="font-size: large;" href="/admin/markdown/#preventXSS">
+            <i class="fa-fw icon-login"></i> {{ t('security_setting.xss_prevent_setting_link') }}
+          </a>
+        </div>
+       </div>
+
+      <form action="/_api/admin/security/mechanism" method="post" class="form-horizontal mt-5" id="mechanismSetting" role="form">
         <fieldset>
           <legend class="alert-anchor">{{ t('Selecting authentication mechanism') }}</legend>
           <p class="alert alert-info"><b>{{ t("security_setting.note") }}: </b>{{ t("security_setting.require_server_restart_change_auth") }}</p>
@@ -274,7 +284,6 @@
         </div>
 
       </div><!-- /.auth-mechanism-configurations -->
-
     </div>
   </div>
 

+ 2 - 0
resource/js/legacy/crowi-admin.js

@@ -106,3 +106,5 @@ $(function() {
   // style switcher
   $('#styleOptions').styleSwitcher();
 });
+
+

+ 13 - 3
resource/js/util/PreProcessor/XssFilter.js

@@ -1,14 +1,24 @@
 import Xss from '../../../../lib/util/xss';
+import xssOption from '../../../../lib/util/xssOption';
 
 export default class XssFilter {
 
   constructor(crowi) {
-    // TODO read options
-    this.xss = new Xss(true);
+    this.crowi = crowi;
+
+    if (crowi.config.isEnabledXssPrevention) {
+      this.xssOption = new xssOption(crowi.config);
+      this.xss = new Xss(this.xssOption);
+    }
   }
 
   process(markdown) {
-    return this.xss.process(markdown);
+    if (this.crowi.config.isEnabledXssPrevention) {
+      return this.xss.process(markdown);
+    }
+    else {
+      return markdown;
+    }
   }
 
 }

+ 3 - 2
resource/styles/scss/_admin.scss

@@ -1,4 +1,5 @@
 .admin-page {
+    //security XSS prevent
 
   .admin-user-menu {
     .dropdown-menu {
@@ -6,7 +7,7 @@
       right: 0;
       width: 300px;
     }
-  }
+   }
 
   .admin-group-menu {
     .dropdown-menu {
@@ -37,7 +38,7 @@
     }
 
     .auth-mechanism-configurations {
-      min-height: 800px;
+      min-height: 300px;
     }
   }