CVE-2021-21245 (published 2021-01-15T21:15Z)

OneDev is an all-in-one devops platform. In OneDev before version 4.0.3, AttachmentUploadServlet also saves user controlled data (`request.getInputStream()`) to a user specified location (`request.getHeader("File-Name")`). This issue may lead to arbitrary file upload which can be used to upload a WebShell to OneDev server. This issue is addressed in 4.0.3 by only allowing uploaded file to be in attachments folder. The webshell issue is not possible as OneDev never executes files in attachments folder.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-434
cwe_name: Unrestricted Upload of File with Dangerous Type
description: The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.
extended_description: 
            
            
            
            
            
         
         

Fix information:
repo_url: https://github.com/theonedev/onedev
commit_details: repo_url: https://github.com/theonedev/onedev
author: Robin Shen
author_date: 2020-11-20 10:08:14+08:00
msg: Fix the issue that uploaded file can be stored anywhere OneDev has write
permissions over
repository: repo_url: https://github.com/theonedev/onedev
repo_name: theonedev/onedev
description: Git Server with CI/CD, Kanban, and Packages. Seamless integration. Unparalleled experience.
repo_language: Java
owner: theonedev
file_changes: filename: FilenameUtils.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,9 @@
+package io.onedev.server.util;
+
+public class FilenameUtils extends org.apache.commons.io.FilenameUtils {
+
+	public static String sanitizeFilename(String fileName) {
+		return fileName.replace("..", "_").replace('/', '_').replace('\\', '_');
+	}
+
+}
\ No newline at end of file

programming_language: Java
filename: InsertUrlPanel.java
change_type: ModificationType.MODIFY
diff: @@ -53,6 +53,7 @@ import io.onedev.server.git.BlobIdent;
 import io.onedev.server.git.BlobIdentFilter;
 import io.onedev.server.git.exception.GitException;
 import io.onedev.server.model.Project;
+import io.onedev.server.util.FilenameUtils;
 import io.onedev.server.util.UrlUtils;
 import io.onedev.server.web.ajaxlistener.ConfirmClickListener;
 import io.onedev.server.web.behavior.ReferenceInputBehavior;
@@ -402,7 +403,8 @@ abstract class InsertUrlPanel extends Panel {
 					String attachmentName;
 					FileUpload upload = uploads.iterator().next();
 					try (InputStream is = upload.getInputStream()) {
-						attachmentName = attachmentSupport.saveAttachment(upload.getClientFileName(), is);
+						attachmentName = attachmentSupport.saveAttachment(
+								FilenameUtils.sanitizeFilename(upload.getClientFileName()), is);
 					} catch (IOException e) {
 						throw new RuntimeException(e);
 					}

programming_language: Java
filename: MarkdownEditor.java
change_type: ModificationType.MODIFY
diff: @@ -60,6 +60,7 @@ import io.onedev.server.model.Issue;
 import io.onedev.server.model.Project;
 import io.onedev.server.model.PullRequest;
 import io.onedev.server.model.User;
+import io.onedev.server.util.FilenameUtils;
 import io.onedev.server.util.markdown.MarkdownManager;
 import io.onedev.server.util.validation.ProjectNameValidator;
 import io.onedev.server.web.avatar.AvatarManager;
@@ -477,7 +478,8 @@ public class MarkdownEditor extends FormComponentPanel<String> {
 				HttpServletRequest request = (HttpServletRequest) RequestCycle.get().getRequest().getContainerRequest();
 				HttpServletResponse response = (HttpServletResponse) RequestCycle.get().getResponse().getContainerResponse();
 				try {
-					String fileName = URLDecoder.decode(request.getHeader("File-Name"), StandardCharsets.UTF_8.name());
+					String fileName = FilenameUtils.sanitizeFilename(
+							URLDecoder.decode(request.getHeader("File-Name"), StandardCharsets.UTF_8.name()));
 					String attachmentName = getAttachmentSupport().saveAttachment(fileName, request.getInputStream());
 					response.getWriter().print(URLEncoder.encode(attachmentName, StandardCharsets.UTF_8.name()));
 					response.setStatus(HttpServletResponse.SC_OK);

programming_language: Java
filename: ProjectBlobPage.java
change_type: ModificationType.MODIFY
diff: @@ -85,6 +85,7 @@ import io.onedev.server.search.code.hit.QueryHit;
 import io.onedev.server.search.code.query.BlobQuery;
 import io.onedev.server.search.code.query.TextQuery;
 import io.onedev.server.security.SecurityUtils;
+import io.onedev.server.util.FilenameUtils;
 import io.onedev.server.util.script.identity.JobIdentity;
 import io.onedev.server.util.script.identity.ScriptIdentity;
 import io.onedev.server.util.script.identity.ScriptIdentityAware;
@@ -1413,7 +1414,7 @@ public class ProjectBlobPage extends ProjectPage implements BlobRenderContext, S
 		BlobIdent blobIdent = getBlobIdent();
 		
 		for (FileUpload upload: uploads) {
-			String blobPath = upload.getClientFileName();
+			String blobPath = FilenameUtils.sanitizeFilename(upload.getClientFileName());
 			if (parentPath != null)
 				blobPath = parentPath + "/" + blobPath;
 			

programming_language: Java