Selaa lähdekoodia

Merge branch 'master' into imprv/6037-button-to-cloud

Steven Fukase 4 vuotta sitten
vanhempi
sitoutus
d2246dba02

+ 10 - 1
CHANGES.md

@@ -1,9 +1,18 @@
 # CHANGES
 # CHANGES
 
 
-## v4.2.20-RC
+## v4.2.21-RC
 
 
 * 
 * 
 
 
+## v4.2.20
+
+* Improvement: Error message when the password is too short
+* Improvement: Repeat XSS processing as a countermeasure against nesting 
+* Fix: NoSQL injection of access-token-parser
+* Fix: Checking permission when operating share links
+* Fix: Invalid NaN label is shown when deletedAt of the page is undefined
+    * Introduced by v4.2.8
+
 ## v4.2.19
 ## v4.2.19
 
 
 * Feature: Set max-age of the user's cookie with the env var `SESSION_MAX_AGE`
 * Feature: Set max-age of the user's cookie with the env var `SESSION_MAX_AGE`

+ 2 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "growi",
   "name": "growi",
-  "version": "4.2.20-RC",
+  "version": "4.2.21-RC",
   "description": "Team collaboration software using markdown",
   "description": "Team collaboration software using markdown",
   "tags": [
   "tags": [
     "wiki",
     "wiki",
@@ -109,6 +109,7 @@
     "express": "^4.16.1",
     "express": "^4.16.1",
     "express-bunyan-logger": "^1.3.3",
     "express-bunyan-logger": "^1.3.3",
     "express-form": "~0.12.0",
     "express-form": "~0.12.0",
+    "express-mongo-sanitize": "^2.1.0",
     "express-session": "^1.16.1",
     "express-session": "^1.16.1",
     "express-validator": "^6.1.1",
     "express-validator": "^6.1.1",
     "express-webpack-assets": "^0.1.0",
     "express-webpack-assets": "^0.1.0",

+ 30 - 11
resource/certs/localhost/cert.pem

@@ -1,13 +1,32 @@
 -----BEGIN CERTIFICATE-----
 -----BEGIN CERTIFICATE-----
-MIICBzCCAXACCQD4US7+0A/b/zANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJK
-UDEOMAwGA1UECAwFVG9reW8xFTATBgNVBAoMDFdFU0VFSywgSW5jLjESMBAGA1UE
-AwwJbG9jYWxob3N0MB4XDTE4MDkxMjEwMjIzNFoXDTE4MTAxMjEwMjIzNFowSDEL
-MAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMRUwEwYDVQQKDAxXRVNFRUssIElu
-Yy4xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
-gYEAy0FKOlCQynbo5wYyMCL5LW27Re9dB14wXPDT+zd7wDdYrCbAKKZ1r+7Sj1e7
-638lnn7n4WkhkgsQi/mTxF7W9PHYF00Dh2X0qGf9t+LocNeLVQBHMGNi7HXh8X3j
-iM7w9FffdlfBvuYxPIdDXP12x9JmRhr59Tpv1aaMcRxAY1cCAwEAATANBgkqhkiG
-9w0BAQsFAAOBgQBa/PwnEeFCQ5G4SS6IcL6QVh3KLfeVMCfYVk1o0iJVmJTvfdrq
-crmVwBzbloUO2l6k1ibwD2WVwpdxMKIF5z58HfKAvxZAzCHE7kMEZr1ge30WRXQA
-pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
+MIIFcTCCA1mgAwIBAgIUPCyJHVo85uXE84ITTVrxFDflhv4wDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMRUwEwYDVQQKDAxXRVNF
+RUssIEluYy4xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMTA2MDcxNTIzMDFaFw0z
+MTA2MDUxNTIzMDFaMEgxCzAJBgNVBAYTAkpQMQ4wDAYDVQQIDAVUb2t5bzEVMBMG
+A1UECgwMV0VTRUVLLCBJbmMuMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQDJwKo3/75jQ7gFO6ejC/3hTptDvZpCnKpI
+TFJ5We2jZpbxULS2lQqFiVj2ezGvXRq+IHO2lUYFI/kLLOmSruM2u/Wrot3F/iZ8
+M31kZQiOoOwIZKrD1TZiEa848q0Gk3yU20d2mZmUB1ddIWl37o7MDeLaqrr825qc
+2Nf8jNm7AdEBPogXcwxI3MNphkEjICm8xOBhZh4IYlmRicweogebOFpUK4oQNdUA
+HejX5tyZboadoBRhXeZxnbQchkMv2HpV0JY31rgpJWKcuK7kTz3BZouOCUgI18Zi
+4nNzRr589IigpDYqDJWcnuz22XeimQJe+T/eEshrhyCFHIFeGi6UEH7CIcZYjigH
+Ha7nommeApjqWultgPZuNjUoQsNW8c8Y/e8rIyQg8WwevcmUbL/PDSKj/kx8In02
+7lR2NETJvRnHwpPuj9/MggfA8jsmnqVaNu9zShUjri03KqLsdkW0mVr3q4jtazsA
+xukAWXGlA+VqRBGwvpwaY2NEG1t9s/NX5FsUQpWmw75caQndxrx9oAr/59NtUeg2
+mLyI9avyZFMhMIa3lnabp0efcII3HeiouPRNJGsR2PrD418dUSwlAXftu4yEPENs
+nlKf8pMP0PCfGCQ2NWxDe0Yp1YvtIwXX9ZoFLrkxEZ4tzVpvSSxOC3YX+IY9E4Q2
+GI5ofJoO4wIDAQABo1MwUTAdBgNVHQ4EFgQUscss6LD5V0Bla8YKtb/mBiVXBR0w
+HwYDVR0jBBgwFoAUscss6LD5V0Bla8YKtb/mBiVXBR0wDwYDVR0TAQH/BAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAgEAiLpFrI7aGlJsPSuarroTPCvSz6vaFFg+QlIR
+MSwITiZ/kNj6c4bh+rzADdsiYU0wOC1ufq841IdHgxnMw7l33hyL1HMc/wrUVCHO
+yE/pz6w2ybxipIOfw21fgwYwYlrdvvlLtqTC/zf92nsuEmMhIjqm8oIhUfRlzX0b
+A1hzMXzvPwxUxNyTX/TSeTvAViDgXTGwstBfN/iiJr8RnX+4cGcyibu+80wa/8As
+T8ryOqdJWC1VoOTupfs5TCbQ8Cc/UDO580ytSHUb8r5RqpB1wl35ui7d6S/6TwwB
+VSKigcbq080oFk+8J0AFAXxYkdAInuDXxvmdSULttQR6H7++kNBiHFObbFOyK4gf
+YDl64bp+q4xAXnKCCmafdpfvMhDlsM91IBy+1utCQd5PeQ6rE5Tv7cz9DPec9X/v
+3tVch3rYYHUHxdPv2ZkXEPTKaMiTFEuO5daV/j5qSvZIwaDofHOEpEojyRGpopxt
+FA9A1oPA/yBFjGpOOQummAKiVooYJUdDX2gSRODNGfXyfYOFh2x1Czdl9VJuK4BW
+qeKTMKJHSVAquOJdk879fF9/Cx4C7iBIaNgFwSCkKnhYIRvYVEarX0QEHBJmiSgG
++0P2AR5mOiUhJqGlRLOWfjEQd+pYfqTi1KOkRMP4xEJdBNCp07zBWmOMXGlRwzQ2
+giuw184=
 -----END CERTIFICATE-----
 -----END CERTIFICATE-----

+ 0 - 11
resource/certs/localhost/csr.pem

@@ -1,11 +0,0 @@
------BEGIN CERTIFICATE REQUEST-----
-MIIBhzCB8QIBADBIMQswCQYDVQQGEwJKUDEOMAwGA1UECAwFVG9reW8xFTATBgNV
-BAoMDFdFU0VFSywgSW5jLjESMBAGA1UEAwwJbG9jYWxob3N0MIGfMA0GCSqGSIb3
-DQEBAQUAA4GNADCBiQKBgQDLQUo6UJDKdujnBjIwIvktbbtF710HXjBc8NP7N3vA
-N1isJsAopnWv7tKPV7vrfyWefufhaSGSCxCL+ZPEXtb08dgXTQOHZfSoZ/234uhw
-14tVAEcwY2LsdeHxfeOIzvD0V992V8G+5jE8h0Nc/XbH0mZGGvn1Om/VpoxxHEBj
-VwIDAQABoAAwDQYJKoZIhvcNAQELBQADgYEAd49hz4IoQO55tr62OFlZr254ZPBX
-SXxCtSWawBWLFij8QLl1B8JkHARrMKdM1jBCy5UXcH05DrxGwIOXIcRW7mfrIQDH
-pGs+BQCHMHuYnssg/z2aDhafkmPaLBwh0KWPypVIStxUwLcKxA1xk5VBoP/q+Lgk
-h/mCVJ7JY40BlLA=
------END CERTIFICATE REQUEST-----

+ 52 - 15
resource/certs/localhost/key.pem

@@ -1,15 +1,52 @@
------BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQDLQUo6UJDKdujnBjIwIvktbbtF710HXjBc8NP7N3vAN1isJsAo
-pnWv7tKPV7vrfyWefufhaSGSCxCL+ZPEXtb08dgXTQOHZfSoZ/234uhw14tVAEcw
-Y2LsdeHxfeOIzvD0V992V8G+5jE8h0Nc/XbH0mZGGvn1Om/VpoxxHEBjVwIDAQAB
-AoGALsf8OafJa5Aq0uGOM54ZE+eprtME6mk3YGzdnXiLtxYGBrl0iOanN7MUK4HZ
-8r30/qHe5Pa5j0+Uo2LyK8RYXOcT77CeSSQiSGlBgGj7US7ZmyTqsOwaUKVnbFcy
-Bf/bTJl4EjZREy7kdfCWVO1yY98tV4XrZ2CBDSEDyI8UiiECQQDw47YBMy2duuS6
-z5Ui0xRr8MYwRlCNOt6xKQxlKEhnnRA0vdNB6VMiSzvZ9Bt5nPWI/B3ugUupo6Hn
-FXi1tOgTAkEA2AE7RTICcGPpogOtq/5g7pPNofH524hN26qtUT2kKjUy316JYjrU
-t+N6Ck867w4juVbDcVOTB2Nbj+2+t2EILQJAfo1CyvKWHm1XSQVRNlBqRCLkG+x0
-2R16bNxB1MsK7tRG9U5ctB3ePQAFW4WxAX0CSYsaNnjaxS5gGkTfe6ak3QJAWVlh
-EAVYtu7NRKQq4btOk0F2TOfQB7xBIH1gRfuufXsV+Qmc4JIfTZV99OfDJAGAS3kV
-TTpZ1jOGO2oHeslbXQJBAM4xX8hUueQMIllpBNjlAx1xTqptOHa4elaaPZi7HcDj
-olRU0OP/wPOoEJRvHGP8+LAerx5CEYbadnukQAnNPLA=
------END RSA PRIVATE KEY-----
+-----BEGIN PRIVATE KEY-----
+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
++13WXbYYm6udwoFKrodRDCRDcDAlCoI=
+-----END PRIVATE KEY-----

+ 20 - 1
src/lib/service/xss/index.js

@@ -1,6 +1,9 @@
 const xss = require('xss');
 const xss = require('xss');
 const commonmarkSpec = require('./commonmark-spec');
 const commonmarkSpec = require('./commonmark-spec');
 
 
+
+const REPETITIONS_NUM = 50;
+
 class Xss {
 class Xss {
 
 
   constructor(xssOption) {
   constructor(xssOption) {
@@ -36,7 +39,23 @@ class Xss {
   }
   }
 
 
   process(document) {
   process(document) {
-    return this.myxss.process(document);
+    let count = 0;
+    let currDoc = document;
+    let prevDoc = document;
+
+    do {
+      count += 1;
+      // stop running infinitely
+      if (count > REPETITIONS_NUM) {
+        return '--filtered--';
+      }
+
+      prevDoc = currDoc;
+      currDoc = this.myxss.process(currDoc);
+    }
+    while (currDoc !== prevDoc);
+
+    return currDoc;
   }
   }
 
 
 }
 }

+ 2 - 0
src/server/crowi/express-init.js

@@ -11,6 +11,7 @@ module.exports = function(crowi, app) {
   const passport = require('passport');
   const passport = require('passport');
   const expressSession = require('express-session');
   const expressSession = require('express-session');
   const flash = require('connect-flash');
   const flash = require('connect-flash');
+  const mongoSanitize = require('express-mongo-sanitize');
   const swig = require('swig-templates');
   const swig = require('swig-templates');
   const webpackAssets = require('express-webpack-assets');
   const webpackAssets = require('express-webpack-assets');
   const i18next = require('i18next');
   const i18next = require('i18next');
@@ -116,6 +117,7 @@ module.exports = function(crowi, app) {
   app.use(passport.session());
   app.use(passport.session());
 
 
   app.use(flash());
   app.use(flash());
+  app.use(mongoSanitize());
 
 
   app.use(promster);
   app.use(promster);
   app.use(registerSafeRedirect);
   app.use(registerSafeRedirect);

+ 1 - 1
src/server/middlewares/access-token-parser.js

@@ -8,7 +8,7 @@ module.exports = (crowi) => {
   return async(req, res, next) => {
   return async(req, res, next) => {
     // TODO: comply HTTP header of RFC6750 / Authorization: Bearer
     // TODO: comply HTTP header of RFC6750 / Authorization: Bearer
     const accessToken = req.query.access_token || req.body.access_token || null;
     const accessToken = req.query.access_token || req.body.access_token || null;
-    if (!accessToken) {
+    if (accessToken == null || typeof accessToken !== 'string') {
       return next();
       return next();
     }
     }
 
 

+ 61 - 9
src/server/routes/apiv3/share-links.js

@@ -8,7 +8,7 @@ const express = require('express');
 
 
 const router = express.Router();
 const router = express.Router();
 
 
-const { body } = require('express-validator');
+const { body, query, param } = require('express-validator');
 
 
 const ErrorV3 = require('../../models/vo/error-apiv3');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
 
@@ -28,14 +28,19 @@ module.exports = (crowi) => {
   const csrf = require('../../middlewares/csrf')(crowi);
   const csrf = require('../../middlewares/csrf')(crowi);
   const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
   const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
   const ShareLink = crowi.model('ShareLink');
   const ShareLink = crowi.model('ShareLink');
+  const Page = crowi.model('Page');
 
 
+  validator.getShareLinks = [
+    // validate the page id is MongoId
+    query('relatedPage').isMongoId().withMessage('Page Id is required'),
+  ];
 
 
   /**
   /**
    * @swagger
    * @swagger
    *
    *
    *  paths:
    *  paths:
    *    /share-links/:
    *    /share-links/:
-   *      post:
+   *      get:
    *        tags: [ShareLink]
    *        tags: [ShareLink]
    *        description: get share links
    *        description: get share links
    *        parameters:
    *        parameters:
@@ -49,10 +54,19 @@ module.exports = (crowi) => {
    *          200:
    *          200:
    *            description: Succeeded to get share links
    *            description: Succeeded to get share links
    */
    */
-  router.get('/', loginRequired, async(req, res) => {
+  router.get('/', loginRequired, validator.getShareLinks, apiV3FormValidator, async(req, res) => {
     const { relatedPage } = req.query;
     const { relatedPage } = req.query;
+
+    const page = await Page.findByIdAndViewer(relatedPage, req.user);
+
+    if (page == null) {
+      const msg = 'Page is not found or forbidden';
+      logger.error('Error', msg);
+      return res.apiv3Err(new ErrorV3(msg, 'get-shareLink-failed'));
+    }
+
     try {
     try {
-      const shareLinksResult = await ShareLink.find({ relatedPage: { $in: relatedPage } }).populate({ path: 'relatedPage', select: 'path' });
+      const shareLinksResult = await ShareLink.find({ relatedPage }).populate({ path: 'relatedPage', select: 'path' });
       return res.apiv3({ shareLinksResult });
       return res.apiv3({ shareLinksResult });
     }
     }
     catch (err) {
     catch (err) {
@@ -63,8 +77,8 @@ module.exports = (crowi) => {
   });
   });
 
 
   validator.shareLinkStatus = [
   validator.shareLinkStatus = [
-    // validate the page id is null
-    body('relatedPage').not().isEmpty().withMessage('Page Id is null'),
+    // validate the page id is MongoId
+    body('relatedPage').isMongoId().withMessage('Page Id is required'),
     // validate expireation date is not empty, is not before today and is date.
     // validate expireation date is not empty, is not before today and is date.
     body('expiredAt').if(value => value != null).isAfter(today.toString()).withMessage('Your Selected date is past'),
     body('expiredAt').if(value => value != null).isAfter(today.toString()).withMessage('Your Selected date is past'),
     // validate the length of description is max 100.
     // validate the length of description is max 100.
@@ -103,6 +117,15 @@ module.exports = (crowi) => {
 
 
   router.post('/', loginRequired, csrf, validator.shareLinkStatus, apiV3FormValidator, async(req, res) => {
   router.post('/', loginRequired, csrf, validator.shareLinkStatus, apiV3FormValidator, async(req, res) => {
     const { relatedPage, expiredAt, description } = req.body;
     const { relatedPage, expiredAt, description } = req.body;
+
+    const page = await Page.findByIdAndViewer(relatedPage, req.user);
+
+    if (page == null) {
+      const msg = 'Page is not found or forbidden';
+      logger.error('Error', msg);
+      return res.apiv3Err(new ErrorV3(msg, 'post-shareLink-failed'));
+    }
+
     const ShareLink = crowi.model('ShareLink');
     const ShareLink = crowi.model('ShareLink');
 
 
     try {
     try {
@@ -116,6 +139,12 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
+
+  validator.deleteShareLinks = [
+    // validate the page id is MongoId
+    query('relatedPage').isMongoId().withMessage('Page Id is required'),
+  ];
+
   /**
   /**
   * @swagger
   * @swagger
   *
   *
@@ -135,9 +164,17 @@ module.exports = (crowi) => {
   *          200:
   *          200:
   *            description: Succeeded to delete o all share links related one page
   *            description: Succeeded to delete o all share links related one page
   */
   */
-  router.delete('/', loginRequired, csrf, async(req, res) => {
+  router.delete('/', loginRequired, csrf, validator.deleteShareLinks, apiV3FormValidator, async(req, res) => {
     const { relatedPage } = req.query;
     const { relatedPage } = req.query;
 
 
+    const page = await Page.findByIdAndViewer(relatedPage, req.user);
+
+    if (page == null) {
+      const msg = 'Page is not found or forbidden';
+      logger.error('Error', msg);
+      return res.apiv3Err(new ErrorV3(msg, 'delete-shareLinks-for-page-failed'));
+    }
+
     try {
     try {
       const deletedShareLink = await ShareLink.remove({ relatedPage });
       const deletedShareLink = await ShareLink.remove({ relatedPage });
       return res.apiv3(deletedShareLink);
       return res.apiv3(deletedShareLink);
@@ -174,6 +211,10 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
+  validator.deleteShareLink = [
+    param('id').isMongoId().withMessage('ShareLink Id is required'),
+  ];
+
   /**
   /**
   * @swagger
   * @swagger
   *
   *
@@ -192,11 +233,22 @@ module.exports = (crowi) => {
   *          200:
   *          200:
   *            description: Succeeded to delete one share link
   *            description: Succeeded to delete one share link
   */
   */
-  router.delete('/:id', loginRequired, csrf, async(req, res) => {
+  router.delete('/:id', loginRequired, csrf, validator.deleteShareLink, apiV3FormValidator, async(req, res) => {
     const { id } = req.params;
     const { id } = req.params;
 
 
     try {
     try {
-      const deletedShareLink = await ShareLink.findOneAndRemove({ _id: id });
+      const deletedShareLink = await ShareLink.findOne({ _id: id });
+
+      // check permission
+      const page = await Page.findByIdAndViewer(deletedShareLink.relatedPage, req.user);
+      if (page == null) {
+        const msg = 'Page is not found or forbidden';
+        logger.error('Error', msg);
+        return res.apiv3Err(new ErrorV3(msg, 'delete-shareLink-failed'));
+      }
+
+      // remove
+      await deletedShareLink.remove();
       return res.apiv3({ deletedShareLink });
       return res.apiv3({ deletedShareLink });
     }
     }
     catch (err) {
     catch (err) {

+ 5 - 0
yarn.lock

@@ -6036,6 +6036,11 @@ express-form@~0.12.0:
     object-additions "^0.5.1"
     object-additions "^0.5.1"
     validator "^2.1.0"
     validator "^2.1.0"
 
 
+express-mongo-sanitize@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/express-mongo-sanitize/-/express-mongo-sanitize-2.1.0.tgz#a8c647787c25ded6e97b5e864d113e7687c5d471"
+  integrity sha512-ELGeH/Tx+kJGn3klCzSmOewfN1ezJQrkqzq83dl/K3xhd5PUbvLtiD5CiuYRmQfoZPL4rUEVjANf/YjE2BpTWQ==
+
 express-session@^1.16.1:
 express-session@^1.16.1:
   version "1.16.1"
   version "1.16.1"
   resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.16.1.tgz#251ff9776c59382301de6c8c33411af357ed439c"
   resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.16.1.tgz#251ff9776c59382301de6c8c33411af357ed439c"