diff --git a/README.md b/README.md index 18d4cd6..2b63d0b 100644 --- a/README.md +++ b/README.md @@ -457,7 +457,9 @@ after fix: ### .escapeShellArg() -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. +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. + +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. ```js const ip = '127.0.0.1 && cat /etc/passwd' diff --git a/README.zh-CN.md b/README.zh-CN.md index 72eed1b..e9470bb 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -370,7 +370,9 @@ __处理过程较复杂,性能损耗较大,尽量避免使用__ ### .escapeShellArg() -命令行参数转义。给字符串增加一对单引号并且能引用或者转码任何已经存在的单引号, 这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。 +POSIX shell 命令行参数转义。给字符串增加一对单引号并且能引用或者转码任何已经存在的单引号, 这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。 + +如果可以,优先使用 `child_process.execFile()` 或 `child_process.spawn()` 的参数数组。这个 helper 只用于 POSIX shell 命令字符串里的单个参数,不适用于 Windows `cmd.exe` 或 PowerShell 转义。 ```js const ip = '127.0.0.1 && cat /etc/passwd' diff --git a/package.json b/package.json index 234bc47..63462c2 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,10 @@ "tshy-after": "1", "typescript": "5" }, + "overrides": { + "fflate": "0.8.2", + "urllib": "4.4.0" + }, "scripts": { "lint": "eslint --cache src test --ext .ts", "pretest": "npm run clean && npm run lint -- --fix", diff --git a/src/lib/helper/escapeShellArg.ts b/src/lib/helper/escapeShellArg.ts index 9b801a6..b1f8d69 100644 --- a/src/lib/helper/escapeShellArg.ts +++ b/src/lib/helper/escapeShellArg.ts @@ -1,4 +1,4 @@ export default function escapeShellArg(text: string) { const str = '' + text; - return '\'' + str.replace(/\\/g, '\\\\').replace(/\'/g, '\\\'') + '\''; + return '\'' + str.replace(/'/g, '\'\\\'\'') + '\''; } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index b2145ca..24fa739 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -191,7 +191,9 @@ export function preprocessConfig(config: SecurityConfig) { }); } -export function getFromUrl(url: string, prop?: string): string | null { +export function getFromUrl(url: string): URL | null; +export function getFromUrl(url: string, prop: string): string | null; +export function getFromUrl(url: string, prop?: string): URL | string | null { try { const parsed = new URL(url); return prop ? Reflect.get(parsed, prop) : parsed; diff --git a/test/app/extends/escapeShellArg.test.ts b/test/app/extends/escapeShellArg.test.ts index fe3fcdb..302f77f 100644 --- a/test/app/extends/escapeShellArg.test.ts +++ b/test/app/extends/escapeShellArg.test.ts @@ -1,5 +1,14 @@ +import assert from 'node:assert/strict'; +import { exec as childProcessExec } from 'node:child_process'; +import { promisify } from 'node:util'; + import { mm, MockApplication } from '@eggjs/mock'; +import escapeShellArg from '../../../src/lib/helper/escapeShellArg.js'; + +const exec = promisify(childProcessExec); +const itOnPosix = process.platform === 'win32' ? it.skip : it; + describe('test/app/extends/escapeShellArg.test.ts', () => { let app: MockApplication; before(() => { @@ -34,5 +43,12 @@ describe('test/app/extends/escapeShellArg.test.ts', () => { .expect(200) .expect('true'); }); + + itOnPosix('should keep single quotes inside one shell argument', async () => { + const payload = "'; echo EGG_SECURITY_INJECTED; #"; + const { stdout } = await exec(`printf 'ARG:%s\\n' ${escapeShellArg(payload)}`); + + assert.equal(stdout, 'ARG:\'; echo EGG_SECURITY_INJECTED; #\n'); + }); }); }); diff --git a/test/fixtures/apps/helper-escapeShellArg-app/app/router.js b/test/fixtures/apps/helper-escapeShellArg-app/app/router.js index 1ec4f11..2322713 100644 --- a/test/fixtures/apps/helper-escapeShellArg-app/app/router.js +++ b/test/fixtures/apps/helper-escapeShellArg-app/app/router.js @@ -6,7 +6,7 @@ module.exports = app => { app.get('/escapeShellArg-2', async function() { const port = '8889\'|chmod 777 /tmp/muma.sh;echo \\'; - this.body = `cp.exec('./start.sh '+${this.helper.escapeShellArg(port)})` === 'cp.exec(\'./start.sh \'+\'8889\\\'|chmod 777 /tmp/muma.sh;echo \\\\\')'; + this.body = `cp.exec('./start.sh '+${this.helper.escapeShellArg(port)})` === 'cp.exec(\'./start.sh \'+\'8889\'\\\'\'|chmod 777 /tmp/muma.sh;echo \\\')'; }); app.get('/escapeShellArg-3', async function() {