CVE-2021-21248: Improper Control of Generation of Code ('Code Injection') (CWE-94)
value: OneDev is an all-in-one devops platform. In OneDev before version 4.0.3, there is a critical vulnerability involving the build endpoint parameters. InputSpec is used to define parameters of a Build spec. It does so by using dynamically generated Groovy classes. A user able to control job parameters can run arbitrary code on OneDev's server by injecting arbitrary Groovy code. The ultimate result is in the injection of a static constructor that will run arbitrary code. For a full example refer to the referenced GHSA. This issue was addressed in 4.0.3 by escaping special characters such as quote from user input.
CWE description: The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
CVSS v3 base score: 8.8
Published: 2021-01-15T21:15Z
Repository: https://github.com/theonedev/onedev
Language: Java
Files changed: server-core/src/main/java/io/onedev/server/model/support/inputspec/InputSpec.java, server-core/src/main/java/io/onedev/server/model/support/inputspec/textinput/TextInput.java, server-product/src/test/java/io/onedev/server/product/Test.java
Commit date: 2020-11-20 16:06:13 +0800
Commit message: Escape pattern field of build job param to prevent security vulnerability

Diff: diff --git a/server-core/src/main/java/io/onedev/server/model/support/inputspec/InputSpec.java b/server-core/src/main/java/io/onedev/server/model/support/inputspec/InputSpec.java
index 814ef2d4cf..6fdf788e24 100644
--- a/server-core/src/main/java/io/onedev/server/model/support/inputspec/InputSpec.java
+++ b/server-core/src/main/java/io/onedev/server/model/support/inputspec/InputSpec.java
@@ -103,11 +103,11 @@ public abstract class InputSpec implements Serializable {
 	
 	public List<String> getPossibleValues() {
 		return Lists.newArrayList();
 	}
 
-	protected String escape(String string) {
+	public static String escape(String string) {
 		String escaped = JavaEscape.escapeJava(string);
 		// escape $ character since it has special meaning in groovy string
 		escaped = escaped.replace("$", "\\$");
 
 		return escaped;
diff --git a/server-core/src/main/java/io/onedev/server/model/support/inputspec/textinput/TextInput.java b/server-core/src/main/java/io/onedev/server/model/support/inputspec/textinput/TextInput.java
index a5d0a51f80..229162fec1 100755
--- a/server-core/src/main/java/io/onedev/server/model/support/inputspec/textinput/TextInput.java
+++ b/server-core/src/main/java/io/onedev/server/model/support/inputspec/textinput/TextInput.java
@@ -13,20 +13,21 @@ import io.onedev.server.model.support.inputspec.textinput.defaultvalueprovider.D
 
 public class TextInput {
 
 	public static String getPropertyDef(InputSpec inputSpec, Map<String, Integer> indexes, 
 			String pattern, DefaultValueProvider defaultValueProvider) {
+		pattern = InputSpec.escape(pattern);
 		int index = indexes.get(inputSpec.getName());
 		StringBuffer buffer = new StringBuffer();
 		inputSpec.appendField(buffer, index, "String");
 		inputSpec.appendCommonAnnotations(buffer, index);
 		if (!inputSpec.isAllowEmpty())
 			buffer.append("    @NotEmpty\n");
 		if (pattern != null)
 			buffer.append("    @Pattern(regexp=\"" + pattern + "\", message=\"Should match regular expression: " + pattern + "\")\n");
 		inputSpec.appendMethods(buffer, index, "String", null, defaultValueProvider);
-		
+
 		return buffer.toString();
 	}
 
 	public static Object convertToObject(List<String> strings) {
 		if (strings.size() == 0)
diff --git a/server-product/src/test/java/io/onedev/server/product/Test.java b/server-product/src/test/java/io/onedev/server/product/Test.java
index 8ce47824ed..1bc98e632b 100644
--- a/server-product/src/test/java/io/onedev/server/product/Test.java
+++ b/server-product/src/test/java/io/onedev/server/product/Test.java
@@ -3,7 +3,7 @@ package io.onedev.server.product;
 public class Test {
 
 	@org.junit.Test
 	public void test() {
 	}		
-	
+
 }
\ No newline at end of file

Vulnerable code: 	protected String escape(String string) {
		
	
Fixed code: 	public static String escape(String string) {
		pattern = InputSpec.escape(pattern);