Severity: High · CWE: CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
Summary
The file-upload controller mapped under the /anyone/ prefix joins request-controlled bucket and bizType values directly into the local storage path without any traversal filtering. Although the per-file name is a server-generated UUID, the directory components are attacker-controlled, so a caller can supply ../ sequences in bucket or bizType to escape the configured storage root and write uploaded content to an arbitrary writable location. The /anyone/** space is configured to skip URI-level authorization, so no privileged role is required; if the gateway's login-token check is not enforced for this path, the endpoint is fully unauthenticated.
Vulnerability chain
| Stage |
Component |
Location |
| Source |
POST /anyone/file/upload, params bizType / bucket |
FileAnyoneController.java:65 |
| Transform |
relative path = getPath(bizType, uniqueFileName); final = Paths.get(storagePath, bucket, path) |
AbstractFileStrategy.java:97, LocalFileStrategyImpl.java:46 |
| Sink |
new File(absolutePath) + FileUtils.copyInputStreamToFile(...) |
LocalFileStrategyImpl.java:49-50 |
The bucket request parameter and the bizType field both flow into the assembled absolute path. bizType becomes a path segment via getPath (joined with /), and bucket is concatenated by Paths.get. Because neither is validated, ../ in either value escapes the storagePath root. The stored filename itself is a UUID (getUniqueFileName), which does not help — the directory is fully attacker-directed.
Key code
The controller is mapped under the no-URI-authorization /anyone space (@RequestMapping("/anyone/file")):
// lamp-base/lamp-base-controller/.../file/controller/FileAnyoneController.java:47,63
@RequestMapping("/anyone/file")
...
@PostMapping(value = "/upload")
public R<FileResultVO> upload(@RequestParam(value = "file") MultipartFile file,
@Validated FileUploadVO fileUploadVO) {
return R.success(fileService.upload(file, fileUploadVO));
}
bizType and bucket are user-supplied and only @NotBlank-validated:
// lamp-base/lamp-base-entity/.../file/vo/param/FileUploadVO.java
@NotBlank(message = "请填写业务类型")
private String bizType;
private String bucket;
Both values are concatenated into the filesystem path with no canonicalization:
// lamp-base/lamp-base-biz/.../file/strategy/impl/local/LocalFileStrategyImpl.java:42-50
String uniqueFileName = getUniqueFileName(file);
String path = getPath(file.getBizType(), uniqueFileName);
String absolutePath = Paths.get(local.getStoragePath(), bucket, path).toString(); // :46 — traversal in bucket/path
java.io.File outFile = new java.io.File(absolutePath);
FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), outFile); // :50
getPath places bizType directly as a path segment:
// AbstractFileStrategy.java:97
protected String getPath(String bizType, String uniqueFileName) {
return new StringJoiner(StrPool.SLASH)
.add(bizType).add(getDateFolder()).add(uniqueFileName).toString();
}
/anyone/** is in the skip-authorization category (URI-permission checks bypassed for matching paths):
# lamp-support/lamp-boot-server/src/main/resources/application.yml:73-75
anyone: # 请求中 需要携带Tenant 且 需要携带Token(不需要登录),但不需要验证uri权限
ALL:
- /anyone/**
Proof of Concept
Request-level only. No URI-level authorization is required. With bucket (or bizType) containing ../../, the uploaded bytes land outside the storage root:
POST /anyone/file/upload HTTP/1.1
Host: <lamp-host>
Content-Type: multipart/form-data; boundary=----boundary
------boundary
Content-Disposition: form-data; name="bizType"
any
------boundary
Content-Disposition: form-data; name="bucket"
../../../../opt/app/config
------boundary
Content-Disposition: form-data; name="file"; filename="note.txt"
Content-Type: application/octet-stream
<uploaded file contents>
------boundary--
Impact
An attacker who can reach the /anyone/file/upload endpoint (no privileged role needed; unauthenticated if the gateway token check is not enforced for this path) can write attacker-controlled content to arbitrary writable paths on the host, escaping the intended storage directory. Overwriting configuration or classpath resources can lead to remote code execution. The traversal component (bucket/bizType) is fully attacker-controlled; only the leaf filename is a UUID.
Remediation
- Canonicalize the final path (
Path.normalize() / getCanonicalFile()) and reject any result outside the configured storagePath.
- Whitelist
bucket and bizType against an explicit allow-list, or reject values containing path separators, .., or absolute paths at the controller boundary.
- Require authentication and an explicit role for the upload endpoint; do not expose a file-write endpoint under the no-authorization
/anyone/** prefix.
- Derive the storage relative path from trusted server state rather than echoing request values into the filesystem path.
Severity: High · CWE: CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
Summary
The file-upload controller mapped under the
/anyone/prefix joins request-controlledbucketandbizTypevalues directly into the local storage path without any traversal filtering. Although the per-file name is a server-generated UUID, the directory components are attacker-controlled, so a caller can supply../sequences inbucketorbizTypeto escape the configured storage root and write uploaded content to an arbitrary writable location. The/anyone/**space is configured to skip URI-level authorization, so no privileged role is required; if the gateway's login-token check is not enforced for this path, the endpoint is fully unauthenticated.Vulnerability chain
POST /anyone/file/upload, paramsbizType/bucketFileAnyoneController.java:65getPath(bizType, uniqueFileName); final =Paths.get(storagePath, bucket, path)AbstractFileStrategy.java:97,LocalFileStrategyImpl.java:46new File(absolutePath)+FileUtils.copyInputStreamToFile(...)LocalFileStrategyImpl.java:49-50The
bucketrequest parameter and thebizTypefield both flow into the assembled absolute path.bizTypebecomes a path segment viagetPath(joined with/), andbucketis concatenated byPaths.get. Because neither is validated,../in either value escapes thestoragePathroot. The stored filename itself is a UUID (getUniqueFileName), which does not help — the directory is fully attacker-directed.Key code
The controller is mapped under the no-URI-authorization
/anyonespace (@RequestMapping("/anyone/file")):bizTypeandbucketare user-supplied and only@NotBlank-validated:Both values are concatenated into the filesystem path with no canonicalization:
getPathplacesbizTypedirectly as a path segment:/anyone/**is in the skip-authorization category (URI-permission checks bypassed for matching paths):Proof of Concept
Request-level only. No URI-level authorization is required. With
bucket(orbizType) containing../../, the uploaded bytes land outside the storage root:Impact
An attacker who can reach the
/anyone/file/uploadendpoint (no privileged role needed; unauthenticated if the gateway token check is not enforced for this path) can write attacker-controlled content to arbitrary writable paths on the host, escaping the intended storage directory. Overwriting configuration or classpath resources can lead to remote code execution. The traversal component (bucket/bizType) is fully attacker-controlled; only the leaf filename is a UUID.Remediation
Path.normalize()/getCanonicalFile()) and reject any result outside the configuredstoragePath.bucketandbizTypeagainst an explicit allow-list, or reject values containing path separators,.., or absolute paths at the controller boundary./anyone/**prefix.