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

Merge pull request #9712 from weseek/fix/161421-prevent-installation-outside-the-plugin-directory

fix:  Prevent GrowiPlugin from being downloaded outside the plugin directory
Yuki Takei 1 год назад
Родитель
Сommit
0edd954e69

+ 38 - 7
apps/app/src/features/growi-plugin/server/services/growi-plugin/growi-plugin.ts

@@ -72,8 +72,16 @@ export class GrowiPluginService implements IGrowiPluginService {
 
 
       // if not exists repository in file system, download latest plugin repository
       // if not exists repository in file system, download latest plugin repository
       for await (const growiPlugin of growiPlugins) {
       for await (const growiPlugin of growiPlugins) {
-        const pluginPath = path.join(PLUGIN_STORING_PATH, growiPlugin.installedPath);
-        const organizationName = path.join(PLUGIN_STORING_PATH, growiPlugin.organizationName);
+        let pluginPath :fs.PathLike|undefined;
+        let organizationName :fs.PathLike|undefined;
+        try {
+          pluginPath = this.joinAndValidatePath(PLUGIN_STORING_PATH, growiPlugin.installedPath);
+          organizationName = this.joinAndValidatePath(PLUGIN_STORING_PATH, growiPlugin.organizationName);
+        }
+        catch (err) {
+          logger.error(err);
+          continue;
+        }
         if (fs.existsSync(pluginPath)) {
         if (fs.existsSync(pluginPath)) {
           continue;
           continue;
         }
         }
@@ -301,22 +309,34 @@ export class GrowiPluginService implements IGrowiPluginService {
     }
     }
 
 
     try {
     try {
-      const growiPluginsPath = path.join(PLUGIN_STORING_PATH, growiPlugins.installedPath);
-      await deleteFolder(growiPluginsPath);
+      await GrowiPlugin.deleteOne({ _id: pluginId });
     }
     }
     catch (err) {
     catch (err) {
       logger.error(err);
       logger.error(err);
-      throw new Error('Failed to delete plugin repository.');
+      throw new Error('Failed to delete plugin from GrowiPlugin documents.');
     }
     }
 
 
+    let growiPluginsPath: fs.PathLike | undefined;
     try {
     try {
-      await GrowiPlugin.deleteOne({ _id: pluginId });
+      growiPluginsPath = this.joinAndValidatePath(PLUGIN_STORING_PATH, growiPlugins.installedPath);
     }
     }
     catch (err) {
     catch (err) {
       logger.error(err);
       logger.error(err);
-      throw new Error('Failed to delete plugin from GrowiPlugin documents.');
+      throw new Error('The installedPath for the plugin is invalid, and the plugin has already been removed.');
     }
     }
 
 
+    if (growiPluginsPath && fs.existsSync(growiPluginsPath)) {
+      try {
+        await deleteFolder(growiPluginsPath);
+      }
+      catch (err) {
+        logger.error(err);
+        throw new Error('Failed to delete plugin repository.');
+      }
+    }
+    else {
+      logger.warn(`Plugin path does not exist : ${growiPluginsPath}`);
+    }
     return growiPlugins.meta.name;
     return growiPlugins.meta.name;
   }
   }
 
 
@@ -402,6 +422,17 @@ export class GrowiPluginService implements IGrowiPluginService {
     return entries;
     return entries;
   }
   }
 
 
+  private joinAndValidatePath(baseDir: string, ...paths: string[]):fs.PathLike {
+    const joinedPath = path.join(baseDir, ...paths);
+    if (!joinedPath.startsWith(baseDir)) {
+      throw new Error(
+        'Invalid plugin path detected! Access outside of the allowed directory is not permitted.'
+        + `\nAttempted Path: ${joinedPath}`,
+      );
+    }
+    return joinedPath;
+  }
+
 }
 }
 
 
 
 

+ 16 - 1
apps/app/src/server/service/import/overwrite-function.ts

@@ -5,6 +5,10 @@ import {
   Types, type Document,
   Types, type Document,
 } from 'mongoose';
 } from 'mongoose';
 
 
+import loggerFactory from '~/utils/logger';
+
+const logger = loggerFactory('growi:service:import:overwrite-function');
+
 
 
 const { ObjectId } = Types;
 const { ObjectId } = Types;
 
 
@@ -21,6 +25,10 @@ export type OverwriteFunction = (value: unknown, ctx: { document: Document, prop
  * @see https://mongoosejs.com/docs/api/schematype.html#schematype_SchemaType-cast
  * @see https://mongoosejs.com/docs/api/schematype.html#schematype_SchemaType-cast
  */
  */
 export const keepOriginal: OverwriteFunction = (value, { document, schema, propertyName }) => {
 export const keepOriginal: OverwriteFunction = (value, { document, schema, propertyName }) => {
+  if (value == null) {
+    return value;
+  }
+
   // Model
   // Model
   if (schema != null && schema.path(propertyName) != null) {
   if (schema != null && schema.path(propertyName) != null) {
     const schemaType = schema.path(propertyName);
     const schemaType = schema.path(propertyName);
@@ -30,7 +38,14 @@ export const keepOriginal: OverwriteFunction = (value, { document, schema, prope
     // ref: https://github.com/Automattic/mongoose/blob/6.11.4/lib/schema/array.js#L334
     // ref: https://github.com/Automattic/mongoose/blob/6.11.4/lib/schema/array.js#L334
     document.schema = schema;
     document.schema = schema;
 
 
-    return schemaType.cast(value, document, true);
+    try {
+      return schemaType.cast(value, document, true);
+    }
+    catch (e) {
+      logger.warn(`Failed to cast value for ${propertyName}`, e);
+      // return original value
+      return value;
+    }
   }
   }
 
 
   // _id
   // _id