[Security] OS command injection in deploy server-reduction (`appName`)
Severity: Critical · CWE: CWE-78 (Improper Neutralization of Special Elements used in an OS Command) Affected versions: v2.7
Summary
The deploy "system reduction" endpoint reads appName from the JSON request body and interpolates it directly into a shell command (rm -rf <deployPath>/<appName>) that is sent over a raw interactive ChannelShell session to the deploy target host. No escaping, quoting, or allowlist is applied to appName, so an authenticated user holding the deploy:edit permission can inject shell metacharacters and execute arbitrary commands on the target host. Earlier hardening of the App entity and the upload checkFile() path does not cover this handler, which reads the value from the DeployHistory request body rather than the persisted App entity.
Vulnerability chain
| Stage | Component | Location |
|---|---|---|
| Source | DeployController.serverReduction binds @RequestBody DeployHistory.appName |
eladmin-system/.../maint/rest/DeployController.java:126-131 |
| Auth gate | @PreAuthorize("@el.check('deploy:edit')") |
DeployController.java:127 |
| Sink (command build) | executeShellUtil.execute("rm -rf " + deployPath + FILE_SEPARATOR + resources.getAppName()) |
eladmin-system/.../maint/service/impl/DeployServiceImpl.java:369 |
| Exec | ChannelShell driven by printWriter.println(command) |
eladmin-system/.../maint/util/ExecuteShellUtil.java:52-66 |
The controller accepts a DeployHistory object whose appName field is a free-form String. DeployServiceImpl.serverReduction passes it unmodified into a command string and executes it through ExecuteShellUtil.execute, which opens a JSch ChannelShell, writes the command to the shell input stream via PrintWriter.println, and reads back stdout. Because the value is concatenated raw into a shell line, any metacharacter (;, |, `, $(), newline) is interpreted by the remote shell.
Key code
DeployController.serverReduction — source, body-bound appName (DeployController.java:126-131):
@PostMapping(value = "/serverReduction")
@PreAuthorize("@el.check('deploy:edit')")
public ResponseEntity<Object> serverReduction(@Validated @RequestBody DeployHistory resources){
String result = deployService.serverReduction(resources);
return new ResponseEntity<>(result,HttpStatus.OK);
}
DeployServiceImpl.serverReduction — sink, unescaped interpolation (DeployServiceImpl.java:369):
executeShellUtil.execute("rm -rf " + deployPath + FILE_SEPARATOR + resources.getAppName());
ExecuteShellUtil.execute — raw shell channel (ExecuteShellUtil.java:52-66):
public int execute(final String command) {
...
channel = (ChannelShell) session.openChannel("shell");
channel.connect();
input = new BufferedReader(new InputStreamReader(channel.getInputStream()));
printWriter = new PrintWriter(channel.getOutputStream());
printWriter.println(command);
printWriter.println("exit");
Proof of Concept
The injection runs on the deploy target host configured for the deployment record. The payload below uses a benign metacharacter to demonstrate that a second command is executed — it is not a working attack payload.
POST /api/deploy/serverReduction HTTP/1.1
Host: <eladmin-host>
Authorization: Bearer <JWT with deploy:edit>
Content-Type: application/json
{"appName":"x; id; #","ip":"<deploy-target-ip>","deployId":<id>}
The shell on the target host receives rm -rf <deployPath>/x; id; #, so id runs as a separate command (visible in ExecuteShellUtil stdout / server logs) before the trailing # comments out the rest of the line.
Impact
- Authenticated remote command execution on the deploy target host. Any principal with the
deploy:editpermission (typically an operator/admin role) can execute arbitrary OS commands on any host reachable from the configured deploy records. - Because the command runs through an interactive shell channel, full control of the target is possible (data exfiltration, lateral movement, persistence), limited only by the privileges of the deploy SSH account.
Remediation
- Do not interpolate user input into shell strings. Delete files via Java
File/PathAPIs, or use JSchChannelExecwith a fixed argv and pass the application name as an isolated argument. - If a shell command is unavoidable, strictly validate
appNameagainst an allowlist of filename characters (reject/,\,.., shell metacharacters, control characters) before use. - Apply the same treatment to the sibling commands built from request-derived values — for example the
cp -rcommand usingbackupPathatDeployServiceImpl.java:372— which interpolate the same untrustedappName.
Source: elunez/eladmin