Arbitrary file write via code generator (path traversal in `outputDir`)
- 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