CVE-2022-38301: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: Onedev v7.4.14 contains a path traversal vulnerability which allows attackers to access restricted files and directories via uploading a crafted JAR file into the directory /opt/onedev/lib.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 8.8
Published: 2022-09-14T21:15Z
Repository: https://github.com/theonedev/onedev
Language: Java
Files changed: server-core/src/main/java/io/onedev/server/web/page/project/builds/detail/artifacts/ArtifactUploadPanel.java
Commit date: 2022-08-09 16:02:48 +0800
Commit message: Fix issue #842 - A security vulnerability relating to path traversal
Diff: diff --git a/server-core/src/main/java/io/onedev/server/web/page/project/builds/detail/artifacts/ArtifactUploadPanel.java b/server-core/src/main/java/io/onedev/server/web/page/project/builds/detail/artifacts/ArtifactUploadPanel.java
index a090503fe19..6018f736c02 100644
--- a/server-core/src/main/java/io/onedev/server/web/page/project/builds/detail/artifacts/ArtifactUploadPanel.java
+++ b/server-core/src/main/java/io/onedev/server/web/page/project/builds/detail/artifacts/ArtifactUploadPanel.java
@@ -76,34 +76,39 @@ public abstract class ArtifactUploadPanel extends Panel {
 
 			@Override
 			protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
 				super.onSubmit(target, form);
 				
-				LockUtils.write(getBuild().getArtifactsLockKey(), new Callable<Void>() {
+				if (directory.contains("..")) {
+					error("'..' is not allowed in the directory");
+					target.add(feedback);
+				} else {
+					LockUtils.write(getBuild().getArtifactsLockKey(), new Callable<Void>() {
 
-					@Override
-					public Void call() throws Exception {
-						File artifactsDir = getBuild().getArtifactsDir();
-						for (FileUpload upload: uploads) {
-							String filePath = FilenameUtils.sanitizeFilename(upload.getFileName());
-							if (directory != null)
-								filePath = directory + "/" + filePath;
-							File file = new File(artifactsDir, filePath);
-							FileUtils.createDir(file.getParentFile());
-							try (	InputStream is = upload.getInputStream();
-									OutputStream os = new FileOutputStream(file)) {
-								IOUtils.copy(is, os);
-							} finally {
-								upload.release();
+						@Override
+						public Void call() throws Exception {
+							File artifactsDir = getBuild().getArtifactsDir();
+							for (FileUpload upload: uploads) {
+								String filePath = FilenameUtils.sanitizeFilename(upload.getFileName());
+								if (directory != null)
+									filePath = directory + "/" + filePath;
+								File file = new File(artifactsDir, filePath);
+								FileUtils.createDir(file.getParentFile());
+								try (	InputStream is = upload.getInputStream();
+										OutputStream os = new FileOutputStream(file)) {
+									IOUtils.copy(is, os);
+								} finally {
+									upload.release();
+								}
 							}
+							return null;
 						}
-						return null;
-					}
+						
+					});
 					
-				});
-				
-				onUploaded(target);
+					onUploaded(target);
+				}
 			}
 
 			@Override
 			protected void onError(AjaxRequestTarget target, Form<?> form) {
 				super.onError(target, form);

Vulnerable code: 				LockUtils.write(getBuild().getArtifactsLockKey(), new Callable<Void>() {
					@Override
					public Void call() throws Exception {
						File artifactsDir = getBuild().getArtifactsDir();
						for (FileUpload upload: uploads) {
							String filePath = FilenameUtils.sanitizeFilename(upload.getFileName());
							if (directory != null)
								filePath = directory + "/" + filePath;
							File file = new File(artifactsDir, filePath);
							FileUtils.createDir(file.getParentFile());
							try (	InputStream is = upload.getInputStream();
									OutputStream os = new FileOutputStream(file)) {
								IOUtils.copy(is, os);
							} finally {
								upload.release();
						return null;
					}
				});
				
				onUploaded(target);
Fixed code: 				if (directory.contains("..")) {
					error("'..' is not allowed in the directory");
					target.add(feedback);
				} else {
					LockUtils.write(getBuild().getArtifactsLockKey(), new Callable<Void>() {
						@Override
						public Void call() throws Exception {
							File artifactsDir = getBuild().getArtifactsDir();
							for (FileUpload upload: uploads) {
								String filePath = FilenameUtils.sanitizeFilename(upload.getFileName());
								if (directory != null)
									filePath = directory + "/" + filePath;
								File file = new File(artifactsDir, filePath);
								FileUtils.createDir(file.getParentFile());
								try (	InputStream is = upload.getInputStream();
										OutputStream os = new FileOutputStream(file)) {
									IOUtils.copy(is, os);
								} finally {
									upload.release();
								}
							return null;
						
					});
					onUploaded(target);
				}