dromara / dromara/lamp-cloud

Arbitrary file write via code generator (path traversal in `outputDir`)

Open
#411 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
5.8k
Forks
1.8k
PR merge metrics
No merged PRs in 30d

Description

**Severity:** High · **CWE:** CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
## Summary

The project-generation endpoint writes generated files to a path that is assembled directly from request-body fields without any canonicalization or base-directory confinement. An authenticated caller can supply an `outputDir` (and the `parent`/`projectPrefix` path components) containing `..` sequences or an absolute path, causing files to be written to arbitrary locations on the server filesystem — including configuration or classpath locations, which can lead to remote code execution depending on the deployment.

## Vulnerability chain

| Stage | Component | Location |
| --------- | ------------------------------------------------------------ | --------------------------------- |
| Source | `POST /defGenProject/generator`, body `ProjectGeneratorVO.outputDir` / `projectPrefix` / `parent` | `DefGenProjectController.java:78` |
| Transform | `parent` dots expanded to path separators; `outputDir` used as the base path | `ProjectUtils.java:367-369` |
| Sink | `new File(outputFile)` + `new FileOutputStream(file)` | `ProjectUtils.java:342, 347` |

The request body's `outputDir`, `projectPrefix`, and `parent` are concatenated into the output file path for each generated template. None of these values are canonicalized or checked against a base directory. Because `parent` has its `.` characters replaced with the platform file separator and `outputDir` is accepted verbatim, a caller can drive the final path anywhere reachable by the process (absolute path, or `../` traversal).

### Key code

The generator controller accepts the body and forwards it with no method-level authorization (`DefGenProjectController` has no `@PreAuthorize`/`@SaCheckPermission`):

```java
// lamp-generator/lamp-generator-controller/.../controller/DefGenProjectController.java:75
@PostMapping("/generator")
@WebLog(value = "生成项目")
public R generator(@RequestBody @Validated ProjectGeneratorVO projectGenerator) {
defGenTableService.generator(projectGenerator);
return R.success(true);
}
```

The path is built from request-controlled fields and written without any traversal check (`@RequestMapping("/defGenProject")` at line 37):

```java
// lamp-generator/lamp-generator-biz/.../generator/utils/ProjectUtils.java:340
@SneakyThrows
private static void writer(Map objectMap, String templatePath, String outputFile) {
File file = new File(outputFile); // :342 — request-controlled path
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs(); // :344 — creates arbitrary dirs
}
Template template = TemplateUtils.getTemplate(templatePath);
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) { // :347
template.process(objectMap, new OutputStreamWriter(fileOutputStream, GenCodeConstant.UTF8));
}
}
```

The base directory comes straight from the request body — `parent` is converted by replacing dots with separators, and `outputDir` is taken as-is:

```java
// ProjectUtils.java:365-369
String outputDir = "";
String projectPrefix = vo.getProjectPrefix();
String parent = vo.getParent();
String parentPath = StrUtil.replace(parent, StrPool.DOT, File.separator);
```

## Proof of Concept

Request-level only. Requires a valid authenticated token; no elevated role is checked.

```http
POST /defGenProject/generator HTTP/1.1
Host:
Content-Type: application/json
Authorization: Bearer

{
"outputDir": "../../opt/app/config",
"projectPrefix": "evil",
"parent": "com.example",
"serviceName": "demo",
"type": "CLOUD"
}
```

The generated files are written under `../../opt/app/config` relative to the working directory (or, with an absolute `outputDir`, to that absolute location) rather than a confined scratch directory.

## Impact

An authenticated remote attacker can write attacker-chosen content (generated template output) to arbitrary paths on the host. If a writable target overlaps the application's classpath, configuration directory, or startup scripts, this can be escalated to remote code execution. The endpoint does not require any elevated role — only a valid login token.

## Remediation

- Canonicalize every output path (`Path.normalize()` / `File.getCanonicalFile()`) and reject any result that does not stay within a dedicated, fixed generator output directory.
- Reject `outputDir`, `projectPrefix`, and `parent` values containing path separators, `..`, or absolute paths at the controller boundary before they reach file APIs.
- Derive the output location from trusted server configuration rather than echoing request fields into the filesystem path.
- Add an explicit authorization check (`@PreAuthorize` / `@SaCheckPermission`) restricting the generator endpoint to administrators.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with DefGenProjectController.java:75-78 and ProjectUtils.java:340-369, then trace ProjectGeneratorVO fields through the generator endpoint. Verify that generated output is confined to a trusted base directory, unsafe path values are rejected, and the endpoint has an appropriate authorization check; no test file is named in the issue.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring-boot
Domain
backend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.