Skip to content

Commit b68517f

Browse files
committed
fix(security): escape shell arg single quotes
1 parent d55efa3 commit b68517f

5 files changed

Lines changed: 23 additions & 5 deletions

File tree

plugins/security/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,9 @@ cp.exec('bash /home/admin/ali-knowledge-graph-backend/initrun.sh ' + ctx.helper.
440440

441441
### .escapeShellArg()
442442

443-
Escape command line arguments. Add single quotes around a string and quotes/escapes any existing single quotes allowing you to pass a string directly to a shell function and having it be treated as a single safe argument.
443+
Escape POSIX shell command line arguments. Add single quotes around a string and quotes/escapes any existing single quotes allowing you to pass a string directly to a shell function and having it be treated as a single safe argument.
444+
445+
Prefer `child_process.execFile()` or `child_process.spawn()` with an arguments array when possible. This helper is for a single argument in a POSIX shell command string, and is not a Windows `cmd.exe` or PowerShell escaping helper.
444446

445447
```js
446448
const ip = '127.0.0.1 && cat /etc/passwd';

plugins/security/README.zh-CN.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,9 @@ cp.exec('bash /home/admin/ali-knowledge-graph-backend/initrun.sh ' + this.helper
358358

359359
### .escapeShellArg()
360360

361-
命令行参数转义。给字符串增加一对单引号并且能引用或者转码任何已经存在的单引号, 这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。
361+
POSIX shell 命令行参数转义。给字符串增加一对单引号并且能引用或者转码任何已经存在的单引号, 这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。
362+
363+
如果可以,优先使用 `child_process.execFile()``child_process.spawn()` 的参数数组。这个 helper 只用于 POSIX shell 命令字符串里的单个参数,不适用于 Windows `cmd.exe` 或 PowerShell 转义。
362364

363365
```js
364366
const ip = '127.0.0.1 && cat /etc/passwd';
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export default function escapeShellArg(text: string): string {
22
const str = '' + text;
3-
return "'" + str.replace(/\\/g, '\\\\').replace(/'/g, "\\'") + "'";
3+
return "'" + str.replace(/'/g, "'\\''") + "'";
44
}

plugins/security/test/app/extends/escapeShellArg.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
import { exec as childProcessExec } from 'node:child_process';
2+
import { promisify } from 'node:util';
3+
14
import { mm, type MockApplication } from '@eggjs/mock';
2-
import { beforeAll, afterAll, describe, it } from 'vitest';
5+
import { beforeAll, afterAll, describe, expect, it } from 'vitest';
36

7+
import escapeShellArg from '../../../src/lib/helper/escapeShellArg.ts';
48
import { getFixtures } from '../../utils.ts';
59

10+
const exec = promisify(childProcessExec);
11+
const itOnPosix = process.platform === 'win32' ? it.skip : it;
12+
613
describe('test/app/extends/escapeShellArg.test.ts', () => {
714
let app: MockApplication;
815
beforeAll(async () => {
@@ -26,5 +33,12 @@ describe('test/app/extends/escapeShellArg.test.ts', () => {
2633
it('should not affect normal arg', () => {
2734
return app.httpRequest().get('/escapeShellArg-3').expect(200).expect('true');
2835
});
36+
37+
itOnPosix('should keep single quotes inside one shell argument', async () => {
38+
const payload = "'; echo EGG_SECURITY_INJECTED; #";
39+
const { stdout } = await exec(`printf 'ARG:%s\\n' ${escapeShellArg(payload)}`);
40+
41+
expect(stdout).toBe("ARG:'; echo EGG_SECURITY_INJECTED; #\n");
42+
});
2943
});
3044
});

plugins/security/test/fixtures/apps/helper-escapeShellArg-app/app/router.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module.exports = (app) => {
1010
const port = "8889'|chmod 777 /tmp/muma.sh;echo \\";
1111
this.body =
1212
`cp.exec('./start.sh '+${this.helper.escapeShellArg(port)})` ===
13-
"cp.exec('./start.sh '+'8889\\'|chmod 777 /tmp/muma.sh;echo \\\\')";
13+
"cp.exec('./start.sh '+'8889'\\''|chmod 777 /tmp/muma.sh;echo \\')";
1414
});
1515

1616
app.get('/escapeShellArg-3', async function () {

0 commit comments

Comments
 (0)