CVE-2023-3431 (published 2023-06-27T15:15Z)

Improper Access Control in GitHub repository plantuml/plantuml prior to 1.2023.9.

CVSS v3 base score: 5.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)

Weakness information:
cwe_id: CWE-284
cwe_name: Improper Access Control
description: The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
extended_description: 
            Access control involves the use of several protection mechanisms such as:
            
               Authentication (proving the identity of an actor)
               Authorization (ensuring that a given actor can access a resource), and
               Accountability (tracking of activities that were performed)
            
            When any mechanism is not applied or otherwise fails, attackers can compromise the security of the product by gaining privileges, reading sensitive information, executing commands, evading detection, etc.
            There are two distinct behaviors that can introduce access control weaknesses:
               
                  Specification: incorrect privileges, permissions, ownership, etc. are explicitly specified for either the user or the resource (for example, setting a password file to be world-writable, or giving administrator capabilities to a guest user). This action could be performed by the program or the administrator.
                  Enforcement: the mechanism contains errors that prevent it from properly enforcing the specified access control requirements (e.g., allowing the user to specify their own privileges, or allowing a syntactically-incorrect ACL to produce insecure settings). This problem occurs within the program itself, in that it does not actually enforce the intended security policy that the administrator specifies.
               
         
		 

Fix information:
repo_url: https://github.com/plantuml/plantuml
commit_details: repo_url: https://github.com/plantuml/plantuml
author: Arnaud Roques
author_date: 2023-06-13 12:41:34+02:00
msg: feat: remove legacy ALLOW_INCLUDE use PLANTUML_SECURITY_PROFILE instead
https://github.com/plantuml/plantuml-server/issues/232
repository: repo_url: https://github.com/plantuml/plantuml
repo_name: plantuml/plantuml
description: Generate diagrams from textual description
repo_language: Java
owner: plantuml
file_changes: filename: gradle.properties
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,4 @@
 # Warning, "version" should be the same in gradle.properties and Version.java
 # Any idea anyone how to magically synchronize those :-) ?
-version = 1.2023.9beta4
+version = 1.2023.9beta5
 org.gradle.workers.max = 3
\ No newline at end of file

programming_language: Batchfile
filename: OptionFlags.java
change_type: ModificationType.MODIFY
diff: @@ -74,11 +74,11 @@ public class OptionFlags {
 	// static public boolean GRAPHVIZCACHE = false;
 	// static public final boolean TRACE_DOT = false;
 
-	static public boolean ALLOW_INCLUDE = true;
-
-	static public void setAllowIncludeFalse() {
-		ALLOW_INCLUDE = false;
-	}
+//	static public boolean ALLOW_INCLUDE = true;
+//
+//	static public void setAllowIncludeFalse() {
+//		ALLOW_INCLUDE = false;
+//	}
 
 	static public void setMaxPixel(int max) {
 	}

programming_language: Java
filename: ImportedFiles.java
change_type: ModificationType.MODIFY
diff: @@ -40,7 +40,6 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
-import net.sourceforge.plantuml.OptionFlags;
 import net.sourceforge.plantuml.file.AFile;
 import net.sourceforge.plantuml.file.AFileRegular;
 import net.sourceforge.plantuml.file.AFileZipEntry;
@@ -63,9 +62,9 @@ public class ImportedFiles {
 	}
 
 	public ImportedFiles withCurrentDir(AParentFolder newCurrentDir) {
-		if (newCurrentDir == null) {
+		if (newCurrentDir == null) 
 			return this;
-		}
+		
 		return new ImportedFiles(imported, newCurrentDir);
 	}
 
@@ -82,27 +81,27 @@ public class ImportedFiles {
 		// Log.info("ImportedFiles::getAFile nameOrPath = " + nameOrPath);
 		// Log.info("ImportedFiles::getAFile currentDir = " + currentDir);
 		final AParentFolder dir = currentDir;
-		if (dir == null || isAbsolute(nameOrPath)) {
+		if (dir == null || isAbsolute(nameOrPath)) 
 			return new AFileRegular(new SFile(nameOrPath).getCanonicalFile());
-		}
+		
 		// final File filecurrent = SecurityUtils.File(dir.getAbsoluteFile(),
 		// nameOrPath);
 		final AFile filecurrent = dir.getAFile(nameOrPath);
 		Log.info("ImportedFiles::getAFile filecurrent = " + filecurrent);
-		if (filecurrent != null && filecurrent.isOk()) {
+		if (filecurrent != null && filecurrent.isOk()) 
 			return filecurrent;
-		}
+		
 		for (SFile d : getPath()) {
 			if (d.isDirectory()) {
 				final SFile file = d.file(nameOrPath);
-				if (file.exists()) {
+				if (file.exists()) 
 					return new AFileRegular(file.getCanonicalFile());
-				}
+				
 			} else if (d.isFile()) {
 				final AFileZipEntry zipEntry = new AFileZipEntry(d, nameOrPath);
-				if (zipEntry.isOk()) {
+				if (zipEntry.isOk()) 
 					return zipEntry;
-				}
+				
 			}
 		}
 		return filecurrent;
@@ -150,27 +149,25 @@ public class ImportedFiles {
 			file = getAFile(filename.substring(0, idx));
 			entry = filename.substring(idx + 1);
 		}
-		if (isAllowed(file) == false)
+		// if (isAllowed(file) == false)
+		if (file == null || file.getUnderlyingFile().isFileOk() == false)
 			return FileWithSuffix.none();
 
 		return new FileWithSuffix(filename, suffix, file, entry);
 	}
 
-	private boolean isAllowed(AFile file) throws IOException {
-		// ::comment when __CORE__
-		if (OptionFlags.ALLOW_INCLUDE)
-			return true;
-
-		if (file != null) {
-			final SFile folder = file.getSystemFolder();
-			// System.err.println("canonicalPath=" + path + " " + folder + " " +
-			// INCLUDE_PATH);
-			if (includePath().contains(folder))
-				return true;
-
-		}
-		// ::done
-		return false;
-	}
+//	private boolean isAllowed(AFile file) throws IOException {
+//		// ::comment when __CORE__
+//		if (file != null) {
+//			final SFile folder = file.getSystemFolder();
+//			// System.err.println("canonicalPath=" + path + " " + folder + " " +
+//			// INCLUDE_PATH);
+//			if (includePath().contains(folder) && folder.isFileOk())
+//				return true;
+//
+//		}
+//		// ::done
+//		return false;
+//	}
 
 }

programming_language: Java
filename: SFile.java
change_type: ModificationType.MODIFY
diff: @@ -117,9 +117,9 @@ public class SFile implements Comparable<SFile> {
 	}
 
 	public static SFile fromFile(File internal) {
-		if (internal == null) {
+		if (internal == null)
 			return null;
-		}
+
 		return new SFile(internal);
 	}
 
@@ -257,7 +257,7 @@ public class SFile implements Comparable<SFile> {
 	/**
 	 * Check SecurityProfile to see if this file can be open.
 	 */
-	private boolean isFileOk() {
+	public boolean isFileOk() {
 		// ::comment when __CORE__
 		if (SecurityUtils.getSecurityProfile() == SecurityProfile.SANDBOX)
 			// In SANDBOX, we cannot read any files

programming_language: Java
filename: SURL.java
change_type: ModificationType.MODIFY
diff: @@ -216,7 +216,7 @@ public class SURL {
 	/**
 	 * Check SecurityProfile to see if this URL can be opened.
 	 */
-	private boolean isUrlOk() {
+	public boolean isUrlOk() {
 		// ::comment when __CORE__
 		if (SecurityUtils.getSecurityProfile() == SecurityProfile.SANDBOX)
 			// In SANDBOX, we cannot read any URL

programming_language: Java
filename: SecurityProfile.java
change_type: ModificationType.MODIFY
diff: @@ -55,7 +55,7 @@ package net.sourceforge.plantuml.security;
  * 
  */
 public enum SecurityProfile {
-    // ::remove folder when __HAXE__
+	// ::remove folder when __HAXE__
 
 	/**
 	 * Running in SANDBOX mode is completely secure. No local file can be read
@@ -161,4 +161,17 @@ public enum SecurityProfile {
 		throw new AssertionError();
 	}
 
+	public boolean canWeReadThisEnvironmentVariable(String name) {
+		if (name == null)
+			return false;
+
+		if (this == UNSECURE)
+			return true;
+		
+		if (name.toLowerCase().startsWith("plantuml"))
+			return true;
+		
+		return true;
+	}
+
 }

programming_language: Java
filename: SecurityUtils.java
change_type: ModificationType.MODIFY
diff: @@ -222,16 +222,16 @@ public class SecurityUtils {
 		return System.getenv(alternateName);
 	}
 
-	/**
-	 * Checks the environment variable and returns true if the variable is used in
-	 * security context. In this case, the value should not be displayed in scripts.
-	 *
-	 * @param name Environment variable to check
-	 * @return true, if this is a secret variable
-	 */
-	public static boolean isSecurityEnv(String name) {
-		return name != null && name.toLowerCase().startsWith("plantuml.security.");
-	}
+//	/**
+//	 * Checks the environment variable and returns true if the variable is used in
+//	 * security context. In this case, the value should not be displayed in scripts.
+//	 *
+//	 * @param name Environment variable to check
+//	 * @return true, if this is a secret variable
+//	 */
+//	public static boolean isSecurityEnv(String name) {
+//		return name != null && name.toLowerCase().startsWith("plantuml.security.");
+//	}
 
 	/**
 	 * Configuration for Non-SSL authentication methods.

programming_language: Java
filename: FileExists.java
change_type: ModificationType.MODIFY
diff: @@ -38,7 +38,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import net.sourceforge.plantuml.OptionFlags;
 import net.sourceforge.plantuml.security.SFile;
 import net.sourceforge.plantuml.tim.EaterException;
 import net.sourceforge.plantuml.tim.EaterExceptionLocated;
@@ -61,18 +60,13 @@ public class FileExists extends SimpleReturnFunction {
 	public TValue executeReturnFunction(TContext context, TMemory memory, LineLocation location, List<TValue> values,
 			Map<String, TValue> named) throws EaterException, EaterExceptionLocated {
 		// ::comment when __CORE__
-		if (OptionFlags.ALLOW_INCLUDE == false)
-			// ::done
-			return TValue.fromBoolean(false);
-		// ::comment when __CORE__
-
 		final String path = values.get(0).toString();
-		return TValue.fromBoolean(fileExists(path));
+		return TValue.fromBoolean(new SFile(path).exists());
 		// ::done
-	}
 
-	private boolean fileExists(String path) {
-		final SFile f = new SFile(path);
-		return f.exists();
+		// ::uncomment when __CORE__
+		// return TValue.fromBoolean(false);
+		// ::done
 	}
+
 }

programming_language: Java
filename: Getenv.java
change_type: ModificationType.MODIFY
diff: @@ -38,7 +38,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import net.sourceforge.plantuml.OptionFlags;
 import net.sourceforge.plantuml.security.SecurityUtils;
 import net.sourceforge.plantuml.tim.EaterException;
 import net.sourceforge.plantuml.tim.EaterExceptionLocated;
@@ -61,18 +60,16 @@ public class Getenv extends SimpleReturnFunction {
 	public TValue executeReturnFunction(TContext context, TMemory memory, LineLocation location, List<TValue> values,
 			Map<String, TValue> named) throws EaterException, EaterExceptionLocated {
 		// ::comment when __CORE__
-		if (OptionFlags.ALLOW_INCLUDE == false)
-			// ::done
-			return TValue.fromString("");
-		// ::comment when __CORE__
-
-		final String name = values.get(0).toString();
-		final String value = getenv(name);
+		final String value = getenv(values.get(0).toString());
 		if (value == null)
 			return TValue.fromString("");
 
 		return TValue.fromString(value);
 		// ::done
+
+		// ::uncomment when __CORE__
+		// return TValue.fromString("");
+		// ::done
 	}
 
 	// ::comment when __CORE__
@@ -81,8 +78,9 @@ public class Getenv extends SimpleReturnFunction {
 		// A plantuml server should have an own SecurityManager to
 		// avoid access to properties and environment variables, but we should
 		// also stop here in other deployments.
-		if (SecurityUtils.isSecurityEnv(name))
+		if (SecurityUtils.getSecurityProfile().canWeReadThisEnvironmentVariable(name) == false)
 			return null;
+		
 		final String env = System.getProperty(name);
 		if (env != null)
 			return env;

programming_language: Java
filename: LoadJson.java
change_type: ModificationType.MODIFY
diff: @@ -165,9 +165,8 @@ public class LoadJson extends SimpleReturnFunction {
 		byte[] byteData = null;
 		if (path.startsWith("http://") || path.startsWith("https://")) {
 			final SURL url = SURL.create(path);
-			if (url == null)
-				throw EaterException.located("load JSON: Invalid URL " + path);
-			byteData = url.getBytes();
+			if (url != null)
+				byteData = url.getBytes();
 			// ::comment when __CORE__
 		} else {
 			try {
@@ -179,7 +178,6 @@ public class LoadJson extends SimpleReturnFunction {
 				}
 			} catch (IOException e) {
 				Logme.error(e);
-				throw EaterException.located("load JSON: Cannot read file " + path + ". " + e.getMessage());
 			}
 			// ::done
 		}

programming_language: Java
filename: LicenseInfo.java
change_type: ModificationType.MODIFY
diff: @@ -108,8 +108,8 @@ public class LicenseInfo {
 
 	public static synchronized LicenseInfo retrieveNamedSlow() {
 		cache = LicenseInfo.NONE;
-		if (OptionFlags.ALLOW_INCLUDE == false)
-			return cache;
+//		if (OptionFlags.ALLOW_INCLUDE == false)
+//			return cache;
 
 		final String key = prefs.get("license", "");
 		if (key.length() > 0) {

programming_language: Java
filename: PSystemVersion.java
change_type: ModificationType.MODIFY
diff: @@ -175,9 +175,8 @@ public class PSystemVersion extends PlainStringsDiagram {
 		// :: done
 		// :: comment when __CORE__
 		GraphvizCrash.checkOldVersionWarning(strings);
-		if (OptionFlags.ALLOW_INCLUDE) {
-			if (SecurityUtils.getSecurityProfile() == SecurityProfile.UNSECURE)
-				strings.add("Loaded from " + Version.getJarPath());
+		if (SecurityUtils.getSecurityProfile() == SecurityProfile.UNSECURE) {
+			strings.add("Loaded from " + Version.getJarPath());
 
 			if (OptionFlags.getInstance().isWord()) {
 				strings.add("Word Mode");

programming_language: Java
filename: Version.java
change_type: ModificationType.MODIFY
diff: @@ -46,7 +46,7 @@ public class Version {
 
 	// Warning, "version" should be the same in gradle.properties and Version.java
 	// Any idea anyone how to magically synchronize those :-) ?
-	private static final String version = "1.2023.9beta4";
+	private static final String version = "1.2023.9beta5";
 
 	public static String versionString() {
 		return version;

programming_language: Java